diff --git a/CHANGELOG.md b/CHANGELOG.md index b56c719..bd6ffe2 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/Makefile b/Makefile index 2ac1ff9..94fadc8 100644 --- a/Makefile +++ b/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 diff --git a/doc/debughttp.md b/doc/debughttp.md index 7a6de92..59ca4e7 100644 --- a/doc/debughttp.md +++ b/doc/debughttp.md @@ -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` diff --git a/doc/environment.md b/doc/environment.md index b8053e2..f1e6bf5 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -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`. +## `LOG_FORMAT` + +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. + ## `LOG_TIMESTAMP` If `1`, show timestamps in the plain text logs. If `0`, don't show the timestamps. diff --git a/go.mod b/go.mod index 1dac177..c5384f2 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 4905022..63c4b72 100644 --- a/go.sum +++ b/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= diff --git a/go.work b/go.work index f91c55e..6fa92c9 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.23.4 +go 1.23.6 use ( . diff --git a/go.work.sum b/go.work.sum index 9d83983..e857cfb 100644 --- a/go.work.sum +++ b/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= diff --git a/internal/access/engine.go b/internal/access/engine.go index 26c9548..d81e6bb 100644 --- a/internal/access/engine.go +++ b/internal/access/engine.go @@ -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 diff --git a/internal/agd/agd.go b/internal/agd/agd.go index 2d1349b..d5ec5fd 100644 --- a/internal/agd/agd.go +++ b/internal/agd/agd.go @@ -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 - } -} diff --git a/internal/agd/context.go b/internal/agd/context.go index 6132aae..0d540f7 100644 --- a/internal/agd/context.go +++ b/internal/agd/context.go @@ -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) { diff --git a/internal/agd/device.go b/internal/agd/device.go index 62832aa..1ec3bf7 100644 --- a/internal/agd/device.go +++ b/internal/agd/device.go @@ -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 diff --git a/internal/agd/devicetype.go b/internal/agd/devicetype.go index 1ad15c9..4ac3572 100644 --- a/internal/agd/devicetype.go +++ b/internal/agd/devicetype.go @@ -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 diff --git a/internal/agd/filteringgroup.go b/internal/agd/filteringgroup.go index 0725150..a0efba9 100644 --- a/internal/agd/filteringgroup.go +++ b/internal/agd/filteringgroup.go @@ -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. diff --git a/internal/agd/humanid.go b/internal/agd/humanid.go index 7427a98..b8b5a29 100644 --- a/internal/agd/humanid.go +++ b/internal/agd/humanid.go @@ -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. diff --git a/internal/agd/profile.go b/internal/agd/profile.go index 68b2331..8e52091 100644 --- a/internal/agd/profile.go +++ b/internal/agd/profile.go @@ -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) } diff --git a/internal/agd/requestid.go b/internal/agd/requestid.go index 75c49eb..df0918c 100644 --- a/internal/agd/requestid.go +++ b/internal/agd/requestid.go @@ -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. diff --git a/internal/agd/requestid_test.go b/internal/agd/requestid_test.go index 5ef9e90..ccd2ecc 100644 --- a/internal/agd/requestid_test.go +++ b/internal/agd/requestid_test.go @@ -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 } diff --git a/internal/agd/servergroup.go b/internal/agd/servergroup.go deleted file mode 100644 index e0c7024..0000000 --- a/internal/agd/servergroup.go +++ /dev/null @@ -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 -} diff --git a/internal/agdrand/agdrand.go b/internal/agdrand/agdrand.go new file mode 100644 index 0000000..ee90727 --- /dev/null +++ b/internal/agdrand/agdrand.go @@ -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 +} diff --git a/internal/agdrand/agdrand_test.go b/internal/agdrand/agdrand_test.go new file mode 100644 index 0000000..25eb717 --- /dev/null +++ b/internal/agdrand/agdrand_test.go @@ -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 +} diff --git a/internal/agdservice/agdservice.go b/internal/agdservice/agdservice.go deleted file mode 100644 index 3fa27bc..0000000 --- a/internal/agdservice/agdservice.go +++ /dev/null @@ -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{} diff --git a/internal/agdservice/agdservice_test.go b/internal/agdservice/agdservice_test.go deleted file mode 100644 index a708d88..0000000 --- a/internal/agdservice/agdservice_test.go +++ /dev/null @@ -1,6 +0,0 @@ -package agdservice_test - -import "time" - -// testTimeout is the timeout for common test operations. -const testTimeout = 1 * time.Second diff --git a/internal/agdservice/refresh.go b/internal/agdservice/refresh.go deleted file mode 100644 index fbde0ab..0000000 --- a/internal/agdservice/refresh.go +++ /dev/null @@ -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 -} diff --git a/internal/agdservice/refresh_test.go b/internal/agdservice/refresh_test.go deleted file mode 100644 index 85541a8..0000000 --- a/internal/agdservice/refresh_test.go +++ /dev/null @@ -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) - }) -} diff --git a/internal/agdtest/interface.go b/internal/agdtest/interface.go index 47e7ba8..262f036 100644 --- a/internal/agdtest/interface.go +++ b/internal/agdtest/interface.go @@ -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 diff --git a/internal/agdtime/agdtime.go b/internal/agdtime/agdtime.go index e0efc2c..c0da4c8 100644 --- a/internal/agdtime/agdtime.go +++ b/internal/agdtime/agdtime.go @@ -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. // diff --git a/internal/agdvalidate/agdvalidate.go b/internal/agdvalidate/agdvalidate.go new file mode 100644 index 0000000..6953df5 --- /dev/null +++ b/internal/agdvalidate/agdvalidate.go @@ -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 + } +} diff --git a/internal/backendpb/backendpb.go b/internal/backendpb/backendpb.go index 45dc3b1..2a160e3 100644 --- a/internal/backendpb/backendpb.go +++ b/internal/backendpb/backendpb.go @@ -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) } diff --git a/internal/backendpb/backendpb_internal_test.go b/internal/backendpb/backendpb_internal_test.go index 7c012ed..3554d07 100644 --- a/internal/backendpb/backendpb_internal_test.go +++ b/internal/backendpb/backendpb_internal_test.go @@ -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. // diff --git a/internal/backendpb/dns.pb.go b/internal/backendpb/dns.pb.go index 0c07bdb..fe8dfb0 100644 --- a/internal/backendpb/dns.pb.go +++ b/internal/backendpb/dns.pb.go @@ -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 } diff --git a/internal/backendpb/dns_grpc.pb.go b/internal/backendpb/dns_grpc.pb.go index 945451c..38bdf21 100644 --- a/internal/backendpb/dns_grpc.pb.go +++ b/internal/backendpb/dns_grpc.pb.go @@ -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 diff --git a/internal/backendpb/profile.go b/internal/backendpb/profile.go index ec6e8d3..20bdf8c 100644 --- a/internal/backendpb/profile.go +++ b/internal/backendpb/profile.go @@ -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{ diff --git a/internal/backendpb/profile_internal_test.go b/internal/backendpb/profile_internal_test.go index 7ef9dc4..eeece71 100644 --- a/internal/backendpb/profile_internal_test.go +++ b/internal/backendpb/profile_internal_test.go @@ -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, ) diff --git a/internal/backendpb/profiledb.go b/internal/backendpb/profiledb.go index 3477150..ddcd8e6 100644 --- a/internal/backendpb/profiledb.go +++ b/internal/backendpb/profiledb.go @@ -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, ) diff --git a/internal/backendpb/profiledb_internal_test.go b/internal/backendpb/profiledb_internal_test.go index a81b603..333a97c 100644 --- a/internal/backendpb/profiledb_internal_test.go +++ b/internal/backendpb/profiledb_internal_test.go @@ -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", }} diff --git a/internal/backendpb/profiledb_test.go b/internal/backendpb/profiledb_test.go index f872646..d195910 100644 --- a/internal/backendpb/profiledb_test.go +++ b/internal/backendpb/profiledb_test.go @@ -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(), diff --git a/internal/backendpb/ratelimiter.go b/internal/backendpb/ratelimiter.go index 5572752..8cc51ee 100644 --- a/internal/backendpb/ratelimiter.go +++ b/internal/backendpb/ratelimiter.go @@ -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") diff --git a/internal/billstat/metrics.go b/internal/billstat/metrics.go index 25907da..2218de8 100644 --- a/internal/billstat/metrics.go +++ b/internal/billstat/metrics.go @@ -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) {} diff --git a/internal/billstat/runtime.go b/internal/billstat/runtime.go index b4bbd9c..638f3e1 100644 --- a/internal/billstat/runtime.go +++ b/internal/billstat/runtime.go @@ -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)) } diff --git a/internal/cmd/access.go b/internal/cmd/access.go index bd4f5d0..486efad 100644 --- a/internal/cmd/access.go +++ b/internal/cmd/access.go @@ -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 } diff --git a/internal/cmd/additional.go b/internal/cmd/additional.go index 19103f3..ea71537 100644 --- a/internal/cmd/additional.go +++ b/internal/cmd/additional.go @@ -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...) } diff --git a/internal/cmd/backend.go b/internal/cmd/backend.go index d8824c5..534b4aa 100644 --- a/internal/cmd/backend.go +++ b/internal/cmd/backend.go @@ -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 diff --git a/internal/cmd/builder.go b/internal/cmd/builder.go index c286311..ee6f172 100644 --- a/internal/cmd/builder.go +++ b/internal/cmd/builder.go @@ -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. // diff --git a/internal/cmd/cache.go b/internal/cmd/cache.go index ef222a7..6654233 100644 --- a/internal/cmd/cache.go +++ b/internal/cmd/cache.go @@ -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...) } diff --git a/internal/cmd/check.go b/internal/cmd/check.go index 72f572f..b3129e0 100644 --- a/internal/cmd/check.go +++ b/internal/cmd/check.go @@ -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 } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 1feb5ab..9904882 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -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)) diff --git a/internal/cmd/config.go b/internal/cmd/config.go index e8d23c1..9c26f48 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -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 diff --git a/internal/cmd/conncheck.go b/internal/cmd/conncheck.go index 0ab6a78..30476c6 100644 --- a/internal/cmd/conncheck.go +++ b/internal/cmd/conncheck.go @@ -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() { diff --git a/internal/cmd/context.go b/internal/cmd/context.go deleted file mode 100644 index 5765ab8..0000000 --- a/internal/cmd/context.go +++ /dev/null @@ -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) - } -} diff --git a/internal/cmd/ddr.go b/internal/cmd/ddr.go index 583c6b6..b5db58d 100644 --- a/internal/cmd/ddr.go +++ b/internal/cmd/ddr.go @@ -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 diff --git a/internal/cmd/dns.go b/internal/cmd/dns.go index c097984..cf95c66 100644 --- a/internal/cmd/dns.go +++ b/internal/cmd/dns.go @@ -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), + ) } diff --git a/internal/cmd/dnscrypt.go b/internal/cmd/dnscrypt.go index 2474c4a..0af522c 100644 --- a/internal/cmd/dnscrypt.go +++ b/internal/cmd/dnscrypt.go @@ -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...) } diff --git a/internal/cmd/dnsdb.go b/internal/cmd/dnsdb.go index 80bdc0a..15e8d95 100644 --- a/internal/cmd/dnsdb.go +++ b/internal/cmd/dnsdb.go @@ -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) } diff --git a/internal/cmd/env.go b/internal/cmd/env.go index 87ff37d..5ceee25 100644 --- a/internal/cmd/env.go +++ b/internal/cmd/env.go @@ -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. diff --git a/internal/cmd/error.go b/internal/cmd/error.go index 7fc9f78..6d4aee6 100644 --- a/internal/cmd/error.go +++ b/internal/cmd/error.go @@ -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) -} diff --git a/internal/cmd/filter.go b/internal/cmd/filter.go index 0596a32..9edd671 100644 --- a/internal/cmd/filter.go +++ b/internal/cmd/filter.go @@ -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) } diff --git a/internal/cmd/filteringgroup.go b/internal/cmd/filteringgroup.go index 4ebc06b..aa88dd9 100644 --- a/internal/cmd/filteringgroup.go +++ b/internal/cmd/filteringgroup.go @@ -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...) } diff --git a/internal/cmd/geoip.go b/internal/cmd/geoip.go index d691136..6941db6 100644 --- a/internal/cmd/geoip.go +++ b/internal/cmd/geoip.go @@ -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), + ) } diff --git a/internal/cmd/ifacelistener.go b/internal/cmd/ifacelistener.go index bb67238..46986dc 100644 --- a/internal/cmd/ifacelistener.go +++ b/internal/cmd/ifacelistener.go @@ -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), + ) } diff --git a/internal/cmd/network.go b/internal/cmd/network.go index 1472b4b..5a0e90a 100644 --- a/internal/cmd/network.go +++ b/internal/cmd/network.go @@ -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 diff --git a/internal/cmd/querylog.go b/internal/cmd/querylog.go index 5ae9e6a..dd5e777 100644 --- a/internal/cmd/querylog.go +++ b/internal/cmd/querylog.go @@ -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. diff --git a/internal/cmd/ratelimit.go b/internal/cmd/ratelimit.go index b107f20..a3923f0 100644 --- a/internal/cmd/ratelimit.go +++ b/internal/cmd/ratelimit.go @@ -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) } diff --git a/internal/cmd/safebrowsing.go b/internal/cmd/safebrowsing.go index ca84772..06462b5 100644 --- a/internal/cmd/safebrowsing.go +++ b/internal/cmd/safebrowsing.go @@ -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), + ) } diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 4b512d1..6197e16 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -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...) } diff --git a/internal/cmd/servergroup.go b/internal/cmd/servergroup.go index 648a282..95fb08a 100644 --- a/internal/cmd/servergroup.go +++ b/internal/cmd/servergroup.go @@ -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) } } diff --git a/internal/cmd/tls.go b/internal/cmd/tls.go index 25ef9ea..afd1352 100644 --- a/internal/cmd/tls.go +++ b/internal/cmd/tls.go @@ -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...) } diff --git a/internal/cmd/upstream.go b/internal/cmd/upstream.go index 450b4a4..bfbb6c1 100644 --- a/internal/cmd/upstream.go +++ b/internal/cmd/upstream.go @@ -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), }) } diff --git a/internal/cmd/validation.go b/internal/cmd/validation.go deleted file mode 100644 index e0afa1d..0000000 --- a/internal/cmd/validation.go +++ /dev/null @@ -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 -} diff --git a/internal/cmd/websvc.go b/internal/cmd/websvc.go index 76794f6..3d45248 100644 --- a/internal/cmd/websvc.go +++ b/internal/cmd/websvc.go @@ -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 } diff --git a/internal/connlimiter/conn.go b/internal/connlimiter/conn.go index e7a2a23..82d3941 100644 --- a/internal/connlimiter/conn.go +++ b/internal/connlimiter/conn.go @@ -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 } diff --git a/internal/connlimiter/limiter.go b/internal/connlimiter/limiter.go index 1940a56..ca3b1cb 100644 --- a/internal/connlimiter/limiter.go +++ b/internal/connlimiter/limiter.go @@ -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, } diff --git a/internal/connlimiter/limiter_test.go b/internal/connlimiter/limiter_test.go index f747e39..8d6ba2f 100644 --- a/internal/connlimiter/limiter_test.go +++ b/internal/connlimiter/limiter_test.go @@ -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) -} diff --git a/internal/connlimiter/listenconfig_test.go b/internal/connlimiter/listenconfig_test.go index ef86698..32f775b 100644 --- a/internal/connlimiter/listenconfig_test.go +++ b/internal/connlimiter/listenconfig_test.go @@ -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) diff --git a/internal/connlimiter/listener.go b/internal/connlimiter/listener.go index 80bd01f..6ec41ff 100644 --- a/internal/connlimiter/listener.go +++ b/internal/connlimiter/listener.go @@ -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() } diff --git a/internal/connlimiter/metrics.go b/internal/connlimiter/metrics.go new file mode 100644 index 0000000..5ad5f46 --- /dev/null +++ b/internal/connlimiter/metrics.go @@ -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) {} diff --git a/internal/consul/allowlist.go b/internal/consul/allowlist.go index a1d2023..2243c51 100644 --- a/internal/consul/allowlist.go +++ b/internal/consul/allowlist.go @@ -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") diff --git a/internal/debugsvc/debugsvc_test.go b/internal/debugsvc/debugsvc_test.go index 3083ca9..fba977b 100644 --- a/internal/debugsvc/debugsvc_test.go +++ b/internal/debugsvc/debugsvc_test.go @@ -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") diff --git a/internal/debugsvc/refresh.go b/internal/debugsvc/refresh.go index 2acd48d..0eacd80 100644 --- a/internal/debugsvc/refresh.go +++ b/internal/debugsvc/refresh.go @@ -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 { diff --git a/internal/dnscheck/dnscheck.go b/internal/dnscheck/dnscheck.go index ed9e63a..c34f162 100644 --- a/internal/dnscheck/dnscheck.go +++ b/internal/dnscheck/dnscheck.go @@ -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 } diff --git a/internal/dnscheck/error.go b/internal/dnscheck/error.go deleted file mode 100644 index b83a721..0000000 --- a/internal/dnscheck/error.go +++ /dev/null @@ -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() -} diff --git a/internal/dnscheck/metrics.go b/internal/dnscheck/metrics.go new file mode 100644 index 0000000..527854c --- /dev/null +++ b/internal/dnscheck/metrics.go @@ -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 + } +} diff --git a/internal/dnscheck/remotekv.go b/internal/dnscheck/remotekv.go index 3933471..489325d 100644 --- a/internal/dnscheck/remotekv.go +++ b/internal/dnscheck/remotekv.go @@ -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") }() diff --git a/internal/dnscheck/remotekv_test.go b/internal/dnscheck/remotekv_test.go index 9452a73..2006af5 100644 --- a/internal/dnscheck/remotekv_test.go +++ b/internal/dnscheck/remotekv_test.go @@ -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) diff --git a/internal/dnsdb/buffer.go b/internal/dnsdb/buffer.go index 4e23647..8b62679 100644 --- a/internal/dnsdb/buffer.go +++ b/internal/dnsdb/buffer.go @@ -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. diff --git a/internal/dnsdb/dnsdb.go b/internal/dnsdb/dnsdb.go index f798333..efb2bcf 100644 --- a/internal/dnsdb/dnsdb.go +++ b/internal/dnsdb/dnsdb.go @@ -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 } diff --git a/internal/dnsdb/http.go b/internal/dnsdb/http.go index a54200c..7ba6890 100644 --- a/internal/dnsdb/http.go +++ b/internal/dnsdb/http.go @@ -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) diff --git a/internal/dnsdb/http_test.go b/internal/dnsdb/http_test.go index 9e124cb..c78fd66 100644 --- a/internal/dnsdb/http_test.go +++ b/internal/dnsdb/http_test.go @@ -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() diff --git a/internal/dnsdb/metrics.go b/internal/dnsdb/metrics.go new file mode 100644 index 0000000..39be0f0 --- /dev/null +++ b/internal/dnsdb/metrics.go @@ -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) {} diff --git a/internal/dnsmsg/constructor.go b/internal/dnsmsg/constructor.go index 3ed4b36..9ab2ea7 100644 --- a/internal/dnsmsg/constructor.go +++ b/internal/dnsmsg/constructor.go @@ -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 diff --git a/internal/dnsmsg/constructor_test.go b/internal/dnsmsg/constructor_test.go index 74e3be4..9398dc8 100644 --- a/internal/dnsmsg/constructor_test.go +++ b/internal/dnsmsg/constructor_test.go @@ -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 +} diff --git a/internal/dnsmsg/dnsmsg_test.go b/internal/dnsmsg/dnsmsg_test.go index 83dea9b..8e799aa 100644 --- a/internal/dnsmsg/dnsmsg_test.go +++ b/internal/dnsmsg/dnsmsg_test.go @@ -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" diff --git a/internal/dnsmsg/svcbmsg.go b/internal/dnsmsg/svcbmsg.go index ed33288..4251c5f 100644 --- a/internal/dnsmsg/svcbmsg.go +++ b/internal/dnsmsg/svcbmsg.go @@ -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 } diff --git a/internal/dnsserver/cache/cache.go b/internal/dnsserver/cache/cache.go index 535c9bd..9738e17 100644 --- a/internal/dnsserver/cache/cache.go +++ b/internal/dnsserver/cache/cache.go @@ -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 } diff --git a/internal/dnsserver/cache/cache_test.go b/internal/dnsserver/cache/cache_test.go index f7c9cd4..b2a6a9f 100644 --- a/internal/dnsserver/cache/cache_test.go +++ b/internal/dnsserver/cache/cache_test.go @@ -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, diff --git a/internal/dnsserver/cache/metrics.go b/internal/dnsserver/cache/metrics.go index 7dada82..d1571f6 100644 --- a/internal/dnsserver/cache/metrics.go +++ b/internal/dnsserver/cache/metrics.go @@ -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) diff --git a/internal/dnsserver/context.go b/internal/dnsserver/context.go index 0a23d15..69cf8b1 100644 --- a/internal/dnsserver/context.go +++ b/internal/dnsserver/context.go @@ -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 diff --git a/internal/dnsserver/dnsserver.go b/internal/dnsserver/dnsserver.go index 614a12a..6b301ef 100644 --- a/internal/dnsserver/dnsserver.go +++ b/internal/dnsserver/dnsserver.go @@ -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 -} diff --git a/internal/dnsserver/dnsserver_test.go b/internal/dnsserver/dnsserver_test.go index 038c7d7..f9869c3 100644 --- a/internal/dnsserver/dnsserver_test.go +++ b/internal/dnsserver/dnsserver_test.go @@ -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 diff --git a/internal/dnsserver/dnsservertest/server.go b/internal/dnsserver/dnsservertest/server.go index b89c592..fe200ef 100644 --- a/internal/dnsserver/dnsservertest/server.go +++ b/internal/dnsserver/dnsservertest/server.go @@ -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, }, } diff --git a/internal/dnsserver/doc.go b/internal/dnsserver/doc.go index b23178a..9c39802 100644 --- a/internal/dnsserver/doc.go +++ b/internal/dnsserver/doc.go @@ -49,7 +49,7 @@ is specified in the configuration. Here's how to create a simple plain DNS server: conf := dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ + Base: &dnsserver.ConfigBase{ // server name Name: "test", // listen address @@ -70,8 +70,8 @@ In order to use a DoT server, you also need to supply a [*tls.Config] with the certificate and its private key. conf := dnsserver.ConfigTLS{ - ConfigDNS: dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ + DNS: &dnsserver.ConfigDNS{ + Base: &dnsserver.ConfigBase{ Name: "test", Addr: "127.0.0.1:0", Handler: h, @@ -87,11 +87,11 @@ certificate and its private key. DoH server uses an [*http.Server] and/or [*http3.Server] internally. There are a couple of things to note: - 1. tls.Config can be omitted, but you must set [ConfigBase.Network] to - NetworkTCP. In this case the server will work simply as a plain HTTP - server. This might be useful if you're running a reverse proxy like Nginx - in front of your DoH server. If you do specify it, the server will listen - to both DoH2 and DoH3 by default. + 1. tls.Config can be omitted, but you must set [Base.Network] to NetworkTCP. + In this case the server will work simply as a plain HTTP server. This might + be useful if you're running a reverse proxy like Nginx in front of your DoH + server. If you do specify it, the server will listen to both DoH2 and DoH3 + by default. 2. In the constructor you can specify an optional [http.HandlerFunc] that processes non-DNS requests, e.g. requests to paths different from @@ -100,7 +100,7 @@ a couple of things to note: Example: conf := dnsserver.ConfigHTTPS{ - ConfigBase: dnsserver.ConfigBase{ + Base: &dnsserver.ConfigBase{ Name: "test", Addr: "127.0.0.1:0", Handler: h, @@ -117,7 +117,7 @@ DoQ server uses the [quic-go module]. Just like DoH and DoT, it requires a [*tls.Config] to encrypt the data. conf := dnsserver.ConfigQUIC{ - ConfigBase: dnsserver.ConfigBase{ + Base: &dnsserver.ConfigBase{ Name: "test", Addr: "127.0.0.1:0", Handler: h, @@ -134,7 +134,7 @@ server you need to supply DNSCrypt configuration. Read the [module documentation] about how to initialize it. conf := dnsserver.ConfigDNSCrypt{ - ConfigBase: dnsserver.ConfigBase{ + Base: &dnsserver.ConfigBase{ Name: "test", Addr: "127.0.0.1:0", Handler: h, diff --git a/internal/dnsserver/error.go b/internal/dnsserver/error.go index a1341ee..ae51d79 100644 --- a/internal/dnsserver/error.go +++ b/internal/dnsserver/error.go @@ -1,11 +1,15 @@ package dnsserver import ( + "context" "fmt" + "io" + "log/slog" "net" "os" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" ) // Common Errors And Error Helpers @@ -67,3 +71,15 @@ func isNonCriticalNetError(err error) (ok bool) { return errors.As(err, &netErr) && netErr.Timeout() } + +// closeWithLog closes c and logs a debug message if c.Close returns an error +// that isn't [net.ErrClosed]. +// +// TODO(a.garipov): Unify error handling with regards to [io.EOF], +// net.ErrClosed, etc. +func closeWithLog(ctx context.Context, l *slog.Logger, msg string, c io.Closer) { + err := c.Close() + if err != nil && !errors.Is(err, net.ErrClosed) { + l.DebugContext(ctx, msg, slogutil.KeyError, err) + } +} diff --git a/internal/dnsserver/example_test.go b/internal/dnsserver/example_test.go index 4025830..9fab102 100644 --- a/internal/dnsserver/example_test.go +++ b/internal/dnsserver/example_test.go @@ -2,13 +2,14 @@ package dnsserver_test import ( "context" - "fmt" + "log/slog" "net/netip" "os" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/querylog" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/miekg/dns" ) @@ -27,13 +28,22 @@ func ExampleNewServerDNS() { }, ) + baseLogger := slogutil.New(&slogutil.Config{ + Format: slogutil.FormatText, + Level: slog.LevelDebug, + }).With("server_name", "test") + // Init the server with this handler func - conf := dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ + conf := &dnsserver.ConfigDNS{ + Base: &dnsserver.ConfigBase{ + BaseLogger: baseLogger, + // server name Name: "test", + // listen address Addr: "127.0.0.1:0", + // handler that will process incoming DNS queries Handler: handler, }, @@ -51,7 +61,13 @@ func ExampleNewServerDNS() { } }() - // Output: + // Unordered output: + // level=INFO msg="starting server" server_name=test + // level=INFO msg="server has been started" server_name=test + // level=INFO msg="shutting down server" server_name=test + // level=INFO msg="starting listening udp" server_name=test + // level=INFO msg="starting listening tcp" server_name=test + // level=INFO msg="server has been shut down" server_name=test } func ExampleWithMiddlewares() { @@ -63,24 +79,32 @@ func ExampleWithMiddlewares() { }}, }) - middleware := querylog.NewLogMiddleware(os.Stdout) + baseLogger := slogutil.New(&slogutil.Config{ + Format: slogutil.FormatText, + Level: slog.LevelDebug, + }) + + middleware := querylog.NewLogMiddleware(os.Stdout, baseLogger) handler := dnsserver.WithMiddlewares(forwarder, middleware) - // Init the server with this handler func - conf := dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ - Name: "test", - Addr: "127.0.0.1:0", - Handler: handler, + // Init the server with this handler func. + conf := &dnsserver.ConfigDNS{ + Base: &dnsserver.ConfigBase{ + BaseLogger: baseLogger.With("server_name", "test"), + Name: "test", + Addr: "127.0.0.1:0", + Handler: handler, }, } srv := dnsserver.NewServerDNS(conf) - err := srv.Start(context.Background()) + + ctx := context.Background() + err := srv.Start(ctx) if err != nil { panic("failed to start the server") } - fmt.Println("started successfully") + baseLogger.InfoContext(ctx, "started successfully") defer func() { err = srv.Shutdown(context.Background()) @@ -88,10 +112,16 @@ func ExampleWithMiddlewares() { panic("failed to shutdown the server") } - fmt.Println("stopped successfully") + baseLogger.InfoContext(ctx, "stopped successfully") }() - // Output: - // started successfully - // stopped successfully + // Unordered output: + // level=INFO msg="starting server" server_name=test + // level=INFO msg="server has been started" server_name=test + // level=INFO msg="started successfully" + // level=INFO msg="shutting down server" server_name=test + // level=INFO msg="starting listening udp" server_name=test + // level=INFO msg="starting listening tcp" server_name=test + // level=INFO msg="server has been shut down" server_name=test + // level=INFO msg="stopped successfully" } diff --git a/internal/dnsserver/forward/example_test.go b/internal/dnsserver/forward/example_test.go index e0c5b8b..f7b912e 100644 --- a/internal/dnsserver/forward/example_test.go +++ b/internal/dnsserver/forward/example_test.go @@ -7,13 +7,15 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" + "github.com/AdguardTeam/golibs/logutil/slogutil" ) func ExampleNewHandler() { - conf := dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ - Name: "srv", - Addr: "127.0.0.1:0", + conf := &dnsserver.ConfigDNS{ + Base: &dnsserver.ConfigBase{ + BaseLogger: slogutil.NewDiscardLogger(), + Name: "srv", + Addr: "127.0.0.1:0", Handler: forward.NewHandler(&forward.HandlerConfig{ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{ Network: forward.NetworkAny, diff --git a/internal/dnsserver/forward/forward.go b/internal/dnsserver/forward/forward.go index 0fc261f..815fac0 100644 --- a/internal/dnsserver/forward/forward.go +++ b/internal/dnsserver/forward/forward.go @@ -30,14 +30,16 @@ import ( "fmt" "io" "log/slog" + "math/rand/v2" "net" "sync" "time" + "github.com/AdguardTeam/AdGuardDNS/internal/agdrand" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/service" "github.com/miekg/dns" - "golang.org/x/exp/rand" ) // Handler is a struct that implements [dnsserver.Handler] and forwards DNS @@ -104,6 +106,10 @@ type HandlerConfig struct { // metrics. If not set, EmptyMetricsListener is used. MetricsListener MetricsListener + // RandSource is used for randomized upstream selection and other + // non-sensitive tasks. If it is nil, [rand.ChaCha8] is used. + RandSource rand.Source + // HealthcheckDomainTmpl is the template for domains used to perform // healthcheck queries. If the HealthcheckDomainTmpl contains the string // "${RANDOM}", all occurrences of this string are replaced with a random @@ -136,17 +142,21 @@ type HandlerConfig struct { // check afterwards if c.HealthcheckInitDuration is not zero. Note, that this // handler only support plain DNS upstreams. c must not be nil. func NewHandler(c *HandlerConfig) (h *Handler) { + src := c.RandSource + if src == nil { + // Do not initialize through [cmp.Or], as the default value could panic. + src = rand.NewChaCha8(agdrand.MustNewSeed()) + } + h = &Handler{ - logger: cmp.Or(c.Logger, slog.Default()), - rand: rand.New(&rand.LockedSource{}), + logger: cmp.Or(c.Logger, slog.Default()), + // #nosec G404 -- We don't need a real random, pseudorandom is enough. + rand: rand.New(agdrand.NewLockedSource(src)), activeUpstreamsMu: &sync.RWMutex{}, hcDomainTmpl: c.HealthcheckDomainTmpl, hcBackoff: c.HealthcheckBackoffDuration, } - // #nosec G115 -- The Unix epoch time is highly unlikely to be negative. - h.rand.Seed(uint64(time.Now().UnixNano())) - if l := c.MetricsListener; l != nil { h.metrics = l } else { @@ -230,7 +240,7 @@ func (h *Handler) ServeDNS( } if useFallbacks && len(h.fallbacks) > 0 { - i := h.rand.Intn(len(h.fallbacks)) + i := h.rand.IntN(len(h.fallbacks)) fallbackUps = h.fallbacks[i] resp, err = h.exchange(ctx, fallbackUps, req) } @@ -268,13 +278,15 @@ func (h *Handler) exchange( return resp, err } -// Refresh implements the [agdservice.Refresher] interface for *Handler. -// -// It checks the accessibility of main upstreams and updates handler's list of -// active upstreams. In case all main upstreams are down, it returns an error -// and when all requests are redirected to the fallbacks. When any of the main -// upstreams is detected to be up again, requests are redirected back to the -// main upstreams. +// type check +var _ service.Refresher = (*Handler)(nil) + +// Refresh implements the [service.Refresher] interface for *Handler. It checks +// the accessibility of main upstreams and updates handler's list of active +// upstreams. In case all main upstreams are down, it returns an error and when +// all requests are redirected to the fallbacks. When any of the main upstreams +// is detected to be up again, requests are redirected back to the main +// upstreams. func (h *Handler) Refresh(ctx context.Context) (err error) { h.logger.DebugContext(ctx, "healthcheck refresh started") defer h.logger.DebugContext(ctx, "healthcheck refresh finished") @@ -293,7 +305,7 @@ func (h *Handler) pickActiveUpstream() (u Upstream) { return nil } - i := h.rand.Intn(len(h.activeUpstreams)) + i := h.rand.IntN(len(h.activeUpstreams)) return h.activeUpstreams[i] } diff --git a/internal/dnsserver/forward/forward_test.go b/internal/dnsserver/forward/forward_test.go index 5e9cb4e..3fc4d6f 100644 --- a/internal/dnsserver/forward/forward_test.go +++ b/internal/dnsserver/forward/forward_test.go @@ -14,11 +14,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestMain(m *testing.M) { - // TODO(a.garipov): Remove after removing golibs/log from dnsserver. - testutil.DiscardLogOutput(m) -} - // testTimeout is the timeout for tests. const testTimeout = 1 * time.Second diff --git a/internal/dnsserver/forward/upstreamplain_test.go b/internal/dnsserver/forward/upstreamplain_test.go index b3123d6..963839f 100644 --- a/internal/dnsserver/forward/upstreamplain_test.go +++ b/internal/dnsserver/forward/upstreamplain_test.go @@ -8,7 +8,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -93,7 +92,7 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) { Network: forward.NetworkUDP, Address: addr, }) - defer log.OnCloserError(uUDP, log.DEBUG) + defer testutil.CleanupAndRequireSuccess(t, uUDP.Close) res, nw, err := uUDP.Exchange(testutil.ContextWithTimeout(t, testTimeout), req) require.NoError(t, err) @@ -106,7 +105,7 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) { Network: forward.NetworkTCP, Address: addr, }) - defer log.OnCloserError(uTCP, log.DEBUG) + defer testutil.CleanupAndRequireSuccess(t, uTCP.Close) res, nw, err = uTCP.Exchange(testutil.ContextWithTimeout(t, testTimeout), req) require.NoError(t, err) @@ -120,7 +119,7 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) { Network: forward.NetworkAny, Address: addr, }) - defer log.OnCloserError(uAny, log.DEBUG) + defer testutil.CleanupAndRequireSuccess(t, uAny.Close) res, nw, err = uAny.Exchange(testutil.ContextWithTimeout(t, testTimeout), req) require.NoError(t, err) diff --git a/internal/dnsserver/go.mod b/internal/dnsserver/go.mod index c3c934a..b2bde61 100644 --- a/internal/dnsserver/go.mod +++ b/internal/dnsserver/go.mod @@ -1,22 +1,22 @@ module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver -go 1.23.4 +go 1.23.6 require ( - github.com/AdguardTeam/golibs v0.30.4 + github.com/AdguardTeam/golibs v0.32.1 github.com/ameshkov/dnscrypt/v2 v2.3.0 github.com/ameshkov/dnsstamps v1.0.3 github.com/bluele/gcache v0.0.2 github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 - github.com/miekg/dns v1.1.62 - github.com/panjf2000/ants/v2 v2.10.0 + github.com/miekg/dns v1.1.63 + github.com/panjf2000/ants/v2 v2.11.0 github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible github.com/prometheus/client_golang v1.20.5 - github.com/quic-go/quic-go v0.48.2 - github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d - golang.org/x/net v0.32.0 - golang.org/x/sys v0.28.0 + github.com/quic-go/quic-go v0.49.0 + github.com/stretchr/testify v1.10.0 + golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 + golang.org/x/net v0.34.0 + golang.org/x/sys v0.30.0 ) require ( @@ -26,23 +26,24 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/pprof v0.0.0-20241203143554-1e3fdc7de467 // indirect + github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // 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/onsi/ginkgo/v2 v2.22.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/common v0.62.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/crypto v0.30.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/time v0.8.0 // indirect - golang.org/x/tools v0.28.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + golang.org/x/crypto v0.32.0 // 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/time v0.10.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/dnsserver/go.sum b/internal/dnsserver/go.sum index a7fdf7f..4ad62b7 100644 --- a/internal/dnsserver/go.sum +++ b/internal/dnsserver/go.sum @@ -1,4 +1,5 @@ -github.com/AdguardTeam/golibs v0.30.4 h1:zfFX1v4hkOCz6BifkneiBW2PCwSK523kYNr+VwaFrIw= +github.com/AdguardTeam/golibs v0.32.1 h1:Ajf6Q0k+A9zjFbj8HOzNAbHImrV4JtbT0vwy02D6VeI= +github.com/AdguardTeam/golibs v0.32.1/go.mod h1:dXRLSsnJQDxOfQVl0ochy1bfk4NgnJQGQdR1YPJdwcw= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= @@ -16,7 +17,6 @@ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9cop 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -25,21 +25,24 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg= +github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -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/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -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/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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -48,39 +51,43 @@ 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.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.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/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= 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/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -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/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -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/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +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.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= diff --git a/internal/dnsserver/handler.go b/internal/dnsserver/handler.go new file mode 100644 index 0000000..e197ec5 --- /dev/null +++ b/internal/dnsserver/handler.go @@ -0,0 +1,40 @@ +package dnsserver + +import ( + "context" + + "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 processes the request and writes a DNS response to rw. ctx must + // contain [*ServerInfo] and [*RequestInfo]. rw and req must not be nil. + // req must have exactly one question. + ServeDNS(ctx context.Context, rw ResponseWriter, req *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) + +// type check +var _ Handler = HandlerFunc(nil) + +// 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 := (&dns.Msg{}).SetRcode(r, dns.RcodeNotImplemented) + + return w.WriteMsg(ctx, r, res) +} diff --git a/internal/dnsserver/msg.go b/internal/dnsserver/msg.go index 4fa2e68..a47d4f8 100644 --- a/internal/dnsserver/msg.go +++ b/internal/dnsserver/msg.go @@ -18,21 +18,6 @@ func genErrorResponse(req *dns.Msg, code int) (m *dns.Msg) { return m } -// questionData extracts DNS Question data in a safe manner. -func questionData(m *dns.Msg) (hostname, qType string) { - if len(m.Question) > 0 { - q := m.Question[0] - hostname = q.Name - if v, ok := dns.TypeToString[q.Qtype]; ok { - qType = v - } else { - qType = fmt.Sprintf("TYPE%d", q.Qtype) - } - } - - return hostname, qType -} - // packWithPrefix packs a DNS message with a 2-byte prefix with the message // length by appending it into buf and returns it. func packWithPrefix(m *dns.Msg, buf []byte) (packed []byte, err error) { diff --git a/internal/dnsserver/normalize.go b/internal/dnsserver/normalize.go index 6d4870d..4677b0b 100644 --- a/internal/dnsserver/normalize.go +++ b/internal/dnsserver/normalize.go @@ -1,7 +1,7 @@ package dnsserver import ( - "math/rand" + "math/rand/v2" "github.com/miekg/dns" ) @@ -144,7 +144,7 @@ func padAnswer(reqOpt, respOpt *dns.OPT) { // TODO(ameshkov): Consider changing to crypto/rand, need to hold a vote. // #nosec G404 -- We don't need a real random for a simple padding - // randomization, pseudo-random is enough. + // randomization, pseudorandom is enough. // // Note, that we don't check for whether reqOpt.UDPSize() here is smaller // than resp.Len() + padLen so in theory the padded response may be larger @@ -152,7 +152,7 @@ func padAnswer(reqOpt, respOpt *dns.OPT) { // avoiding calling resp.Len(). // // TODO(ameshkov): Return this check if we optimize resp.Len(). - padLen := rand.Intn(responsePaddingMaxSize-1) + 1 + padLen := rand.IntN(responsePaddingMaxSize-1) + 1 paddingOpt.Padding = respPadBuf[:padLen:padLen] } diff --git a/internal/dnsserver/prometheus/cache.go b/internal/dnsserver/prometheus/cache.go index 1d102fc..13ee813 100644 --- a/internal/dnsserver/prometheus/cache.go +++ b/internal/dnsserver/prometheus/cache.go @@ -2,11 +2,13 @@ package prometheus import ( "context" + "fmt" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache" + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) // cacheTypeDefault is a "type" label value for the default LRU cache. In the @@ -24,31 +26,63 @@ type CacheMetricsListener struct { // NewCacheMetricsListener returns a new properly initialized // *CacheMetricsListener. As long as this function registers prometheus // counters it must be called only once. -// -// TODO(a.garipov): Do not use promauto. -func NewCacheMetricsListener(namespace string) *CacheMetricsListener { - return &CacheMetricsListener{ - cacheSize: promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "size", +func NewCacheMetricsListener( + namespace string, + reg prometheus.Registerer, +) (l *CacheMetricsListener, err error) { + const ( + cacheSize = "size" + hitsTotal = "hits_total" + missesTotal = "misses_total" + ) + + l = &CacheMetricsListener{ + cacheSize: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: cacheSize, Namespace: namespace, Subsystem: subsystemCache, Help: "The total number items in the cache.", }, []string{"type"}), - hitsTotal: promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "hits_total", + hitsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: hitsTotal, Namespace: namespace, Subsystem: subsystemCache, Help: "The total number of cache hits.", }, []string{"type"}), - missesTotal: promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "misses_total", + missesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: missesTotal, Namespace: namespace, Subsystem: subsystemCache, Help: "The total number of cache misses.", }, []string{"type"}), } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: cacheSize, + Value: l.cacheSize, + }, { + Key: hitsTotal, + Value: l.hitsTotal, + }, { + Key: missesTotal, + Value: l.missesTotal, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return l, nil } // type check diff --git a/internal/dnsserver/prometheus/cache_test.go b/internal/dnsserver/prometheus/cache_test.go index b0aa82b..1057fcb 100644 --- a/internal/dnsserver/prometheus/cache_test.go +++ b/internal/dnsserver/prometheus/cache_test.go @@ -8,8 +8,9 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" + dnssrvprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -17,8 +18,13 @@ import ( // normal unit test, we create a cache middleware, emulate a query and then // check if prom metrics were incremented. func TestCacheMetricsListener_integration_cache(t *testing.T) { + reg := prometheus.NewRegistry() + mtrcListener, err := dnssrvprom.NewCacheMetricsListener(testNamespace, reg) + require.NoError(t, err) + cacheMiddleware := cache.NewMiddleware(&cache.MiddlewareConfig{ - MetricsListener: prometheus.NewCacheMetricsListener(testNamespace), + Logger: testLogger, + MetricsListener: mtrcListener, Count: 100, }) @@ -39,7 +45,7 @@ func TestCacheMetricsListener_integration_cache(t *testing.T) { req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) - err := handlerWithMiddleware.ServeDNS(ctx, nrw, req) + err = handlerWithMiddleware.ServeDNS(ctx, nrw, req) require.NoError(t, err) dnsservertest.RequireResponse(t, req, nrw.Msg(), 1, dns.RcodeSuccess, false) } @@ -47,6 +53,7 @@ func TestCacheMetricsListener_integration_cache(t *testing.T) { // Now make sure that prometheus metrics were incremented properly. requireMetrics( t, + reg, "dns_cache_hits_total", "dns_cache_misses_total", "dns_cache_size", diff --git a/internal/dnsserver/prometheus/forward.go b/internal/dnsserver/prometheus/forward.go index 59f77ab..01b3cbf 100644 --- a/internal/dnsserver/prometheus/forward.go +++ b/internal/dnsserver/prometheus/forward.go @@ -2,15 +2,16 @@ package prometheus import ( "context" + "fmt" "net" "sync" "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" + "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) // ForwardMetricsListener implements the [forward.MetricsListener] interface @@ -33,40 +34,50 @@ type ForwardMetricsListener struct { // NewForwardMetricsListener returns a properly initialized // *ForwardMetricsListener expecting to track upsNumHint upstreams. As long as // this function registers prometheus counters it must be called only once. -// -// TODO(a.garipov): Do not use promauto. -func NewForwardMetricsListener(namespace string, upsNumHint int) (f *ForwardMetricsListener) { - return &ForwardMetricsListener{ - requestsTotal: promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "request_total", +func NewForwardMetricsListener( + namespace string, + reg prometheus.Registerer, + upsNumHint int, +) (f *ForwardMetricsListener, err error) { + const ( + requestsTotal = "request_total" + responseRCode = "response_rcode_total" + requestDuration = "request_duration_seconds" + errorsTotal = "error_total" + upstreamStatus = "upstream_status" + ) + + f = &ForwardMetricsListener{ + requestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: requestsTotal, Namespace: namespace, Subsystem: subsystemForward, Help: "The number of processed DNS requests.", }, []string{"to", "network"}), - responseRCode: promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "response_rcode_total", + responseRCode: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: responseRCode, Namespace: namespace, Subsystem: subsystemForward, Help: "The counter for DNS response codes.", }, []string{"to", "rcode"}), - requestDuration: promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "request_duration_seconds", + requestDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: requestDuration, Namespace: namespace, Subsystem: subsystemForward, Help: "Time elapsed on processing a DNS query.", }, []string{"to"}), - errorsTotal: promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "error_total", + errorsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: errorsTotal, Namespace: namespace, Subsystem: subsystemForward, Help: "The number of errors occurred when processing a DNS query.", }, []string{"to", "type"}), - upstreamStatus: promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "upstream_status", + upstreamStatus: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: upstreamStatus, Namespace: namespace, Subsystem: subsystemForward, Help: "Status of the main upstream. 1 is okay, 0 the upstream is backed off", @@ -76,6 +87,37 @@ func NewForwardMetricsListener(namespace string, upsNumHint int) (f *ForwardMetr statusGauges: make(map[forward.Upstream]prometheus.Gauge, upsNumHint), } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: requestsTotal, + Value: f.requestsTotal, + }, { + Key: responseRCode, + Value: f.responseRCode, + }, { + Key: requestDuration, + Value: f.requestDuration, + }, { + Key: errorsTotal, + Value: f.errorsTotal, + }, { + Key: upstreamStatus, + Value: f.upstreamStatus, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return f, nil } // type check diff --git a/internal/dnsserver/prometheus/forward_test.go b/internal/dnsserver/prometheus/forward_test.go index 5913fa6..58ad5cd 100644 --- a/internal/dnsserver/prometheus/forward_test.go +++ b/internal/dnsserver/prometheus/forward_test.go @@ -8,9 +8,9 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" - "github.com/AdguardTeam/golibs/logutil/slogutil" + dnssrvprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -19,15 +19,18 @@ import ( // check if prom metrics were incremented. func TestForwardMetricsListener_integration_request(t *testing.T) { srv, addr := dnsservertest.RunDNSServer(t, dnsservertest.NewDefaultHandler()) + reg := prometheus.NewRegistry() + mtrcListener, err := dnssrvprom.NewForwardMetricsListener(testNamespace, reg, 0) + require.NoError(t, err) // Initialize a new forward.Handler and set the metrics listener. handler := forward.NewHandler(&forward.HandlerConfig{ - Logger: slogutil.NewDiscardLogger(), + Logger: testLogger, UpstreamsAddresses: []*forward.UpstreamPlainConfig{{ Network: forward.NetworkAny, Address: netip.MustParseAddrPort(addr), }}, - MetricsListener: prometheus.NewForwardMetricsListener(testNamespace, 0), + MetricsListener: mtrcListener, }) // Prepare a test DNS message and call the handler's ServeDNS function. @@ -36,12 +39,13 @@ func TestForwardMetricsListener_integration_request(t *testing.T) { req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) rw := dnsserver.NewNonWriterResponseWriter(srv.LocalUDPAddr(), srv.LocalUDPAddr()) - err := handler.ServeDNS(context.Background(), rw, req) + err = handler.ServeDNS(context.Background(), rw, req) require.NoError(t, err) // Now make sure that prometheus metrics were incremented properly. requireMetrics( t, + reg, "dns_forward_request_total", "dns_forward_request_duration_seconds", "dns_forward_response_rcode_total", diff --git a/internal/dnsserver/prometheus/prometheus_test.go b/internal/dnsserver/prometheus/prometheus_test.go index 3b559aa..431f8dc 100644 --- a/internal/dnsserver/prometheus/prometheus_test.go +++ b/internal/dnsserver/prometheus/prometheus_test.go @@ -5,14 +5,13 @@ import ( "testing" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" - "github.com/AdguardTeam/golibs/testutil" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() // testNamespace is a test namespace for metrics. const testNamespace = "dns" @@ -33,12 +32,12 @@ var testUDPAddr = &net.UDPAddr{ Port: 53, } -// requireMetrics accepts a list of metrics names and checks that -// they exist in the prom registry. -func requireMetrics(t testing.TB, args ...string) { +// requireMetrics accepts a list of metrics names and checks that they exist in +// reg. +func requireMetrics(t testing.TB, reg *prometheus.Registry, args ...string) { t.Helper() - mf, err := prometheus.DefaultGatherer.Gather() + mf, err := reg.Gather() require.NoError(t, err) require.NotNil(t, mf) diff --git a/internal/dnsserver/prometheus/ratelimit.go b/internal/dnsserver/prometheus/ratelimit.go index 5232021..8ff9829 100644 --- a/internal/dnsserver/prometheus/ratelimit.go +++ b/internal/dnsserver/prometheus/ratelimit.go @@ -2,13 +2,15 @@ package prometheus import ( "context" + "fmt" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/syncutil" "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) // RateLimitMetricsListener implements the [ratelimit.Metrics] interface @@ -22,24 +24,52 @@ type RateLimitMetricsListener struct { // *RateLimitMetricsListener. As long as this function registers prometheus // counters it must be called only once. // -// TODO(a.garipov): Do not use promauto. -func NewRateLimitMetricsListener(namespace string) (l *RateLimitMetricsListener) { +// TODO(s.chzhen): Use it. +func NewRateLimitMetricsListener( + namespace string, + reg prometheus.Registerer, +) (l *RateLimitMetricsListener, err error) { + const ( + droppedTotalMtrcName = "dropped_total" + allowlistedTotalMtrcName = "allowlisted_total" + ) + var ( - droppedTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "dropped_total", + droppedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: droppedTotalMtrcName, Namespace: namespace, Subsystem: subsystemRateLimit, Help: "The total number of rate-limited DNS queries.", }, []string{"name", "proto", "network", "addr", "type", "family"}) - allowlistedTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "allowlisted_total", + allowlistedTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: allowlistedTotalMtrcName, Namespace: namespace, Subsystem: subsystemRateLimit, Help: "The total number of allowlisted DNS queries.", }, []string{"name", "proto", "network", "addr", "type", "family"}) ) + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: droppedTotalMtrcName, + Value: droppedTotal, + }, { + Key: allowlistedTotalMtrcName, + Value: allowlistedTotal, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + return &RateLimitMetricsListener{ dropCounters: syncutil.NewOnceConstructor( func(k reqLabelMetricKey) (c prometheus.Counter) { @@ -51,7 +81,7 @@ func NewRateLimitMetricsListener(namespace string) (l *RateLimitMetricsListener) return k.withLabelValues(allowlistedTotal) }, ), - } + }, nil } // type check diff --git a/internal/dnsserver/prometheus/ratelimit_test.go b/internal/dnsserver/prometheus/ratelimit_test.go index 97a126c..761a5d5 100644 --- a/internal/dnsserver/prometheus/ratelimit_test.go +++ b/internal/dnsserver/prometheus/ratelimit_test.go @@ -8,10 +8,11 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" + dnssvcprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" "github.com/c2h5oh/datasize" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -36,8 +37,13 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) { IPv6Interval: ivl, RefuseANY: true, }) + + reg := prometheus.NewRegistry() + mtrcListener, err := dnssvcprom.NewRateLimitMetricsListener(testNamespace, reg) + require.NoError(t, err) + rlMw, err := ratelimit.NewMiddleware(&ratelimit.MiddlewareConfig{ - Metrics: prometheus.NewRateLimitMetricsListener(testNamespace), + Metrics: mtrcListener, RateLimit: rl, }) require.NoError(t, err) @@ -68,11 +74,13 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) { } // Now make sure that prometheus metrics were incremented properly. - requireMetrics(t, "dns_ratelimit_dropped_total") + requireMetrics(t, reg, "dns_ratelimit_dropped_total") } func BenchmarkRateLimitMetricsListener(b *testing.B) { - l := prometheus.NewRateLimitMetricsListener(testNamespace) + reg := prometheus.NewRegistry() + l, err := dnssvcprom.NewRateLimitMetricsListener(testNamespace, reg) + require.NoError(b, err) ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) diff --git a/internal/dnsserver/prometheus/server.go b/internal/dnsserver/prometheus/server.go index 48aaab7..e37d005 100644 --- a/internal/dnsserver/prometheus/server.go +++ b/internal/dnsserver/prometheus/server.go @@ -2,12 +2,14 @@ package prometheus import ( "context" + "fmt" "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/syncutil" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) // ServerMetricsListener implements the [dnsserver.MetricsListener] interface @@ -55,26 +57,39 @@ func (i srvInfoRCode) withLabelValues(vec *prometheus.CounterVec) (c prometheus. // NewServerMetricsListener returns a new properly initialized // *ServerMetricsListener. As long as this function registers prometheus // counters it must be called only once. -// -// TODO(a.garipov): Do not use promauto. -func NewServerMetricsListener(namespace string) (l *ServerMetricsListener) { +func NewServerMetricsListener( + namespace string, + reg prometheus.Registerer, +) (l *ServerMetricsListener, err error) { + const ( + reqTotalMtrcName = "request_total" + reqDurationMtrcName = "request_duration_seconds" + reqSizeMtrcName = "request_size_bytes" + respSizeMtrcName = "response_size_bytes" + respRCodeMtrcName = "response_rcode_total" + errTotalMtrcName = "error_total" + panicTotalMtrcName = "panic_total" + invalidMsgTotalMtrcName = "invalid_msg_total" + quicAddrLookupsMtrcName = "quic_addr_validation_lookups" + ) + var ( - requestTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "request_total", + requestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: reqTotalMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "The number of processed DNS requests.", }, []string{"name", "proto", "network", "addr", "type", "family"}) - requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "request_duration_seconds", + requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: reqDurationMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "Time elapsed on processing a DNS query.", }, []string{"name", "proto", "addr"}) - requestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "request_size_bytes", + requestSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: reqSizeMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "Time elapsed on processing a DNS query.", @@ -83,8 +98,8 @@ func NewServerMetricsListener(namespace string) (l *ServerMetricsListener) { }, }, []string{"name", "proto", "addr"}) - responseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "response_size_bytes", + responseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: respSizeMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "Time elapsed on processing a DNS query.", @@ -93,42 +108,83 @@ func NewServerMetricsListener(namespace string) (l *ServerMetricsListener) { }, }, []string{"name", "proto", "addr"}) - responseRCode = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "response_rcode_total", + responseRCode = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: respRCodeMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "The counter for DNS response codes.", }, []string{"name", "proto", "addr", "rcode"}) - errorTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "error_total", + errorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: errTotalMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "The number of errors occurred in the DNS server.", }, []string{"name", "proto", "addr"}) - panicTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "panic_total", + panicTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: panicTotalMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "The number of panics occurred in the DNS server.", }, []string{"name", "proto", "addr"}) - invalidMsgTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "invalid_msg_total", + invalidMsgTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: invalidMsgTotalMtrcName, Namespace: namespace, Subsystem: subsystemServer, Help: "The number of invalid DNS messages processed by the DNS server.", }, []string{"name", "proto", "addr"}) + + quicAddrValidationCacheLookups = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: quicAddrLookupsMtrcName, + Namespace: namespace, + Subsystem: subsystemServer, + Help: "The number of QUIC address validation lookups." + + "hit=1 means that a cached item was found.", + }, []string{"hit"}) ) - quicAddrValidationCacheLookups := promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "quic_addr_validation_lookups", - Namespace: namespace, - Subsystem: subsystemServer, - Help: "The number of QUIC address validation lookups." + - "hit=1 means that a cached item was found.", - }, []string{"hit"}) + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: reqTotalMtrcName, + Value: requestTotal, + }, { + Key: reqDurationMtrcName, + Value: requestDuration, + }, { + Key: reqSizeMtrcName, + Value: requestSize, + }, { + Key: respSizeMtrcName, + Value: responseSize, + }, { + Key: respRCodeMtrcName, + Value: responseRCode, + }, { + Key: errTotalMtrcName, + Value: errorTotal, + }, { + Key: panicTotalMtrcName, + Value: panicTotal, + }, { + Key: invalidMsgTotalMtrcName, + Value: invalidMsgTotal, + }, { + Key: quicAddrLookupsMtrcName, + Value: quicAddrValidationCacheLookups, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } return &ServerMetricsListener{ quicAddrValidationCacheLookupsHits: quicAddrValidationCacheLookups.WithLabelValues("1"), @@ -177,7 +233,7 @@ func NewServerMetricsListener(namespace string) (l *ServerMetricsListener) { return withSrvInfoLabelValues(responseSize, k) }, ), - } + }, nil } // type check diff --git a/internal/dnsserver/prometheus/server_test.go b/internal/dnsserver/prometheus/server_test.go index f52a3b1..9e81db9 100644 --- a/internal/dnsserver/prometheus/server_test.go +++ b/internal/dnsserver/prometheus/server_test.go @@ -7,9 +7,10 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" + dnssvcprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -17,21 +18,26 @@ import ( // normal unit test, we run a test DNS server, send a DNS query, and then // check that metrics were properly counted. func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { + reg := prometheus.NewRegistry() + mtrcListener, err := dnssvcprom.NewServerMetricsListener(testNamespace, reg) + require.NoError(t, err) + // Initialize the test server and supply the metrics listener. The // acknowledgment-based protocol TCP is used here to make the test // less flaky. - conf := dnsserver.ConfigDNS{ - ConfigBase: dnsserver.ConfigBase{ - Name: "test", - Addr: "127.0.0.1:0", - Handler: dnsservertest.NewDefaultHandler(), - Metrics: prometheus.NewServerMetricsListener(testNamespace), + conf := &dnsserver.ConfigDNS{ + Base: &dnsserver.ConfigBase{ + BaseLogger: testLogger, + Name: "test", + Addr: "127.0.0.1:0", + Handler: dnsservertest.NewDefaultHandler(), + Metrics: mtrcListener, }, } srv := dnsserver.NewServerDNS(conf) // Start the server. - err := srv.Start(context.Background()) + err = srv.Start(context.Background()) require.NoError(t, err) // Make sure the server shuts down in the end. @@ -58,6 +64,7 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { // Now make sure that prometheus metrics were incremented properly. requireMetrics( t, + reg, "dns_server_request_total", "dns_server_request_duration_seconds", "dns_server_request_size_bytes", @@ -67,7 +74,9 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { } func BenchmarkServerMetricsListener(b *testing.B) { - l := prometheus.NewServerMetricsListener(testNamespace) + reg := prometheus.NewRegistry() + l, err := dnssvcprom.NewServerMetricsListener(testNamespace, reg) + require.NoError(b, err) ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{ diff --git a/internal/dnsserver/querylog/querylog.go b/internal/dnsserver/querylog/querylog.go index a5af854..cea18e4 100644 --- a/internal/dnsserver/querylog/querylog.go +++ b/internal/dnsserver/querylog/querylog.go @@ -6,23 +6,27 @@ import ( "context" "fmt" "io" + "log/slog" "strings" "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/miekg/dns" ) // LogMiddleware is a simple middleware that prints DNS queries to the log. // We keep it here to show an example of a middleware. type LogMiddleware struct { + logger *slog.Logger output io.Writer } -// NewLogMiddleware creates a new LogMiddleware with the specified output. -func NewLogMiddleware(output io.Writer) *LogMiddleware { +// NewLogMiddleware creates a new LogMiddleware with the specified output. All +// arguments must not be nil. +func NewLogMiddleware(output io.Writer, logger *slog.Logger) *LogMiddleware { return &LogMiddleware{ + logger: logger, output: output, } } @@ -30,7 +34,7 @@ func NewLogMiddleware(output io.Writer) *LogMiddleware { // type check var _ dnsserver.Middleware = (*LogMiddleware)(nil) -// Wrap implements the dnsserver.Middleware interface for *LogMiddleware. +// Wrap implements the [dnsserver.Middleware] interface for *LogMiddleware. func (l *LogMiddleware) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) { f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error { // Call the next handler and record the response that has been written @@ -66,14 +70,7 @@ func (l *LogMiddleware) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) { if !ok { qTypeStr = fmt.Sprintf("TYPE%d", qType) } - sb.WriteString( - fmt.Sprintf("%d %s %s %d ", - req.Id, - qTypeStr, - hostname, - req.Len(), - ), - ) + sb.WriteString(fmt.Sprintf("%d %s %s %d ", req.Id, qTypeStr, hostname, req.Len())) // Response data: {rcode} {rsize} rcode := 0 @@ -93,7 +90,7 @@ func (l *LogMiddleware) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) { // Suppress errors, it's not that important for a query log _, outErr := l.output.Write([]byte(sb.String())) if outErr != nil { - log.Debug("failed to write to the query log: %v", outErr) + l.logger.DebugContext(ctx, "writing the query log", slogutil.KeyError, outErr) } return err diff --git a/internal/dnsserver/querylog/querylog_test.go b/internal/dnsserver/querylog/querylog_test.go index 82ea9d2..e53dc22 100644 --- a/internal/dnsserver/querylog/querylog_test.go +++ b/internal/dnsserver/querylog/querylog_test.go @@ -10,6 +10,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/querylog" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/miekg/dns" "github.com/stretchr/testify/require" ) @@ -31,7 +32,7 @@ func TestLogMiddleware_Wrap(t *testing.T) { w := new(bytes.Buffer) handlerWithMiddlewares := dnsserver.WithMiddlewares( handler, - querylog.NewLogMiddleware(w), + querylog.NewLogMiddleware(w, slogutil.NewDiscardLogger()), ) // Create a test DNS request diff --git a/internal/dnsserver/ratelimit/ratelimit_test.go b/internal/dnsserver/ratelimit/ratelimit_test.go index adf5f17..a81fca5 100644 --- a/internal/dnsserver/ratelimit/ratelimit_test.go +++ b/internal/dnsserver/ratelimit/ratelimit_test.go @@ -10,17 +10,12 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" - "github.com/AdguardTeam/golibs/testutil" "github.com/c2h5oh/datasize" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} - func TestRatelimitMiddleware(t *testing.T) { const ( rps = 10 diff --git a/internal/dnsserver/recorder.go b/internal/dnsserver/recorder.go deleted file mode 100644 index b90876e..0000000 --- a/internal/dnsserver/recorder.go +++ /dev/null @@ -1,49 +0,0 @@ -package dnsserver - -import ( - "context" - "net" - - "github.com/AdguardTeam/golibs/errors" - "github.com/miekg/dns" -) - -// RecorderResponseWriter implements the ResponseWriter interface and simply -// calls underlying ResponseWriter functions except for the WriteMsg method, -// which records a clone of the response message that has been written. -type RecorderResponseWriter struct { - // rw is the underlying ResponseWriter. - rw ResponseWriter - - // Resp is the response that has been written (if any). - Resp *dns.Msg -} - -// NewRecorderResponseWriter creates a new instance of RecorderResponseWriter. -func NewRecorderResponseWriter(rw ResponseWriter) (recw *RecorderResponseWriter) { - return &RecorderResponseWriter{ - rw: rw, - } -} - -// type check -var _ ResponseWriter = (*RecorderResponseWriter)(nil) - -// LocalAddr implements the ResponseWriter interface for *RecorderResponseWriter. -func (r *RecorderResponseWriter) LocalAddr() (addr net.Addr) { - return r.rw.LocalAddr() -} - -// RemoteAddr implements the ResponseWriter interface for *RecorderResponseWriter. -func (r *RecorderResponseWriter) RemoteAddr() (addr net.Addr) { - return r.rw.RemoteAddr() -} - -// WriteMsg implements the ResponseWriter interface for *RecorderResponseWriter. -func (r *RecorderResponseWriter) WriteMsg(ctx context.Context, req, resp *dns.Msg) (err error) { - defer func() { err = errors.Annotate(err, "recorder: %w") }() - - r.Resp = resp - - return r.rw.WriteMsg(ctx, req, resp) -} diff --git a/internal/dnsserver/responsewriter.go b/internal/dnsserver/responsewriter.go new file mode 100644 index 0000000..d48c6d7 --- /dev/null +++ b/internal/dnsserver/responsewriter.go @@ -0,0 +1,70 @@ +package dnsserver + +import ( + "context" + "net" + + "github.com/AdguardTeam/golibs/errors" + "github.com/miekg/dns" +) + +// 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. req and resp must not be nil. + // + // TODO(a.garipov): Store bytes written to the socket. + WriteMsg(ctx context.Context, req, resp *dns.Msg) (err error) +} + +// RecorderResponseWriter implements the [ResponseWriter] interface and simply +// calls underlying writer's methods except for WriteMsg, which records a clone +// of the response message that has been written. +type RecorderResponseWriter struct { + // rw is the underlying ResponseWriter. + rw ResponseWriter + + // Resp is the response that has been written (if any). + Resp *dns.Msg +} + +// NewRecorderResponseWriter creates a new instance of RecorderResponseWriter. +func NewRecorderResponseWriter(rw ResponseWriter) (recw *RecorderResponseWriter) { + return &RecorderResponseWriter{ + rw: rw, + } +} + +// type check +var _ ResponseWriter = (*RecorderResponseWriter)(nil) + +// LocalAddr implements the [ResponseWriter] interface for +// *RecorderResponseWriter. +func (r *RecorderResponseWriter) LocalAddr() (addr net.Addr) { + return r.rw.LocalAddr() +} + +// RemoteAddr implements the [ResponseWriter] interface for +// *RecorderResponseWriter. +func (r *RecorderResponseWriter) RemoteAddr() (addr net.Addr) { + return r.rw.RemoteAddr() +} + +// WriteMsg implements the [ResponseWriter] interface for +// *RecorderResponseWriter. +func (r *RecorderResponseWriter) WriteMsg(ctx context.Context, req, resp *dns.Msg) (err error) { + defer func() { err = errors.Annotate(err, "recorder: %w") }() + + r.Resp = resp + + return r.rw.WriteMsg(ctx, req, resp) +} diff --git a/internal/dnsserver/serverbase.go b/internal/dnsserver/serverbase.go index 5f54f1e..faece17 100644 --- a/internal/dnsserver/serverbase.go +++ b/internal/dnsserver/serverbase.go @@ -1,22 +1,41 @@ package dnsserver import ( + "cmp" "context" + "fmt" + "log/slog" "net" "os" - "runtime/debug" "sync" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/contextutil" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/osutil" + "github.com/AdguardTeam/golibs/syncutil" "github.com/miekg/dns" ) -// ConfigBase contains the necessary minimum that every Server needs to -// be initialized. +// ConfigBase contains the necessary minimum that every [Server] needs to be +// initialized. +// +// TODO(a.garipov): Consider splitting and adding appropriate fields to the +// configs of the separate server types. type ConfigBase struct { - // Handler is a handler that processes incoming DNS messages. If not set, - // the default handler, which returns error response to any query, is used. + // BaseLogger is used to create loggers for servers and requests. It should + // contain the name of the server. If BaseLogger is nil, [slog.Default] is + // used. + // + // Loggers for requests derived from this logger include the following + // fields: + // - "qname": the target of the DNS query. + // - "qtype": the type of the DNS query. + // - "req_id": the 16-bit ID of the message as set by the client. + BaseLogger *slog.Logger + + // Handler processes incoming DNS messages. If not set, the default + // handler, which returns error responses to all queries, is used. Handler Handler // Metrics is the object we use for collecting performance metrics. If not @@ -24,12 +43,12 @@ type ConfigBase struct { Metrics MetricsListener // Disposer is used to help module users reuse parts of DNS responses. If - // not set, EmptyDisposer is used. + // not set, [EmptyDisposer] is used. Disposer Disposer - // RequestContext is a ContextConstructor that returns contexts for - // requests. If not set, the server uses [DefaultContextConstructor]. - RequestContext ContextConstructor + // RequestContext is a context constructor that returns contexts for + // requests. If not set, the server uses [contextutil.EmptyConstructor]. + RequestContext contextutil.Constructor // ListenConfig, when set, is used to set options of connections used by the // DNS server. If nil, an appropriate default ListenConfig is used. @@ -42,20 +61,27 @@ type ConfigBase struct { Network Network // Name is used for logging, and it may be used for perf counters reporting. + // It should not be empty. Name string // Addr is the address the server listens to. See [net.Dial] for the - // documentation on the address format. + // documentation on the address format. It must not be empty. Addr string } -// ServerBase implements base methods that every Server implementation uses. +// ServerBase implements base methods that every [Server] implementation uses. type ServerBase struct { + // baseLogger is the base logger of this server. + baseLogger *slog.Logger + + // attrPool is the pool of logging attributes for reuse. + attrPool *syncutil.Pool[[]slog.Attr] + // handler is a handler that processes incoming DNS messages. handler Handler // reqCtx is a function that should return the base context. - reqCtx ContextConstructor + reqCtx contextutil.Constructor // metrics is the object we use for collecting performance metrics. metrics MetricsListener @@ -66,6 +92,9 @@ type ServerBase struct { // listenConfig is used to set tcpListener and udpListener. listenConfig netext.ListenConfig + // mu protects started, tcpListener, and udpListener. + mu *sync.RWMutex + // tcpListener is used to accept new TCP connections. It is nil for servers // that don't use TCP. tcpListener net.Listener @@ -74,14 +103,13 @@ type ServerBase struct { // that don't use UDP. udpListener net.PacketConn - // mu protects started, tcpListener, and udpListener. - mu *sync.RWMutex - - // wg tracks active workers (listeners or query processing). Shutdown - // won't finish until there's at least one active worker. + // wg tracks active workers, both listeners and workers processing queries. + // Shutdown won't finish until there's at least one active worker. wg *sync.WaitGroup // name is used for logging and it may be used for perf counters reporting. + // + // TODO(a.garipov): Remove eventually. name string // addr is the address the server listens to. @@ -89,84 +117,81 @@ type ServerBase struct { // network is the network to listen to. It only makes sense for the // following protocols: [ProtoDNS], [ProtoDNSCrypt], [ProtoDoH]. + // + // TODO(a.garipov): Move into separate servers. network Network - // proto is the server protocol. + // proto is the protocol of the server. proto Protocol + // started shows if the server has already been started. started bool } // type check var _ Server = (*ServerBase)(nil) +// logAttrNum is the number of attributes used by the request loggers +const logAttrNum = 4 + // newServerBase creates a new instance of ServerBase and initializes -// some of its internal properties. -func newServerBase(proto Protocol, conf ConfigBase) (s *ServerBase) { - s = &ServerBase{ - handler: conf.Handler, - reqCtx: conf.RequestContext, - metrics: conf.Metrics, - disposer: conf.Disposer, - listenConfig: conf.ListenConfig, +// some of its internal properties. proto must be valid. c must not be nil. +// +// TODO(a.garipov): Consider either relaxing the requirements, by turning +// “must” into “should” and returning errors, or validating the configuration +// contracts explicitly. +func newServerBase(proto Protocol, c *ConfigBase) (s *ServerBase) { + return &ServerBase{ + baseLogger: cmp.Or(c.BaseLogger, slog.Default()), + attrPool: syncutil.NewSlicePool[slog.Attr](logAttrNum), + handler: cmp.Or[Handler](c.Handler, notImplementedHandlerFunc), + reqCtx: cmp.Or[contextutil.Constructor]( + c.RequestContext, + contextutil.EmptyConstructor{}, + ), + metrics: cmp.Or[MetricsListener](c.Metrics, EmptyMetricsListener{}), + disposer: cmp.Or[Disposer](c.Disposer, EmptyDisposer{}), + listenConfig: c.ListenConfig, mu: &sync.RWMutex{}, wg: &sync.WaitGroup{}, - name: conf.Name, - addr: conf.Addr, - network: conf.Network, + name: c.Name, + addr: c.Addr, + network: c.Network, proto: proto, } - - if s.reqCtx == nil { - s.reqCtx = DefaultContextConstructor{} - } - - if s.metrics == nil { - s.metrics = &EmptyMetricsListener{} - } - - if s.disposer == nil { - s.disposer = EmptyDisposer{} - } - - if s.handler == nil { - s.handler = notImplementedHandlerFunc - } - - return s } -// Name implements the [dnsserver.Server] interface for *ServerBase. +// Name implements the [Server] interface for *ServerBase. func (s *ServerBase) Name() (name string) { return s.name } -// Proto implements the [dnsserver.Server] interface for *ServerBase. +// Proto implements the [Server] interface for *ServerBase. func (s *ServerBase) Proto() (proto Protocol) { return s.proto } -// Network implements the [dnsserver.Server] interface for *ServerBase. +// Network implements the [Server] interface for *ServerBase. func (s *ServerBase) Network() (network Network) { return s.network } -// Addr implements the [dnsserver.Server] interface for *ServerBase. +// Addr implements the [Server] interface for *ServerBase. func (s *ServerBase) Addr() (addr string) { return s.addr } -// Start implements the [dnsserver.Server] interface for *ServerBase. +// Start implements the [Server] interface for *ServerBase. func (s *ServerBase) Start(_ context.Context) (err error) { panic("*ServerBase must not be used directly") } -// Shutdown implements the [dnsserver.Server] interface for *ServerBase. +// Shutdown implements the [Server] interface for *ServerBase. func (s *ServerBase) Shutdown(_ context.Context) (err error) { panic("*ServerBase must not be used directly") } -// LocalTCPAddr implements the [dnsserver.Server] interface for *ServerBase. +// LocalTCPAddr implements the [Server] interface for *ServerBase. func (s *ServerBase) LocalTCPAddr() (addr net.Addr) { if s.tcpListener != nil { return s.tcpListener.Addr() @@ -175,7 +200,7 @@ func (s *ServerBase) LocalTCPAddr() (addr net.Addr) { return nil } -// LocalUDPAddr implements the [dnsserver.Server] interface for *ServerBase. +// LocalUDPAddr implements the [Server] interface for *ServerBase. func (s *ServerBase) LocalUDPAddr() (addr net.Addr) { if s.udpListener != nil { return s.udpListener.LocalAddr() @@ -185,8 +210,10 @@ func (s *ServerBase) LocalUDPAddr() (addr net.Addr) { } // requestContext returns a context for one request and adds server information. -func (s *ServerBase) requestContext() (ctx context.Context, cancel context.CancelFunc) { - ctx, cancel = s.reqCtx.New() +func (s *ServerBase) requestContext( + parent context.Context, +) (ctx context.Context, cancel context.CancelFunc) { + ctx, cancel = s.reqCtx.New(parent) ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, Addr: s.addr, @@ -212,17 +239,26 @@ func (s *ServerBase) serveDNS(ctx context.Context, buf []byte, rw ResponseWriter } // serveDNSMsg processes the incoming DNS query and writes the response to the -// specified ResponseWriter. written is false if no response was written. +// specified ResponseWriter. req and rw must not be nil. written is false if +// no response was written. func (s *ServerBase) serveDNSMsg( ctx context.Context, req *dns.Msg, rw ResponseWriter, ) (written bool) { - hostname, qType := questionData(req) - log.Debug("[%d] processing \"%s %s\"", req.Id, qType, hostname) + attrsPtr := s.newAttrsSlicePtr(req, rw.RemoteAddr().String()) + defer s.attrPool.Put(attrsPtr) + + logHdlr := s.baseLogger.Handler().WithAttrs(*attrsPtr) + logger := slog.New(logHdlr) + + logger.DebugContext(ctx, "started processing") + defer logger.DebugContext(ctx, "finished processing") + + ctx = slogutil.ContextWithLogger(ctx, logger) recW := NewRecorderResponseWriter(rw) - s.serveDNSMsgInternal(ctx, req, recW) + s.serveDNSMsgInternal(ctx, logger, req, recW) resp := recW.Resp written = resp != nil @@ -241,13 +277,46 @@ func (s *ServerBase) serveDNSMsg( ResponseSize: respLen, }, rw) - log.Debug("[%d]: finished processing \"%s %s\"", req.Id, qType, hostname) - s.dispose(rw, resp) return written } +// newAttrsSlicePtr returns a pointer to a slice with the attributes from the +// DNS request set. Callers should defer returning the slice back to the pool. +// req must not be nil. +func (s *ServerBase) newAttrsSlicePtr(req *dns.Msg, raddr string) (attrsPtr *[]slog.Attr) { + attrsPtr = s.attrPool.Get() + + attrs := *attrsPtr + + // Optimize bounds checking. + _ = attrs[logAttrNum-1] + + qName, qType := questionData(req) + attrs[0] = slog.String("qname", qName) + attrs[1] = slog.String("qtype", qType) + attrs[2] = slog.String("raddr", raddr) + attrs[3] = slog.Uint64("req_id", uint64(req.Id)) + + return attrsPtr +} + +// questionData extracts DNS Question data in a safe manner. m must not be nil. +func questionData(m *dns.Msg) (hostname, qType string) { + if len(m.Question) > 0 { + q := m.Question[0] + hostname = q.Name + if v, ok := dns.TypeToString[q.Qtype]; ok { + qType = v + } else { + qType = fmt.Sprintf("TYPE%d", q.Qtype) + } + } + + return hostname, qType +} + // dispose is a helper for disposing a DNS response right after writing it to a // connection. Disposal of a response is only safe assuming that there is no // further processing up the stack. Currently, this is only true for plain DNS @@ -266,25 +335,30 @@ func (s *ServerBase) dispose(rw ResponseWriter, resp *dns.Msg) { } // serveDNSMsgInternal serves the DNS request and uses recorder as a -// ResponseWriter. This method is supposed to be called from serveDNSMsg, -// the recorded response is used for counting metrics. +// [ResponseWriter]. This method is supposed to be called from serveDNSMsg, the +// recorded response is used for counting metrics. logger, req, and rw must not +// be nil. +// +// TODO(a.garipov): Think of a better name or refactor its connections to other +// methods. func (s *ServerBase) serveDNSMsgInternal( ctx context.Context, + logger *slog.Logger, req *dns.Msg, rw *RecorderResponseWriter, ) { var resp *dns.Msg - // Check if we can accept this message - switch action := s.acceptMsg(req); action { + // Check if we can accept this message. + switch action, reason := s.acceptMsg(req); action { case dns.MsgReject: - log.Debug("[%d] Query format is invalid", req.Id) + logger.DebugContext(ctx, "rejected", "reason", reason) resp = genErrorResponse(req, dns.RcodeFormatError) case dns.MsgRejectNotImplemented: - log.Debug("[%d] Rejecting this query", req.Id) + logger.DebugContext(ctx, "not implemented", "reason", reason) resp = genErrorResponse(req, dns.RcodeNotImplemented) case dns.MsgIgnore: - log.Debug("[%d] Ignoring this query", req.Id) + logger.DebugContext(ctx, "ignoring", "reason", reason) s.metrics.OnInvalidMsg(ctx) return @@ -293,11 +367,10 @@ func (s *ServerBase) serveDNSMsgInternal( // If resp is not empty at this stage, the request is invalid and we should // simply exit here. if resp != nil { - // Ignore errors and just write the message - log.Debug("[%d]: writing DNS response code %d", req.Id, resp.Rcode) + logger.DebugContext(ctx, "writing response", "rcode", resp.Rcode) err := rw.WriteMsg(ctx, req, resp) if err != nil { - log.Debug("[%d]: error writing a response: %v", req.Id, err) + logger.DebugContext(ctx, "error writing reject response", slogutil.KeyError, err) } return @@ -305,7 +378,7 @@ func (s *ServerBase) serveDNSMsgInternal( err := s.handler.ServeDNS(ctx, rw, req) if err != nil { - log.Debug("[%d]: handler returned an error: %s", req.Id, err) + logger.DebugContext(ctx, "handler error", slogutil.KeyError, err) s.metrics.OnError(ctx, err) resp = genErrorResponse(req, dns.RcodeServerFailure) @@ -315,7 +388,7 @@ func (s *ServerBase) serveDNSMsgInternal( err = rw.WriteMsg(ctx, req, resp) if err != nil { - log.Debug("[%d]: error writing a response: %s", req.Id, err) + logger.DebugContext(ctx, "error writing handler response", slogutil.KeyError, err) } } } @@ -342,78 +415,82 @@ func addEDE(req, resp *dns.Msg, code uint16, text string) { }) } -// acceptMsg checks if we should process the incoming DNS query. -func (s *ServerBase) acceptMsg(m *dns.Msg) (action dns.MsgAcceptAction) { - if m.Response { - log.Debug("[%d]: message rejected since this is a response", m.Id) - - return dns.MsgIgnore +// acceptMsg checks if we should process the incoming DNS query. msg must not be +// nil. +func (s *ServerBase) acceptMsg(msg *dns.Msg) (action dns.MsgAcceptAction, reason string) { + if msg.Response { + return dns.MsgIgnore, "message is a response" } - if m.Opcode != dns.OpcodeQuery && m.Opcode != dns.OpcodeNotify { - log.Debug("[%d]: rejected due to unsupported opcode", m.Opcode) - - return dns.MsgRejectNotImplemented + if msg.Opcode != dns.OpcodeQuery && msg.Opcode != dns.OpcodeNotify { + return dns.MsgRejectNotImplemented, fmt.Sprintf("unsupported opcode %d", msg.Opcode) } // There can only be one question in request, unless DNS Cookies are // involved. See AGDNS-738. - if len(m.Question) != 1 { - log.Debug("[%d]: message rejected due to wrong number of questions", m.Id) - - return dns.MsgReject + if len(msg.Question) != 1 { + return dns.MsgReject, "bad number of questions" } - // NOTIFY requests can have a SOA in the ANSWER section. See RFC 1996 Section 3.7 and 3.11. - if len(m.Answer) > 1 { - log.Debug("[%d]: message rejected due to wrong number of answers", m.Id) - - return dns.MsgReject + // NOTIFY requests can have a SOA in the ANSWER section. See RFC 1996 + // Section 3.7 and 3.11. + if len(msg.Answer) > 1 { + return dns.MsgReject, "bad number of answers" } - // IXFR request could have one SOA RR in the NS section. See RFC 1995, section 3. - if len(m.Ns) > 1 { - log.Debug("[%d]: message rejected due to wrong number of NS records", m.Id) - - return dns.MsgReject + // IXFR request could have one SOA RR in the NS section. See RFC 1995, + // section 3. + if len(msg.Ns) > 1 { + return dns.MsgReject, "bad number of ns records" } - return dns.MsgAccept + return dns.MsgAccept, "" } // handlePanicAndExit writes panic info to log, reports it to the registered -// MetricsListener and calls os.Exit with a positive exit code. +// [MetricsListener] and calls [os.Exit] with [osutil.ExitCodeFailure]. func (s *ServerBase) handlePanicAndExit(ctx context.Context) { - if v := recover(); v != nil { - log.Error( - "%q(%s://%s): panic encountered, exiting: %v\n%s", - s.name, - s.proto, - s.addr, - v, - string(debug.Stack()), - ) - - s.metrics.OnPanic(ctx, v) - - os.Exit(1) + v := recover() + if v == nil { + return } + + s.handlePanic(ctx, v) + + os.Exit(osutil.ExitCodeFailure) +} + +// handlePanic is the common panic handler. v should be the recovered value and +// must not be nil. +func (s *ServerBase) handlePanic(ctx context.Context, v any) { + s.metrics.OnPanic(ctx, v) + + logger, ok := slogutil.LoggerFromContext(ctx) + if !ok { + logger = s.baseLogger + } + + var args []any + err, ok := v.(error) + if ok { + args = []any{slogutil.KeyError, err} + } else { + args = []any{"value", v} + } + + logger.ErrorContext(ctx, "recovered from panic", args...) + slogutil.PrintStack(ctx, logger, slog.LevelError) } // handlePanicAndRecover writes panic info to log, reports it to the registered // MetricsListener. func (s *ServerBase) handlePanicAndRecover(ctx context.Context) { - if v := recover(); v != nil { - log.Error( - "%s %s://%s: panic encountered, recovered: %s\n%s", - s.name, - s.addr, - s.proto, - v, - string(debug.Stack()), - ) - s.metrics.OnPanic(ctx, v) + v := recover() + if v == nil { + return } + + s.handlePanic(ctx, v) } // listenUDP initializes and starts s.udpListener using s.addr. If the TCP @@ -457,17 +534,18 @@ func (s *ServerBase) listenTCP(ctx context.Context) (err error) { } // closeListeners stops UDP and TCP listeners. -func (s *ServerBase) closeListeners() { +func (s *ServerBase) closeListeners(ctx context.Context) { if s.udpListener != nil { err := s.udpListener.Close() if err != nil { - log.Info("[%s]: Failed to close NetworkUDP listener: %v", s.Name(), err) + s.baseLogger.InfoContext(ctx, "closing udp listener", slogutil.KeyError, err) } } + if s.tcpListener != nil { err := s.tcpListener.Close() if err != nil { - log.Info("[%s]: Failed to close NetworkTCP listener: %v", s.Name(), err) + s.baseLogger.InfoContext(ctx, "closing tcp listener", slogutil.KeyError, err) } } } @@ -476,8 +554,9 @@ func (s *ServerBase) closeListeners() { func (s *ServerBase) waitShutdown(ctx context.Context) (err error) { // Using this channel to wait until all goroutines finish their work closed := make(chan struct{}) + go func() { - defer log.OnPanic("waitShutdown") + defer slogutil.RecoverAndLog(ctx, s.baseLogger) // wait until all queries are processed s.wg.Wait() diff --git a/internal/dnsserver/serverdns.go b/internal/dnsserver/serverdns.go index 9276f3a..061fca2 100644 --- a/internal/dnsserver/serverdns.go +++ b/internal/dnsserver/serverdns.go @@ -10,8 +10,9 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" + "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/syncutil" "github.com/miekg/dns" "github.com/panjf2000/ants/v2" @@ -39,14 +40,16 @@ const ( // ConfigDNS is a struct that needs to be passed to NewServerDNS to // initialize a new ServerDNS instance. type ConfigDNS struct { - ConfigBase + // Base is the base configuration for this server. It must not be nil and + // must be valid. + Base *ConfigBase // ReadTimeout is the net.Conn.SetReadTimeout value for new connections. - // If not set it defaults to DefaultReadTimeout. + // If not set it defaults to [DefaultReadTimeout]. ReadTimeout time.Duration // WriteTimeout is the net.Conn.SetWriteTimeout value for connections. If - // not set it defaults to DefaultWriteTimeout. + // not set it defaults to [DefaultWriteTimeout]. WriteTimeout time.Duration // TCPIdleTimeout is the timeout for waiting between multiple queries. If @@ -68,6 +71,7 @@ type ConfigDNS struct { TCPSize int // MaxUDPRespSize is the maximum size of DNS response over UDP protocol. + // If not set, [dns.MinMsgSize] is used. MaxUDPRespSize uint16 // MaxPipelineEnabled, if true, enables TCP pipeline limiting. @@ -75,6 +79,8 @@ type ConfigDNS struct { } // ServerDNS is a plain DNS server (e.g. it supports UDP and TCP protocols). +// +// TODO(a.garipov): Consider unembedding ServerBase. type ServerDNS struct { *ServerBase @@ -94,33 +100,41 @@ type ServerDNS struct { respPool *syncutil.Pool[[]byte] // tcpConns is a set that is used to track active connections. - tcpConns map[net.Conn]struct{} + tcpConns *container.MapSet[net.Conn] tcpConnsMu *sync.Mutex - // TODO(ameshkov, a.garipov): Only save the parameters a server actually - // needs. - conf ConfigDNS + readTimeout time.Duration + tcpIdleTimeout time.Duration + writeTimeout time.Duration + + maxPipelineCount uint + + maxUDPRespSize uint16 + + maxPipelineEnabled bool } // type check var _ Server = (*ServerDNS)(nil) -// NewServerDNS creates a new ServerDNS instance. -func NewServerDNS(conf ConfigDNS) (s *ServerDNS) { - return newServerDNS(ProtoDNS, conf) +// NewServerDNS creates a new ServerDNS instance. c must not be nil and must be +// valid. +func NewServerDNS(c *ConfigDNS) (s *ServerDNS) { + return newServerDNS(ProtoDNS, c) } // newServerDNS initializes a new ServerDNS instance with the specified proto. -// This function is reused in ServerTLS as it is basically a plain DNS-over-TCP -// server with a TLS layer on top of it. -func newServerDNS(proto Protocol, conf ConfigDNS) (s *ServerDNS) { +// This function is reused in [ServerTLS] as it is basically a plain +// DNS-over-TCP server with a TLS layer on top of it. c must not be nil and +// must be valid. +func newServerDNS(proto Protocol, c *ConfigDNS) (s *ServerDNS) { // Init default settings first. - conf.ReadTimeout = cmp.Or(conf.ReadTimeout, DefaultReadTimeout) - conf.WriteTimeout = cmp.Or(conf.WriteTimeout, DefaultWriteTimeout) - conf.TCPIdleTimeout = cmp.Or(conf.TCPIdleTimeout, DefaultTCPIdleTimeout) + c.ReadTimeout = cmp.Or(c.ReadTimeout, DefaultReadTimeout) + c.WriteTimeout = cmp.Or(c.WriteTimeout, DefaultWriteTimeout) + c.TCPIdleTimeout = cmp.Or(c.TCPIdleTimeout, DefaultTCPIdleTimeout) // TODO(a.garipov): Return an error instead. - if t := conf.TCPIdleTimeout; t < 0 || t > MaxTCPIdleTimeout { + if t := c.TCPIdleTimeout; t < 0 || t > MaxTCPIdleTimeout { panic(fmt.Errorf( "newServerDNS: tcp idle timeout: %w: must be >= 0 and <= %s, got %s", errors.ErrOutOfRange, @@ -131,27 +145,34 @@ func newServerDNS(proto Protocol, conf ConfigDNS) (s *ServerDNS) { // Use dns.MinMsgSize since 99% of DNS queries fit this size, so this is a // sensible default. - conf.UDPSize = cmp.Or(conf.UDPSize, dns.MinMsgSize) - conf.TCPSize = cmp.Or(conf.TCPSize, dns.MinMsgSize) + c.UDPSize = cmp.Or(c.UDPSize, dns.MinMsgSize) + c.TCPSize = cmp.Or(c.TCPSize, dns.MinMsgSize) - if conf.ListenConfig == nil { - conf.ListenConfig = netext.DefaultListenConfigWithOOB(nil) - } + c.Base.ListenConfig = cmp.Or(c.Base.ListenConfig, netext.DefaultListenConfigWithOOB(nil)) s = &ServerDNS{ - ServerBase: newServerBase(proto, conf.ConfigBase), - workerPool: newPoolNonblocking(), + ServerBase: newServerBase(proto, c.Base), - udpPool: syncutil.NewSlicePool[byte](conf.UDPSize), - tcpPool: syncutil.NewSlicePool[byte](conf.TCPSize), + udpPool: syncutil.NewSlicePool[byte](c.UDPSize), + tcpPool: syncutil.NewSlicePool[byte](c.TCPSize), respPool: syncutil.NewSlicePool[byte](dns.MinMsgSize), - tcpConns: map[net.Conn]struct{}{}, + tcpConns: container.NewMapSet[net.Conn](), tcpConnsMu: &sync.Mutex{}, - conf: conf, + readTimeout: c.ReadTimeout, + tcpIdleTimeout: c.TCPIdleTimeout, + writeTimeout: c.WriteTimeout, + + maxPipelineCount: c.MaxPipelineCount, + + maxUDPRespSize: max(c.MaxUDPRespSize, dns.MinMsgSize), + + maxPipelineEnabled: c.MaxPipelineEnabled, } + s.workerPool = mustNewPoolNonblocking(s.baseLogger) + return s } @@ -166,7 +187,7 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) { return ErrServerAlreadyStarted } - log.Info("[%s]: Starting the server", s.Name()) + s.baseLogger.InfoContext(ctx, "starting server") ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, @@ -202,7 +223,7 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) { s.started = true - log.Info("[%s]: Server has been started", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been started") return nil } @@ -211,20 +232,22 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) { func (s *ServerDNS) Shutdown(ctx context.Context) (err error) { defer func() { err = errors.Annotate(err, "shutting down dns server: %w") }() - err = s.shutdown() + s.baseLogger.InfoContext(ctx, "shutting down server") + + err = s.shutdown(ctx) if err != nil { - log.Info("[%s]: Failed to shutdown: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "error while shutting down", slogutil.KeyError, err) return err } - s.unblockTCPConns() + s.unblockTCPConns(ctx) err = s.waitShutdown(ctx) // Close the workerPool and releases all workers. s.workerPool.Release() - log.Info("[%s]: Finished stopping the server", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been shut down") return err } @@ -236,10 +259,11 @@ func (s *ServerDNS) startServeUDP(ctx context.Context) { defer s.handlePanicAndExit(ctx) defer s.wg.Done() - log.Info("[%s]: Start listening to udp://%s", s.Name(), s.Addr()) + s.baseLogger.InfoContext(ctx, "starting listening udp") + err := s.serveUDP(ctx, s.udpListener) if err != nil { - log.Info("[%s]: Finished listening to udp://%s due to %v", s.Name(), s.Addr(), err) + s.baseLogger.WarnContext(ctx, "listening udp failed", slogutil.KeyError, err) } } @@ -250,15 +274,15 @@ func (s *ServerDNS) startServeTCP(ctx context.Context) { defer s.handlePanicAndExit(ctx) defer s.wg.Done() - log.Info("[%s]: Start listening to tcp://%s", s.Name(), s.Addr()) + s.baseLogger.InfoContext(ctx, "starting listening tcp") err := s.serveTCP(ctx, s.tcpListener) if err != nil { - log.Info("[%s]: Finished listening to tcp://%s due to %v", s.Name(), s.Addr(), err) + s.baseLogger.WarnContext(ctx, "listening tcp failed", slogutil.KeyError, err) } } // shutdown marks the server as stopped and closes active listeners. -func (s *ServerDNS) shutdown() (err error) { +func (s *ServerDNS) shutdown(ctx context.Context) (err error) { s.mu.Lock() defer s.mu.Unlock() @@ -270,21 +294,24 @@ func (s *ServerDNS) shutdown() (err error) { s.started = false // Now close all listeners. - s.closeListeners() + s.closeListeners(ctx) return nil } // unblockTCPConns unblocks reads for all active TCP connections. -func (s *ServerDNS) unblockTCPConns() { +func (s *ServerDNS) unblockTCPConns(ctx context.Context) { s.tcpConnsMu.Lock() defer s.tcpConnsMu.Unlock() - for conn := range s.tcpConns { + + s.tcpConns.Range(func(conn net.Conn) (cont bool) { err := conn.SetReadDeadline(time.Unix(1, 0)) if err != nil { - log.Debug("[%s]: Failed to set read deadline: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "failed to unblock conn", slogutil.KeyError, err) } - } + + return true + }) } // writeDeadlineSetter is an interface for connections that can set write @@ -306,6 +333,11 @@ func withWriteDeadline( // sooner. ctx, cancel := context.WithTimeout(ctx, timeout) + logger, ok := slogutil.LoggerFromContext(ctx) + if !ok { + logger = slogutil.NewDiscardLogger() + } + defer func() { cancel() @@ -313,7 +345,7 @@ func withWriteDeadline( if err != nil && !errors.Is(err, net.ErrClosed) { // Consider deadline errors non-critical. Ignore [net.ErrClosed] as // it is expected to happen when the client closes connections. - log.Error("dnsserver: removing deadlines: %s", err) + logger.WarnContext(ctx, "removing deadlines", slogutil.KeyError, err) } }() @@ -324,7 +356,7 @@ func withWriteDeadline( if err != nil && !errors.Is(err, net.ErrClosed) { // Consider deadline errors non-critical. Ignore [net.ErrClosed] as it // is expected to happen when the client closes connections. - log.Error("dnsserver: setting deadlines: %s", err) + logger.WarnContext(ctx, "setting deadlines", slogutil.KeyError, err) } f() diff --git a/internal/dnsserver/serverdns_test.go b/internal/dnsserver/serverdns_test.go index 0a7634c..cb6357a 100644 --- a/internal/dnsserver/serverdns_test.go +++ b/internal/dnsserver/serverdns_test.go @@ -12,7 +12,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -376,7 +375,7 @@ func TestServerDNS_integration_udpMsgIgnore(t *testing.T) { conn, err := net.Dial("udp", addr) require.Nil(t, err) - defer log.OnCloserError(conn, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, conn.Close) // Write some crap _, err = conn.Write([]byte{1, 3, 1, 52, 12, 5, 32, 12}) @@ -473,7 +472,7 @@ func TestServerDNS_integration_tcpMsgIgnore(t *testing.T) { conn, err := net.Dial("tcp", addr) require.Nil(t, err) - defer log.OnCloserError(conn, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, conn.Close) // Write the invalid request _, err = conn.Write(tc.buf) diff --git a/internal/dnsserver/serverdnscrypt.go b/internal/dnsserver/serverdnscrypt.go index 98535a3..9cc5deb 100644 --- a/internal/dnsserver/serverdnscrypt.go +++ b/internal/dnsserver/serverdnscrypt.go @@ -1,6 +1,7 @@ package dnsserver import ( + "cmp" "context" "fmt" "net" @@ -8,7 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/ameshkov/dnscrypt/v2" "github.com/miekg/dns" ) @@ -16,36 +17,41 @@ import ( // ConfigDNSCrypt is a struct that needs to be passed to NewServerDNSCrypt to // initialize a new ServerDNSCrypt instance. type ConfigDNSCrypt struct { - ConfigBase + // Base is the base configuration for this server. It must not be nil + // and must be valid. + Base *ConfigBase - // DNSCryptResolverCert is a DNSCrypt server certificate. - DNSCryptResolverCert *dnscrypt.Cert + // ResolverCert is a DNSCrypt server certificate. It must not be nil. + ResolverCert *dnscrypt.Cert - // DNSCryptProviderName is a DNSCrypt provider name (see DNSCrypt spec). - DNSCryptProviderName string + // ProviderName is a DNSCrypt provider name, see DNSCrypt spec. It must not + // be empty. + ProviderName string } // ServerDNSCrypt is a DNSCrypt server implementation. +// +// TODO(a.garipov): Consider unembedding ServerBase. type ServerDNSCrypt struct { *ServerBase - dnsCryptServer *dnscrypt.Server - - conf ConfigDNSCrypt + server *dnscrypt.Server + resolverCert *dnscrypt.Cert + providerName string } // type check var _ Server = (*ServerDNSCrypt)(nil) -// NewServerDNSCrypt creates a new instance of ServerDNSCrypt. -func NewServerDNSCrypt(conf ConfigDNSCrypt) (s *ServerDNSCrypt) { - if conf.ListenConfig == nil { - conf.ListenConfig = netext.DefaultListenConfig(nil) - } +// NewServerDNSCrypt creates a new instance of ServerDNSCrypt. c must not be +// nil and must be valid. +func NewServerDNSCrypt(c *ConfigDNSCrypt) (s *ServerDNSCrypt) { + c.Base.ListenConfig = cmp.Or(c.Base.ListenConfig, netext.DefaultListenConfig(nil)) return &ServerDNSCrypt{ - ServerBase: newServerBase(ProtoDNSCrypt, conf.ConfigBase), - conf: conf, + ServerBase: newServerBase(ProtoDNSCrypt, c.Base), + resolverCert: c.ResolverCert, + providerName: c.ProviderName, } } @@ -65,7 +71,7 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) { return ErrServerAlreadyStarted } - log.Info("[%s]: Starting the server", s.Name()) + s.baseLogger.InfoContext(ctx, "starting server") ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, @@ -74,9 +80,9 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) { }) // Create DNSCrypt server with a handler - s.dnsCryptServer = &dnscrypt.Server{ - ProviderName: s.conf.DNSCryptProviderName, - ResolverCert: s.conf.DNSCryptResolverCert, + s.server = &dnscrypt.Server{ + ProviderName: s.providerName, + ResolverCert: s.resolverCert, Handler: &dnsCryptHandler{ srv: s, }, @@ -89,7 +95,7 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) { s.started = true - log.Info("[%s]: Server has been started", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been started") return nil } @@ -98,16 +104,18 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) { func (s *ServerDNSCrypt) Shutdown(ctx context.Context) (err error) { defer func() { err = errors.Annotate(err, "shutting down dnscrypt server: %w") }() - log.Info("[%s]: Stopping the server", s.Name()) - err = s.shutdown() + s.baseLogger.InfoContext(ctx, "shutting down server") + + err = s.shutdown(ctx) if err != nil { - log.Info("[%s]: Failed to shutdown: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "error while shutting down", slogutil.KeyError, err) return err } - err = s.dnsCryptServer.Shutdown(ctx) - log.Info("[%s]: Finished stopping the server", s.Name()) + err = s.server.Shutdown(ctx) + + s.baseLogger.InfoContext(ctx, "server has been shut down") return err } @@ -133,7 +141,7 @@ func (s *ServerDNSCrypt) startServe(ctx context.Context) (err error) { } if len(errs) > 0 { - s.closeListeners() + s.closeListeners(ctx) return fmt.Errorf("creating listeners: %w", errors.Join(errs...)) } @@ -150,7 +158,7 @@ func (s *ServerDNSCrypt) startServeUDP(ctx context.Context) { // the application won't be able to continue listening to DoT. defer s.handlePanicAndExit(ctx) - log.Info("[%s]: Start listening to udp://%s", s.Name(), s.Addr()) + s.baseLogger.InfoContext(ctx, "starting listening udp") // TODO(ameshkov): Add context to the ServeTCP and ServeUDP methods in // dnscrypt/v3. Or at least add ServeTCPContext and ServeUDPContext @@ -158,9 +166,9 @@ func (s *ServerDNSCrypt) startServeUDP(ctx context.Context) { // // TODO(ameshkov): Redo the dnscrypt module to make it not depend on // *net.UDPConn and use net.PacketConn instead. - err := s.dnsCryptServer.ServeUDP(s.udpListener.(*net.UDPConn)) + err := s.server.ServeUDP(s.udpListener.(*net.UDPConn)) if err != nil { - log.Info("[%s]: Finished listening to udp://%s due to %v", s.Name(), s.Addr(), err) + s.baseLogger.WarnContext(ctx, "listening udp failed", slogutil.KeyError, err) } } @@ -170,19 +178,19 @@ func (s *ServerDNSCrypt) startServeTCP(ctx context.Context) { // the application won't be able to continue listening to DoT. defer s.handlePanicAndExit(ctx) - log.Info("[%s]: Start listening to tcp://%s", s.Name(), s.Addr()) + s.baseLogger.InfoContext(ctx, "starting listening tcp") // TODO(ameshkov): Add context to the ServeTCP and ServeUDP methods in // dnscrypt/v3. Or at least add ServeTCPContext and ServeUDPContext // methods for now. - err := s.dnsCryptServer.ServeTCP(s.tcpListener) + err := s.server.ServeTCP(s.tcpListener) if err != nil { - log.Info("[%s]: Finished listening to tcp://%s due to %v", s.Name(), s.Addr(), err) + s.baseLogger.WarnContext(ctx, "listening tcp failed", slogutil.KeyError, err) } } // shutdown marks the server as stopped and closes active listeners. -func (s *ServerDNSCrypt) shutdown() (err error) { +func (s *ServerDNSCrypt) shutdown(ctx context.Context) (err error) { s.mu.Lock() defer s.mu.Unlock() @@ -194,7 +202,7 @@ func (s *ServerDNSCrypt) shutdown() (err error) { s.started = false // Now close all listeners - s.closeListeners() + s.closeListeners(ctx) return nil } @@ -212,7 +220,7 @@ func (h *dnsCryptHandler) ServeDNS(rw dnscrypt.ResponseWriter, r *dns.Msg) (err defer func() { err = errors.Annotate(err, "dnscrypt: %w") }() // TODO(ameshkov): Use the context from the arguments once it's added there. - ctx, cancel := h.srv.requestContext() + ctx, cancel := h.srv.requestContext(context.Background()) defer cancel() ctx = ContextWithRequestInfo(ctx, &RequestInfo{StartTime: time.Now()}) diff --git a/internal/dnsserver/serverdnscrypt_test.go b/internal/dnsserver/serverdnscrypt_test.go index 3365b69..e019951 100644 --- a/internal/dnsserver/serverdnscrypt_test.go +++ b/internal/dnsserver/serverdnscrypt_test.go @@ -6,12 +6,19 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/golibs/testutil" "github.com/ameshkov/dnscrypt/v2" "github.com/ameshkov/dnsstamps" "github.com/miekg/dns" "github.com/stretchr/testify/require" ) +// TODO(a.garipov): Remove when https://github.com/ameshkov/dnscrypt/issues/26 +// is fixed. +func TestMain(m *testing.M) { + testutil.DiscardLogOutput(m) +} + func TestServerDNSCrypt_integration_query(t *testing.T) { testCases := []struct { handler dnsserver.Handler diff --git a/internal/dnsserver/serverdnstcp.go b/internal/dnsserver/serverdnstcp.go index b8411e4..1596c71 100644 --- a/internal/dnsserver/serverdnstcp.go +++ b/internal/dnsserver/serverdnstcp.go @@ -6,20 +6,21 @@ import ( "encoding/binary" "fmt" "io" + "log/slog" "net" "slices" "sync" "time" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/syncutil" "github.com/miekg/dns" ) // serveTCP runs the TCP serving loop. func (s *ServerDNS) serveTCP(ctx context.Context, l net.Listener) (err error) { - defer log.OnCloserError(l, log.DEBUG) + defer func() { closeWithLog(ctx, s.baseLogger, "closing tcp listener", l) }() for s.isStarted() { err = s.acceptTCPConn(ctx, l) @@ -56,7 +57,7 @@ func (s *ServerDNS) acceptTCPConn(ctx context.Context, l net.Listener) (err erro defer s.tcpConnsMu.Unlock() // Track the connection to allow unblocking reads on shutdown. - s.tcpConns[conn] = struct{}{} + s.tcpConns.Add(conn) }() s.wg.Add(1) @@ -99,30 +100,30 @@ func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) { wg.Wait() - log.OnCloserError(conn, log.DEBUG) + closeWithLog(ctx, s.baseLogger, "closing tcp conn", conn) s.tcpConnsMu.Lock() defer s.tcpConnsMu.Unlock() - delete(s.tcpConns, conn) + s.tcpConns.Delete(conn) }() defer s.handlePanicAndRecover(ctx) var msgSema syncutil.Semaphore = syncutil.EmptySemaphore{} - if s.conf.MaxPipelineEnabled { - msgSema = syncutil.NewChanSemaphore(s.conf.MaxPipelineCount) + if s.maxPipelineEnabled { + msgSema = syncutil.NewChanSemaphore(s.maxPipelineCount) } // writeMu serializes write deadline setting and writing to conn. writeMu := &sync.Mutex{} - timeout := s.conf.ReadTimeout - idleTimeout := s.conf.TCPIdleTimeout + timeout := s.readTimeout + idleTimeout := s.tcpIdleTimeout err := handshake(conn, timeout) if err != nil { - s.logReadErr("handshaking", err) + s.logReadErr(ctx, "handshaking", err) return } @@ -130,7 +131,7 @@ func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) { for s.isStarted() { err = s.acceptTCPMsg(conn, wg, writeMu, timeout, msgSema) if err != nil { - s.logReadErr("reading from conn", err) + s.logReadErr(ctx, "reading from conn", err) return } @@ -142,12 +143,12 @@ func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) { // logReadErr logs err on debug level unless it's trivial ([io.EOF] or // [net.ErrClosed]). -func (s *ServerDNS) logReadErr(msg string, err error) { +func (s *ServerDNS) logReadErr(ctx context.Context, msg string, err error) { if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) { return } - log.Debug("[%s]: %s: %s", s.Name(), msg, err) + s.baseLogger.DebugContext(ctx, msg, slogutil.KeyError, err) } // acceptTCPMsg reads and starts processing a single TCP message. If conn is a @@ -171,7 +172,7 @@ func (s *ServerDNS) acceptTCPMsg( ri.TLSServerName = cs.ConnectionState().ServerName } - reqCtx, reqCancel := s.requestContext() + reqCtx, reqCancel := s.requestContext(context.Background()) reqCtx = ContextWithRequestInfo(reqCtx, ri) err = msgSema.Acquire(reqCtx) @@ -213,8 +214,8 @@ func (s *ServerDNS) serveTCPMessage( respPool: s.respPool, writeMu: writeMu, conn: conn, - writeTimeout: s.conf.WriteTimeout, - idleTimeout: s.conf.TCPIdleTimeout, + writeTimeout: s.writeTimeout, + idleTimeout: s.tcpIdleTimeout, } written := s.serveDNS(ctx, buf, rw) @@ -223,7 +224,7 @@ func (s *ServerDNS) serveTCPMessage( // avoid hanging connections. Than might happen if the handler // rate-limited connections or if we received garbage data instead of // a DNS query. - log.OnCloserError(conn, log.DEBUG) + slogutil.CloseAndLog(ctx, s.baseLogger, conn, slog.LevelDebug) } } diff --git a/internal/dnsserver/serverdnsudp.go b/internal/dnsserver/serverdnsudp.go index e334e91..091a747 100644 --- a/internal/dnsserver/serverdnsudp.go +++ b/internal/dnsserver/serverdnsudp.go @@ -8,14 +8,13 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/syncutil" "github.com/miekg/dns" ) // serveUDP runs the UDP serving loop. func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) (err error) { - defer log.OnCloserError(conn, log.DEBUG) + defer func() { closeWithLog(ctx, s.baseLogger, "closing udp conn", conn) }() for s.isStarted() { err = s.acceptUDPMsg(ctx, conn) @@ -52,7 +51,7 @@ func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err s.wg.Add(1) // Save the start time here, but create the context inside the goroutine, - // since s.reqCtx.New can be slow. + // since s.requestContext can be slow. // // TODO(a.garipov): The slowness is likely due to constant reallocation of // timers in [context.WithTimeout]. Consider creating an optimized reusable @@ -60,7 +59,7 @@ func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err startTime := time.Now() return s.workerPool.Submit(func() { - reqCtx, reqCancel := s.requestContext() + reqCtx, reqCancel := s.requestContext(context.Background()) defer reqCancel() reqCtx = ContextWithRequestInfo(reqCtx, &RequestInfo{ @@ -86,8 +85,8 @@ func (s *ServerDNS) serveUDPPacket( respPool: s.respPool, udpSession: sess, conn: conn, - writeTimeout: s.conf.WriteTimeout, - maxRespSize: s.conf.MaxUDPRespSize, + writeTimeout: s.writeTimeout, + maxRespSize: s.maxUDPRespSize, } s.serveDNS(ctx, buf, rw) } @@ -98,7 +97,7 @@ func (s *ServerDNS) readUDPMsg( conn net.PacketConn, buf []byte, ) (n int, sess netext.PacketSession, err error) { - err = conn.SetReadDeadline(time.Now().Add(s.conf.ReadTimeout)) + err = conn.SetReadDeadline(time.Now().Add(s.readTimeout)) if err != nil { return 0, nil, err } diff --git a/internal/dnsserver/serverhttps.go b/internal/dnsserver/serverhttps.go index 81427e2..c661118 100644 --- a/internal/dnsserver/serverhttps.go +++ b/internal/dnsserver/serverhttps.go @@ -1,11 +1,13 @@ package dnsserver import ( + "cmp" "context" "crypto/tls" "encoding/base64" "fmt" "io" + "log/slog" "net" "net/http" "net/url" @@ -17,7 +19,8 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/httphdr" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/ioutil" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/miekg/dns" @@ -55,13 +58,13 @@ var NextProtoDoH3 = []string{http3.NextProtoH3, http2.NextProtoTLS, "http/1.1"} // ConfigHTTPS is a struct that needs to be passed to NewServerHTTPS to // initialize a new ServerHTTPS instance. You can choose whether HTTP/3 is -// enabled or not by specifying [ConfigBase.Network]. By default, the server -// will listen to both HTTP/2 and HTTP/3, but if you set it to NetworkTCP, the -// server will only use HTTP/2 and NetworkUDP will mean HTTP/3 only. +// enabled or not by specifying [Base.Network]. By default, the server will +// listen to both HTTP/2 and HTTP/3, but if you set it to NetworkTCP, the server +// will only use HTTP/2 and NetworkUDP will mean HTTP/3 only. type ConfigHTTPS struct { // TLSConfDefault is the TLS configuration for HTTPS. If not set and - // [ConfigBase.Network] is set to NetworkTCP the server will listen to plain - // HTTP. If it is not nil, it must be set to [NextProtoDoH]. + // [Base.Network] is set to NetworkTCP the server will listen to plain HTTP. + // If it is not nil, it must be set to [NextProtoDoH]. TLSConfDefault *tls.Config // TLSConfH3 is the TLS configuration for DoH3. If it is not nil, it must @@ -72,10 +75,12 @@ type ConfigHTTPS struct { // If it is empty, the server will return 404 for requests like that. NonDNSHandler http.Handler - ConfigBase + // Base is the base configuration for this server. It must not be nil and + // must be valid. + Base *ConfigBase // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer - // is allowed to open. + // is allowed to open. If not set, 100 is used. MaxStreamsPerPeer int // QUICLimitsEnabled, if true, enables QUIC limiting. @@ -86,6 +91,8 @@ type ConfigHTTPS struct { // and DNS JSON format. Regular DoH (wireformat) will be available at the // /dns-query location. JSON format will be available at the "/resolve" // location. +// +// TODO(a.garipov): Consider unembedding ServerBase. type ServerHTTPS struct { *ServerBase @@ -103,26 +110,36 @@ type ServerHTTPS struct { // quicTransport is saved here to close it later. quicTransport *quic.Transport - conf ConfigHTTPS + tlsConfDefault *tls.Config + tlsConfH3 *tls.Config + + nonDNSHandler http.Handler + + maxStreamsPerPeer int + + quicLimitsEnabled bool } // type check var _ Server = (*ServerHTTPS)(nil) -// NewServerHTTPS creates a new ServerHTTPS instance. -func NewServerHTTPS(conf ConfigHTTPS) (s *ServerHTTPS) { - if conf.ListenConfig == nil { - // Do not enable OOB here, because ListenPacket is only used by HTTP/3, - // and quic-go sets the necessary flags. - conf.ListenConfig = netext.DefaultListenConfig(nil) - } +// NewServerHTTPS creates a new ServerHTTPS instance. c must not be nil and +// must be valid. +func NewServerHTTPS(c *ConfigHTTPS) (s *ServerHTTPS) { + // Do not enable OOB here, because ListenPacket is only used by HTTP/3, and + // quic-go sets the necessary flags. + c.Base.ListenConfig = cmp.Or(c.Base.ListenConfig, netext.DefaultListenConfig(nil)) - s = &ServerHTTPS{ - ServerBase: newServerBase(ProtoDoH, conf.ConfigBase), - conf: conf, + return &ServerHTTPS{ + ServerBase: newServerBase(ProtoDoH, c.Base), + tlsConfDefault: c.TLSConfDefault, + tlsConfH3: c.TLSConfH3, + nonDNSHandler: c.NonDNSHandler, + // NOTE: 100 is the current default in package quic, but set it + // explicitly in case that changes in the future. + maxStreamsPerPeer: cmp.Or(c.MaxStreamsPerPeer, 100), + quicLimitsEnabled: c.QUICLimitsEnabled, } - - return s } // Start implements the dnsserver.Server interface for *ServerHTTPS. @@ -136,7 +153,7 @@ func (s *ServerHTTPS) Start(ctx context.Context) (err error) { return ErrServerAlreadyStarted } - log.Info("[%s]: Starting the server", s.addr) + s.baseLogger.InfoContext(ctx, "starting server") ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, @@ -164,7 +181,7 @@ func (s *ServerHTTPS) Start(ctx context.Context) (err error) { s.started = true - log.Info("[%s]: Server has been started", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been started") return nil } @@ -173,16 +190,18 @@ func (s *ServerHTTPS) Start(ctx context.Context) (err error) { func (s *ServerHTTPS) Shutdown(ctx context.Context) (err error) { defer func() { err = errors.Annotate(err, "shutting down doh server: %w") }() - log.Info("[%s]: Stopping the server", s.Name()) + s.baseLogger.InfoContext(ctx, "shutting down server") + err = s.shutdown(ctx) if err != nil { - log.Info("[%s]: Failed to shutdown: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "error while shutting down", slogutil.KeyError, err) return err } err = s.waitShutdown(ctx) - log.Info("[%s]: Finished stopping the server", s.Name()) + + s.baseLogger.InfoContext(ctx, "server has been shut down") return err } @@ -208,7 +227,7 @@ func (s *ServerHTTPS) startHTTPSServer(ctx context.Context) (err error) { ReadHeaderTimeout: httpReadTimeout, WriteTimeout: httpWriteTimeout, IdleTimeout: httpIdleTimeout, - ErrorLog: log.StdLog("dnsserver/serverhttps: "+s.name, log.DEBUG), + ErrorLog: slog.NewLogLogger(s.baseLogger.Handler(), slog.LevelDebug), } // Start the server worker goroutine. @@ -262,41 +281,41 @@ func (s *ServerHTTPS) shutdown(ctx context.Context) (err error) { if s.tcpListener != nil { err = s.tcpListener.Close() if err != nil { - log.Info("[%s]: Failed to close NetworkTCP listener: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "closing tcp listener", slogutil.KeyError, err) } } // Second, shutdown the HTTP server. err = s.httpServer.Shutdown(ctx) if err != nil { - log.Debug("[%s]: http server shutdown: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "shutting down http server", slogutil.KeyError, err) } // Finally, shutdown the HTTP/3 server. - s.shutdownH3() + s.shutdownH3(ctx) return nil } // shutdownH3 shuts down the HTTP/3 server, if enabled, and logs all errors. -func (s *ServerHTTPS) shutdownH3() { +func (s *ServerHTTPS) shutdownH3(ctx context.Context) { if s.h3Server == nil { return } err := s.quicListener.Close() if err != nil { - log.Debug("[%s]: quic listener shutdown: %s", s.Name(), err) + s.baseLogger.WarnContext(ctx, "closing quic listener", slogutil.KeyError, err) } err = s.quicTransport.Close() if err != nil { - log.Debug("[%s]: quic transport shutdown: %s", s.Name(), err) + s.baseLogger.WarnContext(ctx, "closing quic transport", slogutil.KeyError, err) } err = s.h3Server.Close() if err != nil { - log.Debug("[%s]: http/3 server shutdown: %s", s.Name(), err) + s.baseLogger.WarnContext(ctx, "shutting down http/3 server", slogutil.KeyError, err) } } @@ -310,19 +329,15 @@ func (s *ServerHTTPS) serveHTTPS(ctx context.Context, hs *http.Server, l net.Lis defer s.handlePanicAndExit(ctx) scheme := urlutil.SchemeHTTPS - if s.conf.TLSConfDefault == nil { + if s.tlsConfDefault == nil { scheme = urlutil.SchemeHTTP } - u := &url.URL{ - Scheme: scheme, - Host: s.addr, - } - log.Info("[%s]: Start listening to %s", s.name, u) + s.baseLogger.InfoContext(ctx, "starting serving http", "scheme", scheme) err := hs.Serve(l) if err != nil { - log.Info("[%s]: Finished listening to %s due to %v", s.name, u, err) + s.baseLogger.WarnContext(ctx, "serving http failed", "scheme", scheme, slogutil.KeyError, err) } } @@ -334,20 +349,18 @@ func (s *ServerHTTPS) serveH3(ctx context.Context, hs *http3.Server, ql *quic.Ea // application won't be able to continue listening to DoH. defer s.handlePanicAndExit(ctx) - u := &url.URL{ - Scheme: http3.NextProtoH3, - Host: s.addr, - } - log.Info("[%s]: Start listening to %s", s.name, u) + s.baseLogger.InfoContext(ctx, "starting serving http/3") err := hs.ServeListener(ql) if err != nil { - log.Info("[%s]: Finished listening to %s due to %v", s.name, u, err) + s.baseLogger.WarnContext(ctx, "serving http/3 failed", slogutil.KeyError, err) } } -// httpHandler is a helper structure that implements http.Handler -// and holds pointers to ServerHTTPS, net.Listener. +// httpHandler is a helper structure that implements [http.Handler] and holds +// pointers to ServerHTTPS and net.Addr. +// +// TODO(a.garipov): Think of ways of using [httputil.LogMiddleware] with this. type httpHandler struct { srv *ServerHTTPS localAddr net.Addr @@ -384,22 +397,13 @@ func (h *httpHandler) remoteAddr(r *http.Request) (addr net.Addr) { // ServeHTTP implements the http.Handler interface for *httpHandler. It reads // the DNS data from the request, resolves it, and sends a response. -// -// NOTE: r.Context() is only used to control cancelation. To add values to the -// context, use the BaseContext of this handler's ServerHTTPS. func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx, cancel := h.srv.requestContext() + ctx := r.Context() + ctx, cancel := h.srv.requestContext(ctx) defer cancel() - if dl, ok := r.Context().Deadline(); ok { - ctx, cancel = context.WithDeadline(ctx, dl) - defer cancel() - } - defer h.srv.handlePanicAndRecover(ctx) - log.Debug("Received a request to %s", r.URL) - // TODO(ameshkov): Consider using ants.Pool here. isDNS, _, _ := isDoH(r) @@ -409,8 +413,8 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if h.srv.conf.NonDNSHandler != nil { - h.srv.conf.NonDNSHandler.ServeHTTP(w, r) + if hdlr := h.srv.nonDNSHandler; hdlr != nil { + hdlr.ServeHTTP(w, r) } else { h.srv.metrics.OnInvalidMsg(ctx) http.Error(w, "", http.StatusNotFound) @@ -422,7 +426,6 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *http.Request) { m, err := httpRequestToMsg(r) if err != nil { - log.Debug("Failed to convert request to a DNS message: %v", err) h.srv.metrics.OnInvalidMsg(ctx) http.Error(w, err.Error(), http.StatusBadRequest) @@ -439,7 +442,6 @@ func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *ht // If no response were written, indicate it via an internal server error. if !written { - log.Debug("No response has been written by the handler") http.Error(w, "No response", http.StatusInternalServerError) return @@ -452,8 +454,6 @@ func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *ht // Write the response to the client err = h.writeResponse(req, resp, r, w) if err != nil { - log.Debug("[%d] Failed to write HTTP response: %v", req.Id, err) - // Try writing an error response just in case. http.Error(w, "Internal error", http.StatusInternalServerError) @@ -505,7 +505,6 @@ func (h *httpHandler) writeResponse( w.WriteHeader(http.StatusOK) // Write the actual response - log.Debug("[%d] Writing HTTP response", req.Id) _, err = w.Write(buf) return err @@ -520,7 +519,7 @@ func (s *ServerHTTPS) listenTLS(ctx context.Context) (err error) { } // Prepare the TLS configuration of the server. - tlsConf := s.conf.TLSConfDefault + tlsConf := s.tlsConfDefault if tlsConf == nil { return nil } @@ -533,7 +532,7 @@ func (s *ServerHTTPS) listenTLS(ctx context.Context) (err error) { // listenQUIC starts a QUIC listener that will be used to serve HTTP/3 requests. func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) { // Prepare the TLS configuration of the server. - tlsConf := s.conf.TLSConfH3 + tlsConf := s.tlsConfH3 conn, err := s.listenConfig.ListenPacket(ctx, "udp", s.addr) if err != nil { @@ -546,7 +545,7 @@ func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) { VerifySourceAddress: v.requiresValidation, } - qConf := newServerQUICConfig(s.conf.QUICLimitsEnabled, s.conf.MaxStreamsPerPeer) + qConf := newServerQUICConfig(s.quicLimitsEnabled, s.maxStreamsPerPeer) ql, err := transport.ListenEarly(tlsConf, qConf) if err != nil { return fmt.Errorf("listening quic: %w", err) @@ -596,10 +595,14 @@ func httpRequestToMsg(req *http.Request) (b []byte, err error) { } } -// httpRequestToMsgPost extracts the DNS message from a request body. +// httpRequestToMsgPost extracts the DNS message from a request body. req must +// not be nil. func httpRequestToMsgPost(req *http.Request) (b []byte, err error) { - buf, err := io.ReadAll(req.Body) - defer log.OnCloserError(req.Body, log.DEBUG) + // TODO(a.garipov): Make the limit configurable. + r := ioutil.LimitReader(req.Body, dns.MaxMsgSize) + buf, err := io.ReadAll(r) + err = errors.WithDeferred(err, req.Body.Close()) + return buf, err } diff --git a/internal/dnsserver/serverhttps_test.go b/internal/dnsserver/serverhttps_test.go index 53ace00..bf7f766 100644 --- a/internal/dnsserver/serverhttps_test.go +++ b/internal/dnsserver/serverhttps_test.go @@ -18,7 +18,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/golibs/httphdr" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/quic-go/quic-go" @@ -140,9 +139,12 @@ func TestServerHTTPS_integration_serveRequests(t *testing.T) { } func TestServerHTTPS_integration_nonDNSHandler(t *testing.T) { + errCh := make(chan error, 1) testHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("OK")) + + _, err := io.WriteString(w, "OK") + errCh <- err }) srv, err := dnsservertest.RunLocalHTTPSServer( @@ -156,11 +158,14 @@ func TestServerHTTPS_integration_nonDNSHandler(t *testing.T) { return srv.Shutdown(context.Background()) }) - var resp *http.Response - resp, err = http.Get(fmt.Sprintf("http://%s/test", srv.LocalTCPAddr())) - defer log.OnCloserError(resp.Body, log.DEBUG) + resp, err := http.Get(fmt.Sprintf("http://%s/test", srv.LocalTCPAddr())) require.NoError(t, err) + testutil.CleanupAndRequireSuccess(t, resp.Body.Close) require.Equal(t, http.StatusOK, resp.StatusCode) + + err, ok := testutil.RequireReceive(t, errCh, testTimeout) + require.True(t, ok) + require.NoError(t, err) } func TestDNSMsgToJSONMsg(t *testing.T) { @@ -404,7 +409,7 @@ func testDoH3Exchange( // Send the request and check the response. httpResp, err := client.Do(httpReq) require.NoError(t, err) - defer log.OnCloserError(httpResp.Body, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, httpResp.Body.Close) body, err := io.ReadAll(httpResp.Body) require.NoError(t, err) @@ -447,7 +452,7 @@ func mustDoHReq( httpResp, err := client.Do(httpReq) require.NoError(t, err) - defer log.OnCloserError(httpResp.Body, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, httpResp.Body.Close) if tlsConfig != nil && !httpResp.ProtoAtLeast(2, 0) { t.Fatal(fmt.Errorf("protocol is too old: %s", httpResp.Proto)) diff --git a/internal/dnsserver/serverquic.go b/internal/dnsserver/serverquic.go index c83c7b0..3f7d4ef 100644 --- a/internal/dnsserver/serverquic.go +++ b/internal/dnsserver/serverquic.go @@ -1,18 +1,20 @@ package dnsserver import ( + "cmp" "context" "crypto/tls" "encoding/binary" "fmt" "io" + "log/slog" "net" "sync" "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/syncutil" "github.com/bluele/gcache" "github.com/miekg/dns" @@ -74,10 +76,12 @@ type ConfigQUIC struct { // be set to [NextProtoDoQ]. TLSConfig *tls.Config - ConfigBase + // Base is the base configuration for this server. It must not be nil and + // must be valid. + Base *ConfigBase // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer - // is allowed to open. + // is allowed to open. If not set, 100 is used. MaxStreamsPerPeer int // QUICLimitsEnabled, if true, enables QUIC limiting. @@ -85,6 +89,8 @@ type ConfigQUIC struct { } // ServerQUIC is a DNS-over-QUIC server implementation. +// +// TODO(a.garipov): Consider unembedding ServerBase. type ServerQUIC struct { *ServerBase @@ -107,29 +113,35 @@ type ServerQUIC struct { // transport is the QUIC transport saved here to close it later. transport *quic.Transport - // TODO(a.garipov): Remove this and only save the values a server actually - // uses. - conf ConfigQUIC + tlsConf *tls.Config + + maxStreamsPerPeer int + + quicLimitsEnabled bool } // quicBytePoolSize is the size for the QUIC byte pools. const quicBytePoolSize = dns.MaxMsgSize -// NewServerQUIC creates a new ServerQUIC instance. -func NewServerQUIC(conf ConfigQUIC) (s *ServerQUIC) { - if conf.ListenConfig == nil { - // Do not enable OOB here as quic-go will do that on its own. - conf.ListenConfig = netext.DefaultListenConfig(nil) - } +// NewServerQUIC creates a new ServerQUIC instance. c must not be nil and must +// be valid. +func NewServerQUIC(c *ConfigQUIC) (s *ServerQUIC) { + // Do not enable OOB here as quic-go will do that on its own. + c.Base.ListenConfig = cmp.Or(c.Base.ListenConfig, netext.DefaultListenConfig(nil)) s = &ServerQUIC{ - ServerBase: newServerBase(ProtoDoQ, conf.ConfigBase), - pool: newPoolNonblocking(), + ServerBase: newServerBase(ProtoDoQ, c.Base), reqPool: syncutil.NewSlicePool[byte](quicBytePoolSize), respPool: syncutil.NewSlicePool[byte](quicBytePoolSize), - conf: conf, + tlsConf: c.TLSConfig, + // NOTE: 100 is the current default in package quic, but set it + // explicitly in case that changes in the future. + maxStreamsPerPeer: cmp.Or(c.MaxStreamsPerPeer, 100), + quicLimitsEnabled: c.QUICLimitsEnabled, } + s.pool = mustNewPoolNonblocking(s.baseLogger) + return s } @@ -143,15 +155,11 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) { s.mu.Lock() defer s.mu.Unlock() - if s.conf.TLSConfig == nil { - return errors.Error("tls config is required") - } - if s.started { return ErrServerAlreadyStarted } - log.Info("[%s]: Starting the server", s.name) + s.baseLogger.InfoContext(ctx, "starting server") ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, @@ -171,7 +179,7 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) { s.started = true - log.Info("[%s]: Server has been started", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been started") return nil } @@ -180,11 +188,11 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) { func (s *ServerQUIC) Shutdown(ctx context.Context) (err error) { defer func() { err = errors.Annotate(err, "shutting down doq server: %w") }() - log.Info("[%s]: Stopping the server", s.Name()) + s.baseLogger.InfoContext(ctx, "shutting down server") - err = s.shutdown() + err = s.shutdown(ctx) if err != nil { - log.Info("[%s]: Failed to shutdown: %v", s.Name(), err) + s.baseLogger.WarnContext(ctx, "error while shutting down", slogutil.KeyError, err) return err } @@ -194,13 +202,13 @@ func (s *ServerQUIC) Shutdown(ctx context.Context) (err error) { // Close the workerPool and releases all workers. s.pool.Release() - log.Info("[%s]: Finished stopping the server", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been shut down") return err } // shutdown marks the server as stopped and closes active listeners. -func (s *ServerQUIC) shutdown() (err error) { +func (s *ServerQUIC) shutdown(ctx context.Context) (err error) { s.mu.Lock() defer s.mu.Unlock() @@ -214,13 +222,13 @@ func (s *ServerQUIC) shutdown() (err error) { // Now close all listeners. err = s.quicListener.Close() if err != nil { - log.Debug("[%s]: closing quic listener: %s", s.Name(), err) + s.baseLogger.DebugContext(ctx, "closing quic listener", slogutil.KeyError, err) } // And the transport. err = s.transport.Close() if err != nil { - log.Debug("[%s]: closing quic transport: %s", s.Name(), err) + s.baseLogger.DebugContext(ctx, "closing quic transport", slogutil.KeyError, err) } return nil @@ -233,15 +241,11 @@ func (s *ServerQUIC) startServeQUIC(ctx context.Context) { defer s.handlePanicAndExit(ctx) defer s.wg.Done() - log.Info("[%s]: Start listening to quic://%s", s.Name(), s.LocalUDPAddr()) + s.baseLogger.InfoContext(ctx, "starting listening quic") + err := s.serveQUIC(ctx, s.quicListener) if err != nil { - log.Info( - "[%s]: Finished listening to quic://%s due to %v", - s.Name(), - s.LocalUDPAddr(), - err, - ) + s.baseLogger.WarnContext(ctx, "listening quic failed", slogutil.KeyError, err) } } @@ -299,7 +303,7 @@ func (s *ServerQUIC) acceptQUICConn( if err != nil { // Most likely the workerPool is closed, and we can exit right away. // Make sure that the connection is closed just in case. - closeQUICConn(conn, DOQCodeNoError) + s.closeQUICConn(ctx, conn, DOQCodeNoError) return err } @@ -321,7 +325,7 @@ func (s *ServerQUIC) serveQUICConnAsync( err := s.serveQUICConn(ctx, conn) if !isExpectedQUICErr(err) { s.metrics.OnError(ctx, err) - log.Debug("[%s] Error while serving a QUIC conn: %v", s.Name(), err) + s.baseLogger.DebugContext(ctx, "serving quic conn", slogutil.KeyError, err) } } @@ -334,7 +338,7 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn quic.Connection) (e streamWg.Wait() // Close the connection to make sure resources are freed. - closeQUICConn(conn, DOQCodeNoError) + s.closeQUICConn(ctx, conn, DOQCodeNoError) }() for s.isStarted() { @@ -372,7 +376,7 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn quic.Connection) (e TLSServerName: conn.ConnectionState().TLS.ServerName, } - reqCtx, reqCancel := s.requestContext() + reqCtx, reqCancel := s.requestContext(context.Background()) reqCtx = ContextWithRequestInfo(reqCtx, ri) streamWg.Add(1) @@ -409,7 +413,7 @@ func (s *ServerQUIC) serveQUICStreamAsync( err := s.serveQUICStream(ctx, stream, conn) if !isExpectedQUICErr(err) { s.metrics.OnError(ctx, err) - log.Debug("[%s] Failed to process a QUIC stream: %v", s.Name(), err) + s.baseLogger.DebugContext(ctx, "serving quic stream", slogutil.KeyError, err) } } @@ -423,11 +427,11 @@ func (s *ServerQUIC) serveQUICStream( // The server MUST send the response on the same stream, and MUST indicate // through the STREAM FIN mechanism that no further data will be sent on // that stream. - defer log.OnCloserError(stream, log.DEBUG) + defer slogutil.CloseAndLog(ctx, s.baseLogger, stream, slog.LevelDebug) msg, err := s.readQUICMsg(ctx, stream) if err != nil { - closeQUICConn(conn, DOQCodeProtocolError) + s.closeQUICConn(ctx, conn, DOQCodeProtocolError) return err } @@ -437,7 +441,7 @@ func (s *ServerQUIC) serveQUICStream( // fatal error. It SHOULD forcibly abort the connection using QUIC's // CONNECTION_CLOSE mechanism and SHOULD use the DoQ error code // DOQ_PROTOCOL_ERROR. - closeQUICConn(conn, DOQCodeProtocolError) + s.closeQUICConn(ctx, conn, DOQCodeProtocolError) return ErrProtocol } @@ -464,7 +468,7 @@ func (s *ServerQUIC) serveQUICStream( b, err := packWithPrefix(resp, *bufPtr) if err != nil { - closeQUICConn(conn, DOQCodeProtocolError) + s.closeQUICConn(ctx, conn, DOQCodeProtocolError) return err } @@ -570,8 +574,8 @@ func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) { VerifySourceAddress: v.requiresValidation, } - qConf := newServerQUICConfig(s.conf.QUICLimitsEnabled, s.conf.MaxStreamsPerPeer) - ql, err := transport.Listen(s.conf.TLSConfig, qConf) + qConf := newServerQUICConfig(s.quicLimitsEnabled, s.maxStreamsPerPeer) + ql, err := transport.Listen(s.tlsConf, qConf) if err != nil { return fmt.Errorf("listening quic: %w", err) } @@ -684,10 +688,14 @@ func validQUICMsg(req *dns.Msg) (ok bool) { // closeQUICConn quietly closes the QUIC connection with the specified error // code and logs if it fails to close the connection. -func closeQUICConn(conn quic.Connection, code quic.ApplicationErrorCode) { +func (s *ServerQUIC) closeQUICConn( + ctx context.Context, + conn quic.Connection, + code quic.ApplicationErrorCode, +) { err := conn.CloseWithError(code, "") if err != nil { - log.Debug("failed to close the QUIC connection: %v", err) + s.baseLogger.DebugContext(ctx, "closing quic conn", slogutil.KeyError, err) } } diff --git a/internal/dnsserver/serverquic_test.go b/internal/dnsserver/serverquic_test.go index f3c1a2b..e04e4ce 100644 --- a/internal/dnsserver/serverquic_test.go +++ b/internal/dnsserver/serverquic_test.go @@ -14,7 +14,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/quic-go/quic-go" @@ -219,7 +218,7 @@ func sendQUICMessage( return nil, fmt.Errorf("opening stream: %w", err) } - defer log.OnCloserError(stream, log.ERROR) + defer func() { err = errors.WithDeferred(err, stream.Close()) }() data, err := req.Pack() if err != nil { diff --git a/internal/dnsserver/servertls.go b/internal/dnsserver/servertls.go index ec68a75..93c7da6 100644 --- a/internal/dnsserver/servertls.go +++ b/internal/dnsserver/servertls.go @@ -5,35 +5,40 @@ import ( "crypto/tls" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" ) // ConfigTLS is a struct that needs to be passed to NewServerTLS to // initialize a new ServerTLS instance. type ConfigTLS struct { - // TLSConfig is the TLS configuration for TLS. + // TLSConfig is the TLS configuration for TLS. It must not be nil. TLSConfig *tls.Config - ConfigDNS + // DNS is the configuration for the underlying DNS server. It must not be + // nil and must be valid. + DNS *ConfigDNS } -// ServerTLS implements a DNS-over-TLS server. -// Note that it heavily relies on ServerDNS. +// ServerTLS implements a DNS-over-TLS server. Note that it heavily relies on +// ServerDNS. +// +// TODO(a.garipov): Consider unembedding ServerDNS. type ServerTLS struct { *ServerDNS - conf ConfigTLS + tlsConf *tls.Config } // type check var _ Server = (*ServerTLS)(nil) -// NewServerTLS creates a new ServerTLS instance. -func NewServerTLS(conf ConfigTLS) (s *ServerTLS) { - srv := newServerDNS(ProtoDoT, conf.ConfigDNS) +// NewServerTLS creates a new ServerTLS instance. c must not be nil and must be +// valid. +func NewServerTLS(c *ConfigTLS) (s *ServerTLS) { + srv := newServerDNS(ProtoDoT, c.DNS) s = &ServerTLS{ ServerDNS: srv, - conf: conf, + tlsConf: c.TLSConfig, } return s @@ -46,15 +51,11 @@ func (s *ServerTLS) Start(ctx context.Context) (err error) { s.mu.Lock() defer s.mu.Unlock() - if s.conf.TLSConfig == nil { - return errors.Error("tls config is required") - } - if s.started { return ErrServerAlreadyStarted } - log.Info("[%s]: Starting the server", s.name) + s.baseLogger.InfoContext(ctx, "starting server") ctx = ContextWithServerInfo(ctx, &ServerInfo{ Name: s.name, @@ -77,7 +78,7 @@ func (s *ServerTLS) Start(ctx context.Context) (err error) { // listeners are up. s.started = true - log.Info("[%s]: Server has been started", s.Name()) + s.baseLogger.InfoContext(ctx, "server has been started") return nil } @@ -95,10 +96,11 @@ func (s *ServerTLS) startServeTCP(ctx context.Context) { // the application won't be able to continue listening to DoT defer s.handlePanicAndExit(ctx) - log.Info("[%s]: Start listening to tls://%s", s.Name(), s.Addr()) + s.baseLogger.InfoContext(ctx, "starting listening tls") + err := s.serveTCP(ctx, s.tcpListener) if err != nil { - log.Info("[%s]: Finished listening to tls://%s due to %v", s.Name(), s.Addr(), err) + s.baseLogger.WarnContext(ctx, "listening tls failed", slogutil.KeyError, err) } } @@ -109,7 +111,7 @@ func (s *ServerTLS) listenTLS(ctx context.Context) (err error) { return err } - s.tcpListener = newTLSListener(l, s.conf.TLSConfig) + s.tcpListener = newTLSListener(l, s.tlsConf) return nil } diff --git a/internal/dnsserver/servertls_test.go b/internal/dnsserver/servertls_test.go index 16af47c..8acae89 100644 --- a/internal/dnsserver/servertls_test.go +++ b/internal/dnsserver/servertls_test.go @@ -10,7 +10,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/require" ) @@ -100,7 +100,7 @@ func TestServerTLS_integration_msgIgnore(t *testing.T) { conn, err := tls.Dial("tcp", addr.String(), tlsConfig) require.Nil(t, err) - defer log.OnCloserError(conn, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, conn.Close) // Write the invalid request _, err = conn.Write(tc.buf) @@ -161,7 +161,7 @@ func TestServerTLS_integration_queriesPipelining(t *testing.T) { conn, err := tls.Dial("tcp", addr.String(), tlsConfig) require.Nil(t, err) - defer log.OnCloserError(conn, log.DEBUG) + testutil.CleanupAndRequireSuccess(t, conn.Close) // Second - write multiple queries (let's say 100) and save // those queries IDs diff --git a/internal/dnsserver/workerpool.go b/internal/dnsserver/workerpool.go index b8b3827..00fbfca 100644 --- a/internal/dnsserver/workerpool.go +++ b/internal/dnsserver/workerpool.go @@ -1,37 +1,43 @@ package dnsserver import ( + "fmt" + "log/slog" "time" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/panjf2000/ants/v2" ) // antsLogger implements the [ants.Logger] interface and writes everything -// using golibs logger. -type antsLogger struct{} +// to its logger. +type antsLogger struct { + logger *slog.Logger +} // type check var _ ants.Logger = (*antsLogger)(nil) // Printf implements the [ants.Logger] interface for *antsLogger. func (l *antsLogger) Printf(format string, args ...interface{}) { - log.Info(format, args...) + l.logger.Info("ants pool", slogutil.KeyMessage, fmt.Sprintf(format, args...)) } -// newPoolNonblocking creates a new instance of [*ants.Pool] configured optimally -// for using it in DNS servers. -func newPoolNonblocking() (p *ants.Pool) { +// mustNewPoolNonblocking creates a new instance of [*ants.Pool] configured +// optimally for using it in DNS servers. It panics if there are errors. +// logger must not be nil. +func mustNewPoolNonblocking(logger *slog.Logger) (p *ants.Pool) { p, err := ants.NewPool(0, ants.WithOptions(ants.Options{ ExpiryDuration: time.Minute, PreAlloc: false, Nonblocking: true, DisablePurge: false, - Logger: &antsLogger{}, + Logger: &antsLogger{ + logger: logger, + }, })) - if err != nil { - log.Fatalf("failed to init goroutines workerPool: %v", err) - } + errors.Check(err) return p } diff --git a/internal/dnssvc/config.go b/internal/dnssvc/config.go index 1c01ad9..272c05c 100644 --- a/internal/dnssvc/config.go +++ b/internal/dnssvc/config.go @@ -17,6 +17,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" @@ -28,6 +29,10 @@ import ( // Config is the configuration of the AdGuard DNS service. type Config struct { + // BaseLogger is used to create loggers for the DNS listeners. It must not + // be nil. + BaseLogger *slog.Logger + // Handlers are the handlers to use in this DNS service. Handlers Handlers @@ -56,22 +61,27 @@ type Config struct { // NonDNS is the handler for non-DNS HTTP requests. It must not be nil. NonDNS http.Handler + // PrometheusRegisterer is used to register Prometheus metrics. It must not + // be nil. + PrometheusRegisterer prometheus.Registerer + // MetricsNamespace is a namespace for Prometheus metrics. It must be a // valid Prometheus metric label. MetricsNamespace string // ServerGroups are the DNS server groups. Each element must be non-nil. - ServerGroups []*agd.ServerGroup + ServerGroups []*ServerGroupConfig // HandleTimeout defines the timeout for the entire handling of a single // query. It must be greater than zero. HandleTimeout time.Duration } -// NewListenerFunc is the type for DNS listener constructors. +// NewListenerFunc is the type for DNS listener constructors. All arguments +// must not be nil. type NewListenerFunc func( srv *agd.Server, - baseConf dnsserver.ConfigBase, + baseConf *dnsserver.ConfigBase, nonDNS http.Handler, ) (l Listener, err error) @@ -142,6 +152,9 @@ type HandlersConfig struct { // Handler is the ultimate handler of the DNS query to be wrapped by // middlewares. It must not be nil. + // + // TODO(a.garipov): Use the logger from the context throughout the + // handling. Handler dnsserver.Handler // HashMatcher is the safe-browsing hash matcher for TXT queries. It must @@ -176,8 +189,8 @@ type HandlersConfig struct { FilteringGroups map[agd.FilteringGroupID]*agd.FilteringGroup // ServerGroups are the DNS server groups for which to build handlers. Each - // element and its servers must be non-nil. - ServerGroups []*agd.ServerGroup + // server group and its servers must be valid and non-nil. + ServerGroups []*ServerGroupConfig // EDEEnabled enables the addition of the Extended DNS Error (EDE) codes in // the profiles' message constructors. @@ -192,7 +205,7 @@ type Handlers map[HandlerKey]dnsserver.Handler // HandlerKey is a key for the [Handlers] map. type HandlerKey struct { Server *agd.Server - ServerGroup *agd.ServerGroup + ServerGroup *ServerGroupConfig } // CacheConfig is the configuration for the DNS cache. @@ -226,3 +239,37 @@ const ( CacheTypeSimple CacheTypeECS ) + +// ServerGroupConfig is the configuration for a group of DNS servers all of +// which use the same filtering settings. +type ServerGroupConfig struct { + // DDR is the configuration for the server group's Discovery Of Designated + // Resolvers (DDR) handlers. DDR must not be nil. + DDR *DDRConfig + + // DeviceDomains is the list of domain names used to detect device IDs from + // clients' server names. + DeviceDomains []string + + // Name is the unique name of the server group. + Name agd.ServerGroupName + + // FilteringGroup is the ID of the filtering group for this server group. + FilteringGroup agd.FilteringGroupID + + // Servers are the settings for servers. Each element must be non-nil. + // + // TODO(a.garipov): Move servers here as well as ServerConfig. + Servers []*agd.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 + +// DDRConfig is the configuration for the server group's Discovery Of Designated +// Resolvers (DDR) handlers. +type DDRConfig = initial.DDRConfig diff --git a/internal/dnssvc/context.go b/internal/dnssvc/context.go index 3e054bc..05f9171 100644 --- a/internal/dnssvc/context.go +++ b/internal/dnssvc/context.go @@ -5,11 +5,11 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/golibs/contextutil" ) -// contextConstructor is a [dnsserver.ContextConstructor] implementation that -// returns a context with the given timeout as well as a new [agd.RequestID]. +// contextConstructor is a [contextutil.Constructor] implementation that returns +// a context with the given timeout as well as a new [agd.RequestID]. type contextConstructor struct { timeout time.Duration } @@ -22,13 +22,15 @@ func newContextConstructor(timeout time.Duration) (c *contextConstructor) { } // type check -var _ dnsserver.ContextConstructor = (*contextConstructor)(nil) +var _ contextutil.Constructor = (*contextConstructor)(nil) -// New implements the [dnsserver.ContextConstructor] interface for +// New implements the [contextutil.Constructor] interface for // *contextConstructor. It returns a context with a new [agd.RequestID] as well // as its timeout and the corresponding cancelation function. -func (c *contextConstructor) New() (ctx context.Context, cancel context.CancelFunc) { - ctx, cancel = context.WithTimeout(context.Background(), c.timeout) +func (c *contextConstructor) New( + parent context.Context, +) (ctx context.Context, cancel context.CancelFunc) { + ctx, cancel = context.WithTimeout(parent, c.timeout) ctx = agd.WithRequestID(ctx, agd.NewRequestID()) return ctx, cancel diff --git a/internal/dnssvc/dnssvc.go b/internal/dnssvc/dnssvc.go index 66c7661..28c4aeb 100644 --- a/internal/dnssvc/dnssvc.go +++ b/internal/dnssvc/dnssvc.go @@ -15,6 +15,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" dnssrvprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/service" "github.com/miekg/dns" ) @@ -56,9 +57,17 @@ func New(c *Config) (svc *Service, err error) { newListener = NewListener } + mtrcListener, err := dnssrvprom.NewServerMetricsListener( + c.MetricsNamespace, + c.PrometheusRegisterer, + ) + if err != nil { + return nil, fmt.Errorf("metrics listener: %w", err) + } + errCollListener := &errCollMetricsListener{ errColl: c.ErrColl, - baseListener: dnssrvprom.NewServerMetricsListener(c.MetricsNamespace), + baseListener: mtrcListener, } // Configure the service itself. @@ -87,7 +96,7 @@ func New(c *Config) (svc *Service, err error) { // newServers creates a slice of servers. func newServers( c *Config, - srvGrp *agd.ServerGroup, + srvGrp *ServerGroupConfig, errCollListener *errCollMetricsListener, newListener NewListenerFunc, ) (servers []*server, err error) { @@ -140,7 +149,15 @@ func newListeners( proto := srv.Protocol name := listenerName(srv.Name, addr, proto) - baseConf := dnsserver.ConfigBase{ + baseConf := &dnsserver.ConfigBase{ + // TODO(a.garipov): Consider making servers add the address instead + // of module users doing that. Including the correct handling of + // addresses with zero port. + BaseLogger: c.BaseLogger.With( + "listener_addr", addr, + "listener_name", name, + slogutil.KeyPrefix, "dnsserver", + ), Network: dnsserver.NetworkAny, Handler: handler, Metrics: errCollListener, @@ -152,7 +169,6 @@ func newListeners( c.ConnLimiter, proto, ), - Name: name, Addr: addr, } @@ -318,7 +334,7 @@ func (svc *Service) Handle( // TODO(a.garipov): Replace this in tests with [netext.ListenConfig]. func NewListener( s *agd.Server, - baseConf dnsserver.ConfigBase, + baseConf *dnsserver.ConfigBase, nonDNS http.Handler, ) (l Listener, err error) { defer func() { err = errors.Annotate(err, "listener %q: %w", baseConf.Name) }() @@ -328,8 +344,8 @@ func NewListener( switch p := s.Protocol; p { case agd.ProtoDNS: udpConf := s.UDPConf - l = dnsserver.NewServerDNS(dnsserver.ConfigDNS{ - ConfigBase: baseConf, + l = dnsserver.NewServerDNS(&dnsserver.ConfigDNS{ + Base: baseConf, ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, MaxUDPRespSize: udpConf.MaxRespSize, @@ -339,14 +355,14 @@ func NewListener( }) case agd.ProtoDNSCrypt: dcConf := s.DNSCrypt - l = dnsserver.NewServerDNSCrypt(dnsserver.ConfigDNSCrypt{ - ConfigBase: baseConf, - DNSCryptProviderName: dcConf.ProviderName, - DNSCryptResolverCert: dcConf.Cert, + l = dnsserver.NewServerDNSCrypt(&dnsserver.ConfigDNSCrypt{ + Base: baseConf, + ProviderName: dcConf.ProviderName, + ResolverCert: dcConf.Cert, }) case agd.ProtoDoH: - l = dnsserver.NewServerHTTPS(dnsserver.ConfigHTTPS{ - ConfigBase: baseConf, + l = dnsserver.NewServerHTTPS(&dnsserver.ConfigHTTPS{ + Base: baseConf, TLSConfDefault: s.TLS.Default, TLSConfH3: s.TLS.H3, NonDNSHandler: nonDNS, @@ -354,16 +370,16 @@ func NewListener( QUICLimitsEnabled: quicConf.QUICLimitsEnabled, }) case agd.ProtoDoQ: - l = dnsserver.NewServerQUIC(dnsserver.ConfigQUIC{ + l = dnsserver.NewServerQUIC(&dnsserver.ConfigQUIC{ TLSConfig: s.TLS.Default, - ConfigBase: baseConf, + Base: baseConf, MaxStreamsPerPeer: quicConf.MaxStreamsPerPeer, QUICLimitsEnabled: quicConf.QUICLimitsEnabled, }) case agd.ProtoDoT: - l = dnsserver.NewServerTLS(dnsserver.ConfigTLS{ - ConfigDNS: dnsserver.ConfigDNS{ - ConfigBase: baseConf, + l = dnsserver.NewServerTLS(&dnsserver.ConfigTLS{ + DNS: &dnsserver.ConfigDNS{ + Base: baseConf, ReadTimeout: s.ReadTimeout, WriteTimeout: s.WriteTimeout, MaxPipelineEnabled: tcpConf.MaxPipelineEnabled, diff --git a/internal/dnssvc/dnssvc_test.go b/internal/dnssvc/dnssvc_test.go index 6c91802..687b34b 100644 --- a/internal/dnssvc/dnssvc_test.go +++ b/internal/dnssvc/dnssvc_test.go @@ -9,20 +9,20 @@ import ( "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// type check -var _ agdservice.Refresher = (*forward.Handler)(nil) +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() // testListener is a [dnssvc.Listener] for tests. type testListener struct { @@ -99,7 +99,7 @@ func newTestListener() (tl *testListener) { func newTestListenerFunc(tl *testListener) (f dnssvc.NewListenerFunc) { return func( _ *agd.Server, - _ dnsserver.ConfigBase, + _ *dnsserver.ConfigBase, _ http.Handler, ) (l dnssvc.Listener, err error) { return tl, nil @@ -150,7 +150,7 @@ func TestService_Start(t *testing.T) { AddrPort: netip.MustParseAddrPort("127.0.0.1:53"), }) - srvGrp := &agd.ServerGroup{ + srvGrp := &dnssvc.ServerGroupConfig{ Name: dnssvctest.ServerGroupName, Servers: []*agd.Server{srv}, } @@ -161,12 +161,14 @@ func TestService_Start(t *testing.T) { } c := &dnssvc.Config{ + BaseLogger: testLogger, NewListener: newTestListenerFunc(tl), Handlers: dnssvc.Handlers{ k: dnsservertest.NewDefaultHandler(), }, - MetricsNamespace: "test_start", - ServerGroups: []*agd.ServerGroup{srvGrp}, + PrometheusRegisterer: prometheus.NewRegistry(), + MetricsNamespace: "test_start", + ServerGroups: []*dnssvc.ServerGroupConfig{srvGrp}, } svc, err := dnssvc.New(c) @@ -204,7 +206,7 @@ func TestNew(t *testing.T) { }), } - srvGrp := &agd.ServerGroup{ + srvGrp := &dnssvc.ServerGroupConfig{ Name: dnssvctest.ServerGroupName, Servers: srvs, } @@ -220,9 +222,11 @@ func TestNew(t *testing.T) { } c := &dnssvc.Config{ - Handlers: handlers, - MetricsNamespace: "test_new", - ServerGroups: []*agd.ServerGroup{srvGrp}, + BaseLogger: testLogger, + Handlers: handlers, + PrometheusRegisterer: prometheus.NewRegistry(), + MetricsNamespace: "test_new", + ServerGroups: []*dnssvc.ServerGroupConfig{srvGrp}, } svc, err := dnssvc.New(c) diff --git a/internal/dnssvc/handler.go b/internal/dnssvc/handler.go index 0862e20..2c833d9 100644 --- a/internal/dnssvc/handler.go +++ b/internal/dnssvc/handler.go @@ -15,15 +15,21 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/preupstream" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/ratelimitmw" "github.com/AdguardTeam/AdGuardDNS/internal/ecscache" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/logutil/slogutil" + + // TODO(e.burkov): Move registering of the metrics to another package to + // avoid dependency on the metrics package. + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" ) // NewHandlers returns the main DNS handlers wrapped in all necessary // middlewares. c must not be nil. func NewHandlers(ctx context.Context, c *HandlersConfig) (handlers Handlers, err error) { - handler := wrapPreUpstreamMw(ctx, c) + handler, err := wrapPreUpstreamMw(ctx, c) + if err != nil { + return nil, fmt.Errorf("wrapping pre-upstream middleware: %w", err) + } mainMwMtrc, err := newMainMiddlewareMetrics(c) if err != nil { @@ -60,12 +66,6 @@ func NewHandlers(ctx context.Context, c *HandlersConfig) (handlers Handlers, err handler = postInitMw.Wrap(handler) } - initMw := initial.New(&initial.Config{ - Logger: c.BaseLogger.With(slogutil.KeyPrefix, "initmw"), - }) - - handler = initMw.Wrap(handler) - return newHandlersForServers(c, handler) } @@ -74,7 +74,10 @@ func NewHandlers(ctx context.Context, c *HandlersConfig) (handlers Handlers, err // // TODO(a.garipov): Adapt the cache tests that previously were in package // preupstream. -func wrapPreUpstreamMw(ctx context.Context, c *HandlersConfig) (wrapped dnsserver.Handler) { +func wrapPreUpstreamMw( + ctx context.Context, + c *HandlersConfig, +) (wrapped dnsserver.Handler, err error) { // TODO(a.garipov): Use in other places if necessary. l := c.BaseLogger.With(slogutil.KeyPrefix, "dnssvc") @@ -85,9 +88,18 @@ func wrapPreUpstreamMw(ctx context.Context, c *HandlersConfig) (wrapped dnsserve case CacheTypeSimple: l.InfoContext(ctx, "plain cache enabled", "count", conf.NoECSCount) + var mtrcListener *dnssrvprom.CacheMetricsListener + mtrcListener, err = dnssrvprom.NewCacheMetricsListener( + c.MetricsNamespace, + c.PrometheusRegisterer, + ) + if err != nil { + return nil, fmt.Errorf("registering cache metrics: %w", err) + } + cacheMw := cache.NewMiddleware(&cache.MiddlewareConfig{ - // TODO(a.garipov): Do not use promauto and refactor. - MetricsListener: dnssrvprom.NewCacheMetricsListener(metrics.Namespace()), + Logger: c.BaseLogger.With(slogutil.KeyPrefix, "cache"), + MetricsListener: mtrcListener, Count: conf.NoECSCount, MinTTL: conf.MinTTL, OverrideTTL: conf.OverrideCacheTTL, @@ -102,7 +114,14 @@ func wrapPreUpstreamMw(ctx context.Context, c *HandlersConfig) (wrapped dnsserve "no_ecs_count", conf.NoECSCount, ) + var mtrc ecscache.Metrics + mtrc, err = metrics.NewECSCache(c.MetricsNamespace, c.PrometheusRegisterer) + if err != nil { + return nil, fmt.Errorf("registering ecs cache metrics: %w", err) + } + cacheMw := ecscache.NewMiddleware(&ecscache.MiddlewareConfig{ + Metrics: mtrc, Cloner: c.Cloner, Logger: c.BaseLogger.With(slogutil.KeyPrefix, "ecscache"), CacheManager: c.CacheManager, @@ -124,7 +143,7 @@ func wrapPreUpstreamMw(ctx context.Context, c *HandlersConfig) (wrapped dnsserve wrapped = preUps.Wrap(wrapped) - return wrapped + return wrapped, nil } // newMainMiddlewareMetrics returns a filtering-middleware metrics @@ -135,7 +154,11 @@ func newMainMiddlewareMetrics(c *HandlersConfig) (mainMwMtrc MainMiddlewareMetri return mainMwMtrc, nil } - mainMwMtrc, err = metrics.NewDefaultMainMiddleware(c.MetricsNamespace, c.PrometheusRegisterer) + mainMwMtrc, err = metrics.NewDefaultMainMiddleware( + c.BaseLogger.With(slogutil.KeyPrefix, "mainmw_metrics"), + c.MetricsNamespace, + c.PrometheusRegisterer, + ) if err != nil { return nil, fmt.Errorf("mainmw metrics: %w", err) } @@ -145,7 +168,7 @@ func newMainMiddlewareMetrics(c *HandlersConfig) (mainMwMtrc MainMiddlewareMetri // newHandlersForServers returns a handler map for each server group and each // server. -func newHandlersForServers(c *HandlersConfig, h dnsserver.Handler) (handlers Handlers, err error) { +func newHandlersForServers(c *HandlersConfig, handler dnsserver.Handler) (handlers Handlers, err error) { rlMwMtrc, err := metrics.NewDefaultRatelimitMiddleware( c.MetricsNamespace, c.PrometheusRegisterer, @@ -167,13 +190,27 @@ func newHandlersForServers(c *HandlersConfig, h dnsserver.Handler) (handlers Han ) } + initMw := initial.New(&initial.Config{ + Logger: c.BaseLogger.With(slogutil.KeyPrefix, "initmw"), + DDR: srvGrp.DDR, + }) + + srvGrpHandler := initMw.Wrap(handler) + for _, srv := range srvGrp.Servers { + srvInfo := &agd.RequestServerInfo{ + GroupName: srvGrp.Name, + Name: srv.Name, + DeviceDomains: srvGrp.DeviceDomains, + Protocol: srv.Protocol, + ProfilesEnabled: srvGrp.ProfilesEnabled, + } + rlMw := ratelimitmw.New(&ratelimitmw.Config{ Logger: rlMwLogger, Messages: c.Messages, FilteringGroup: fltGrp, - ServerGroup: srvGrp, - Server: srv, + ServerInfo: srvInfo, StructuredErrors: c.StructuredErrors, AccessManager: c.AccessManager, DeviceFinder: newDeviceFinder(c, srvGrp, srv), @@ -190,7 +227,7 @@ func newHandlersForServers(c *HandlersConfig, h dnsserver.Handler) (handlers Han ServerGroup: srvGrp, } - handlers[k] = rlMw.Wrap(h) + handlers[k] = rlMw.Wrap(srvGrpHandler) } } @@ -199,7 +236,7 @@ func newHandlersForServers(c *HandlersConfig, h dnsserver.Handler) (handlers Han // newDeviceFinder returns a new agd.DeviceFinder for a server based on the // configuration. All arguments must not be nil. -func newDeviceFinder(c *HandlersConfig, g *agd.ServerGroup, s *agd.Server) (df agd.DeviceFinder) { +func newDeviceFinder(c *HandlersConfig, g *ServerGroupConfig, s *agd.Server) (df agd.DeviceFinder) { if !g.ProfilesEnabled { return agd.EmptyDeviceFinder{} } diff --git a/internal/dnssvc/handler_test.go b/internal/dnssvc/handler_test.go index d7e5df4..8ffb83a 100644 --- a/internal/dnssvc/handler_test.go +++ b/internal/dnssvc/handler_test.go @@ -16,7 +16,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/querylog" - "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -108,8 +107,8 @@ func TestNewHandlers(t *testing.T) { AddrPort: dnssvctest.ServerAddrPort, }) - srvGrp := &agd.ServerGroup{ - DDR: &agd.DDR{ + srvGrp := &dnssvc.ServerGroupConfig{ + DDR: &dnssvc.DDRConfig{ Enabled: true, }, Name: dnssvctest.ServerGroupName, @@ -151,7 +150,7 @@ func TestNewHandlers(t *testing.T) { ctx := testutil.ContextWithTimeout(t, dnssvctest.Timeout) handlers, err := dnssvc.NewHandlers(ctx, &dnssvc.HandlersConfig{ - BaseLogger: slogutil.NewDiscardLogger(), + BaseLogger: testLogger, Cloner: agdtest.NewCloner(), Cache: tc.cacheConf, HumanIDParser: agd.NewHumanIDParser(), @@ -176,7 +175,7 @@ func TestNewHandlers(t *testing.T) { RuleStat: ruleStat, MetricsNamespace: path.Base(t.Name()), FilteringGroups: fltGrps, - ServerGroups: []*agd.ServerGroup{srvGrp}, + ServerGroups: []*dnssvc.ServerGroupConfig{srvGrp}, EDEEnabled: true, }) require.NoError(t, err) diff --git a/internal/dnssvc/integration_test.go b/internal/dnssvc/integration_test.go index eae2d60..80953b2 100644 --- a/internal/dnssvc/integration_test.go +++ b/internal/dnssvc/integration_test.go @@ -23,10 +23,10 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/querylog" - "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -186,8 +186,8 @@ func newTestService( return true, false, nil } - srvGrps := []*agd.ServerGroup{{ - DDR: &agd.DDR{ + srvGrps := []*dnssvc.ServerGroupConfig{{ + DDR: &dnssvc.DDRConfig{ Enabled: true, }, DeviceDomains: []string{dnssvctest.DomainForDevices}, @@ -210,7 +210,7 @@ func newTestService( } hdlrConf := &dnssvc.HandlersConfig{ - BaseLogger: slogutil.NewDiscardLogger(), + BaseLogger: testLogger, Cache: &dnssvc.CacheConfig{ Type: dnssvc.CacheTypeNone, }, @@ -256,14 +256,16 @@ func newTestService( require.NoError(t, err) c := &dnssvc.Config{ - Handlers: handlers, - NewListener: newTestListenerFunc(tl), - Cloner: agdtest.NewCloner(), - ErrColl: errColl, - NonDNS: http.NotFoundHandler(), - MetricsNamespace: path.Base(t.Name()), - ServerGroups: srvGrps, - HandleTimeout: dnssvctest.Timeout, + BaseLogger: testLogger, + Handlers: handlers, + NewListener: newTestListenerFunc(tl), + Cloner: agdtest.NewCloner(), + ErrColl: errColl, + NonDNS: http.NotFoundHandler(), + PrometheusRegisterer: prometheus.NewRegistry(), + MetricsNamespace: path.Base(t.Name()), + ServerGroups: srvGrps, + HandleTimeout: dnssvctest.Timeout, } svc, err = dnssvc.New(c) diff --git a/internal/dnssvc/internal/initial/initial.go b/internal/dnssvc/internal/initial/initial.go index 39ad44b..3db10c0 100644 --- a/internal/dnssvc/internal/initial/initial.go +++ b/internal/dnssvc/internal/initial/initial.go @@ -16,6 +16,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal" "github.com/AdguardTeam/AdGuardDNS/internal/optslog" + "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" "github.com/miekg/dns" ) @@ -25,6 +26,7 @@ import ( // middleware. type Middleware struct { logger *slog.Logger + ddr *DDRConfig } // Config is the configuration structure for the initial middleware. All fields @@ -32,6 +34,34 @@ type Middleware struct { type Config struct { // Logger is used to log the operation of the middleware. Logger *slog.Logger + + // DDR is the configuration for the server group's Discovery Of Designated + // Resolvers (DDR) handlers. It must not be nil. + DDR *DDRConfig +} + +// DDRConfig is the configuration for the server group's Discovery Of Designated +// Resolvers (DDR) handlers. +type DDRConfig 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 } // New returns a new initial middleware. c must not be nil, and all its fields @@ -39,6 +69,7 @@ type Config struct { func New(c *Config) (mw *Middleware) { return &Middleware{ logger: c.Logger, + ddr: c.DDR, } } diff --git a/internal/dnssvc/internal/initial/specialdomain.go b/internal/dnssvc/internal/initial/specialdomain.go index 7f57960..49970e7 100644 --- a/internal/dnssvc/internal/initial/specialdomain.go +++ b/internal/dnssvc/internal/initial/specialdomain.go @@ -116,18 +116,17 @@ func (mw *Middleware) isDDRRequest(ri *agd.RequestInfo) (ok bool) { return true } - return isDDRDomain(ri, host) + return mw.isDDRDomain(ri, host) } // isDDRDomain returns true if host is a DDR domain. -func isDDRDomain(ri *agd.RequestInfo, host string) (ok bool) { +func (mw *Middleware) isDDRDomain(ri *agd.RequestInfo, host string) (ok bool) { firstLabel, resolverDomain, cut := strings.Cut(host, ".") if !cut || firstLabel != DDRLabel { return false } - ddr := ri.ServerGroup.DDR - if ddr.PublicTargets.Has(resolverDomain) { + if mw.ddr.PublicTargets.Has(resolverDomain) { // The client may simply send a DNS SVCB query using the known name of // the resolver. This query can be issued to the named Encrypted // Resolver itself or to any other resolver. Unlike the case of @@ -144,7 +143,7 @@ func isDDRDomain(ri *agd.RequestInfo, host string) (ok bool) { firstLabel, resolverDomain, cut = strings.Cut(resolverDomain, ".") if cut && firstLabel == string(dev.ID) { // A request for the device ID resolver domain. - return ddr.DeviceTargets.Has(resolverDomain) + return mw.ddr.DeviceTargets.Has(resolverDomain) } return false @@ -162,7 +161,7 @@ func (mw *Middleware) handleDDR( metrics.DNSSvcDDRRequestsTotal.Inc() - if ri.ServerGroup.DDR.Enabled { + if mw.ddr.Enabled { return rw.WriteMsg(ctx, req, mw.newRespDDR(req, ri)) } @@ -181,7 +180,7 @@ func (mw *Middleware) handleDDRNoData( metrics.DNSSvcDDRRequestsTotal.Inc() - if ri.ServerGroup.DDR.Enabled { + if mw.ddr.Enabled { return rw.WriteMsg(ctx, req, ri.Messages.NewRespRCode(req, dns.RcodeSuccess)) } @@ -194,11 +193,10 @@ func (mw *Middleware) handleDDRNoData( func (mw *Middleware) newRespDDR(req *dns.Msg, ri *agd.RequestInfo) (resp *dns.Msg) { resp = ri.Messages.NewResp(req) name := req.Question[0].Name - ddr := ri.ServerGroup.DDR // TODO(a.garipov): Optimize calls to ri.DeviceData. if _, dev := ri.DeviceData(); dev != nil { - for _, rr := range ddr.DeviceRecordTemplates { + for _, rr := range mw.ddr.DeviceRecordTemplates { rr = dns.Copy(rr).(*dns.SVCB) rr.Hdr.Name = name rr.Target = string(dev.ID) + "." + rr.Target @@ -209,7 +207,7 @@ func (mw *Middleware) newRespDDR(req *dns.Msg, ri *agd.RequestInfo) (resp *dns.M return resp } - for _, rr := range ddr.PublicRecordTemplates { + for _, rr := range mw.ddr.PublicRecordTemplates { rr = dns.Copy(rr).(*dns.SVCB) rr.Hdr.Name = name diff --git a/internal/dnssvc/internal/initial/specialdomain_test.go b/internal/dnssvc/internal/initial/specialdomain_test.go index d37ac01..db6e407 100644 --- a/internal/dnssvc/internal/initial/specialdomain_test.go +++ b/internal/dnssvc/internal/initial/specialdomain_test.go @@ -123,6 +123,9 @@ func TestMiddleware_Wrap_specialDomain(t *testing.T) { t.Run(tc.name, func(t *testing.T) { mw := initial.New(&initial.Config{ Logger: slogutil.NewDiscardLogger(), + DDR: &initial.DDRConfig{ + Enabled: false, + }, }) h := mw.Wrap(newSpecDomHandler(tc.wantRCode == dns.RcodeSuccess)) @@ -163,7 +166,6 @@ func newSpecDomReqInfo( ri = &agd.RequestInfo{ Messages: agdtest.NewConstructor(tb), - ServerGroup: &agd.ServerGroup{}, FilteringGroup: fltGrp, Host: host, QClass: dns.ClassINET, diff --git a/internal/dnssvc/internal/mainmw/error.go b/internal/dnssvc/internal/mainmw/error.go index d1f079a..187c2b3 100644 --- a/internal/dnssvc/internal/mainmw/error.go +++ b/internal/dnssvc/internal/mainmw/error.go @@ -36,5 +36,6 @@ var _ errcoll.SentryReportableError = afterFilteringError{} // IsSentryReportable implements the [errcoll.SentryReportableError] interface // for afterFilteringError. func (err afterFilteringError) IsSentryReportable() (ok bool) { - return !errors.Is(err.err, context.DeadlineExceeded) + return !errors.Is(err.err, context.DeadlineExceeded) && + !errors.Is(err.err, context.Canceled) } diff --git a/internal/dnssvc/internal/mainmw/mainmw_test.go b/internal/dnssvc/internal/mainmw/mainmw_test.go index 57726dd..8353a5f 100644 --- a/internal/dnssvc/internal/mainmw/mainmw_test.go +++ b/internal/dnssvc/internal/mainmw/mainmw_test.go @@ -358,11 +358,13 @@ func newContext( FilteringGroup: &agd.FilteringGroup{ FilterConfig: fltConf, }, + ServerInfo: &agd.RequestServerInfo{ + Protocol: testProto, + }, Messages: agdtest.NewConstructor(tb), RemoteIP: dnssvctest.ClientAddr, Host: host, QType: qType, - Proto: testProto, }) return ctx diff --git a/internal/dnssvc/internal/mainmw/record.go b/internal/dnssvc/internal/mainmw/record.go index 247f4ed..690a102 100644 --- a/internal/dnssvc/internal/mainmw/record.go +++ b/internal/dnssvc/internal/mainmw/record.go @@ -45,7 +45,7 @@ func (mw *Middleware) recordQueryInfo( reqInfo := dnsserver.MustRequestInfoFromContext(ctx) start := reqInfo.StartTime - mw.billStat.Record(ctx, devID, reqCtry, reqASN, start, ri.Proto) + mw.billStat.Record(ctx, devID, reqCtry, reqASN, start, ri.ServerInfo.Protocol) if !prof.QueryLogEnabled { return @@ -80,7 +80,7 @@ func (mw *Middleware) recordQueryInfo( ClientASN: reqASN, RequestType: ri.QType, ResponseCode: rcode, - Protocol: ri.Proto, + Protocol: ri.ServerInfo.Protocol, DNSSEC: respDNSSEC, RemoteIP: clientIP, } diff --git a/internal/dnssvc/internal/mainmw/record_internal_test.go b/internal/dnssvc/internal/mainmw/record_internal_test.go index 7bcadb7..87d0a80 100644 --- a/internal/dnssvc/internal/mainmw/record_internal_test.go +++ b/internal/dnssvc/internal/mainmw/record_internal_test.go @@ -184,6 +184,13 @@ func TestMiddleware_recordQueryInfo_respCtry(t *testing.T) { QueryLogEnabled: true, }, }, + ServerInfo: &agd.RequestServerInfo{ + GroupName: dnssvctest.ServerGroupName, + Name: dnssvctest.ServerName, + DeviceDomains: []string{dnssvctest.DomainForDevices}, + Protocol: agd.ProtoDoT, + ProfilesEnabled: true, + }, QType: tc.req.Question[0].Qtype, QClass: class, } diff --git a/internal/dnssvc/internal/ratelimitmw/access_test.go b/internal/dnssvc/internal/ratelimitmw/access_test.go index dc5e798..2852562 100644 --- a/internal/dnssvc/internal/ratelimitmw/access_test.go +++ b/internal/dnssvc/internal/ratelimitmw/access_test.go @@ -64,10 +64,13 @@ func TestMiddleware_Wrap_access(t *testing.T) { Logger: slogutil.NewDiscardLogger(), Messages: agdtest.NewConstructor(t), FilteringGroup: &agd.FilteringGroup{}, - ServerGroup: &agd.ServerGroup{}, - Server: &agd.Server{ + ServerInfo: &agd.RequestServerInfo{ + GroupName: dnssvctest.ServerGroupName, + Name: dnssvctest.ServerName, + DeviceDomains: []string{dnssvctest.DomainForDevices}, // Use a DoT server to prevent ratelimiting. - Protocol: agd.ProtoDoT, + Protocol: agd.ProtoDoT, + ProfilesEnabled: true, }, StructuredErrors: agdtest.NewSDEConfig(true), AccessManager: accessMgr, diff --git a/internal/dnssvc/internal/ratelimitmw/limit.go b/internal/dnssvc/internal/ratelimitmw/limit.go index d6f24e9..9d809eb 100644 --- a/internal/dnssvc/internal/ratelimitmw/limit.go +++ b/internal/dnssvc/internal/ratelimitmw/limit.go @@ -21,7 +21,7 @@ func (mw *Middleware) serveWithRatelimiting( ri *agd.RequestInfo, next dnsserver.Handler, ) (err error) { - if !slices.Contains(mw.protos, ri.Proto) { + if !slices.Contains(mw.protos, ri.ServerInfo.Protocol) { return next.ServeDNS(ctx, rw, req) } diff --git a/internal/dnssvc/internal/ratelimitmw/ratelimitmw.go b/internal/dnssvc/internal/ratelimitmw/ratelimitmw.go index 338dfad..b7f8239 100644 --- a/internal/dnssvc/internal/ratelimitmw/ratelimitmw.go +++ b/internal/dnssvc/internal/ratelimitmw/ratelimitmw.go @@ -48,41 +48,46 @@ type Middleware struct { // Config is the configuration structure for the access and ratelimiting // middleware. All fields must not be empty. type Config struct { - // Logger is used to log the operation of the middleware. + // Logger is used to log the operation of the middleware. It must not be + // nil. Logger *slog.Logger // Messages is used to build the responses specific for a request's context. + // It must not be nil. Messages *dnsmsg.Constructor // FilteringGroup is the filtering group to which [Config.Server] belongs. + // It must not be nil. FilteringGroup *agd.FilteringGroup - // ServerGroup is the server group to which [Config.Server] belongs. - ServerGroup *agd.ServerGroup - - // Server is the current server which serves the request. - Server *agd.Server + // ServerInfo contains the information about the server processing the + // queries and its server group. It must not be nil. + ServerInfo *agd.RequestServerInfo // StructuredErrors is the configuration for the experimental Structured DNS - // Errors feature for the profiles' message constructors. + // Errors feature for the profiles' message constructors. It must not be + // nil. StructuredErrors *dnsmsg.StructuredDNSErrorsConfig - // AccessManager is the global access manager. + // AccessManager is the global access manager. It must not be nil. AccessManager access.Interface // DeviceFinder is used to set the device and profile for a request, if any. + // If [Config.ServerInfo.ProfilesEnabled] is true, it must not be nil. DeviceFinder agd.DeviceFinder - // ErrColl collects and reports the errors considered non-critical. + // ErrColl collects and reports the errors considered non-critical. It must + // not be nil. ErrColl errcoll.Interface - // GeoIP detects the location of the request source. + // GeoIP detects the location of the request source. It must not be nil. GeoIP geoip.Interface - // Metrics is a listener for the middleware events. + // Metrics is a listener for the middleware events. It must not be nil. Metrics Metrics - // Limiter defines whether the query should be dropped or not. + // Limiter defines whether the query should be dropped or not. It must not + // be nil. Limiter ratelimit.Interface // Protocols is a list of protocols this middleware applies ratelimiting @@ -103,9 +108,7 @@ func New(c *Config) (mw *Middleware) { // Set the filtering-group and server information here immediately. return &agd.RequestInfo{ FilteringGroup: c.FilteringGroup, - ServerGroup: c.ServerGroup, - Server: c.Server.Name, - Proto: c.Server.Protocol, + ServerInfo: c.ServerInfo, } }), sdeConf: c.StructuredErrors, diff --git a/internal/ecscache/ecsblocklist.go b/internal/ecscache/ecsblocklist.go index 8246cc1..3075310 100644 --- a/internal/ecscache/ecsblocklist.go +++ b/internal/ecscache/ecsblocklist.go @@ -7,7 +7,6 @@ import "github.com/AdguardTeam/golibs/container" // FakeECSFQDNs contains all domains that indicate ECS support, but in fact // don't have one. var FakeECSFQDNs = container.NewMapSet( - "0.html-load.com.", "0231034e888a4837a17fd85b1ab159.ba.tenant.api.powerplatform.com.", "0272ac85-5199-4024-a555-397c3d825d95.prmutv.co.", "0b732edd-087f-4c27-b0f6-5ff26d96cdf6.prmutv.co.", @@ -18,11 +17,11 @@ var FakeECSFQDNs = container.NewMapSet( "103.chtsite.com.", "11-alarm-mop.meshare.com.", "12-alarm-mop.meshare.com.", + "123ipip.icu.", "126.com.", "126.net.", "127.net.", "13-alarm-mop.meshare.com.", - "1337x.to.", "14-alarm-mop.meshare.com.", "1447723502.rsc.cdn77.org.", "1533248697.rsc.cdn77.org.", @@ -32,20 +31,14 @@ var FakeECSFQDNs = container.NewMapSet( "163yun.com.", "1663719199.rsc.cdn77.org.", "1688.com.", - "17de4c1f.akstat.io.", "1845588971.rsc.cdn77.org.", "18ea70d2d9a945cfb97d818ba71817dc.pacloudflare.com.", - "193377-ipv4mte.gr.global.aa-rt.sharepoint.com.", - "193767-ipv4mte.gr.global.aa-rt.sharepoint.com.", - "193918-ipv4mte.gr.global.aa-rt.sharepoint.com.", - "1e3f1c45e0354da8a119ffe5209cab06.pacloudflare.com.", "2.401402081.west-gcloud.codm.activision.com.", - "2.api.pof.com.", "2.realtime.services.box.net.", - "201205igp.gameloft.com.", "2acdb9b66bb242618283aadb21ede6c1.pacloudflare.com.", "2e4b93d1-a8ae-4a89-8885-6109135ac0de.prmutv.co.", - "2talk.com.", + "2gis.ru.", + "33ipip.top.", "360safe.com.", "3a6b0682-f3e1-4576-a706-5eb4101b9cc3.prmutv.co.", "3aba5292-ba75-422b-8715-bd21146f7836.prmutv.co.", @@ -54,14 +47,14 @@ var FakeECSFQDNs = container.NewMapSet( "4.adsco.re.", "401402081.west-gcloud.codm.activision.com.", "4186979c18134d1eae82ec64dbfc9af2.pacloudflare.com.", - "46a2e5c3c5a64e218b60f2c2ee76b750.pacloudflare.com.", "4b32bb64ce554875ae3f8836479c89d4.pacloudflare.com.", + "507b28fb-2ef1-4c34-8bda-ba32030bb199.prmutv.co.", "520.jp.", - "59b517704ce43f0f.cartx.cloud.", "5d79bce7-5d2b-427e-a6c4-b89b6c7bf048.prmutv.co.", "5nines.com.", "6.401402081.west-gcloud.codm.activision.com.", "6093eccf-6734-4877-ac8b-83d6d0e27b46.prmutv.co.", + "64.adsco.re.", "66yahoo.com.", "733868e706dd40d3a4a0588fc39b3df8.pacloudflare.com.", "7c7b02d4bc3d48dd81a7c7738d4de1ab.pacloudflare.com.", @@ -73,203 +66,14 @@ var FakeECSFQDNs = container.NewMapSet( "a-api.anthropic.com.", "a-cdn.anthropic.com.", "a-m-p.xyz.", - "a.api.permutive.app.", - "a.applvn.com.", + "a.audrte.com.", "a.getepic.com.", + "a.lulucdn.com.", "a.nel.cloudflare.com.", "a.nitropay.com.", - "a10681260716.cdn.optimizely.com.", - "a2321.casalemedia.com.", - "a2322.casalemedia.com.", - "a2323.casalemedia.com.", - "a2324.casalemedia.com.", - "a2325.casalemedia.com.", - "a2326.casalemedia.com.", - "a2327.casalemedia.com.", - "a2328.casalemedia.com.", - "a2329.casalemedia.com.", - "a2330.casalemedia.com.", - "a2331.casalemedia.com.", - "a2332.casalemedia.com.", - "a2333.casalemedia.com.", - "a2334.casalemedia.com.", - "a2336.casalemedia.com.", - "a2337.casalemedia.com.", - "a2338.casalemedia.com.", - "a2339.casalemedia.com.", - "a2340.casalemedia.com.", - "a2341.casalemedia.com.", - "a2342.casalemedia.com.", - "a2344.casalemedia.com.", - "a2345.casalemedia.com.", - "a2346.casalemedia.com.", - "a2347.casalemedia.com.", - "a2348.casalemedia.com.", - "a2350.casalemedia.com.", - "a2351.casalemedia.com.", - "a2352.casalemedia.com.", - "a2353.casalemedia.com.", - "a2354.casalemedia.com.", - "a2355.casalemedia.com.", - "a2356.casalemedia.com.", - "a2358.casalemedia.com.", - "a2359.casalemedia.com.", - "a2361.casalemedia.com.", - "a2362.casalemedia.com.", - "a2363.casalemedia.com.", - "a2364.casalemedia.com.", - "a2365.casalemedia.com.", - "a2366.casalemedia.com.", - "a2367.casalemedia.com.", - "a2368.casalemedia.com.", - "a2369.casalemedia.com.", - "a2370.casalemedia.com.", - "a2371.casalemedia.com.", - "a2372.casalemedia.com.", - "a2373.casalemedia.com.", - "a2375.casalemedia.com.", - "a2377.casalemedia.com.", - "a2378.casalemedia.com.", - "a2379.casalemedia.com.", - "a2380.casalemedia.com.", - "a2381.casalemedia.com.", - "a2382.casalemedia.com.", - "a2383.casalemedia.com.", - "a2384.casalemedia.com.", - "a2385.casalemedia.com.", - "a2386.casalemedia.com.", - "a2387.casalemedia.com.", - "a2388.casalemedia.com.", - "a2389.casalemedia.com.", - "a2390.casalemedia.com.", - "a2391.casalemedia.com.", - "a2392.casalemedia.com.", - "a2393.casalemedia.com.", - "a2394.casalemedia.com.", - "a2395.casalemedia.com.", - "a2396.casalemedia.com.", - "a2397.casalemedia.com.", - "a2398.casalemedia.com.", - "a2399.casalemedia.com.", - "a2400.casalemedia.com.", - "a2402.casalemedia.com.", - "a2403.casalemedia.com.", - "a2404.casalemedia.com.", - "a2405.casalemedia.com.", - "a2406.casalemedia.com.", - "a2407.casalemedia.com.", - "a2408.casalemedia.com.", - "a2411.casalemedia.com.", - "a2412.casalemedia.com.", - "a2413.casalemedia.com.", - "a2414.casalemedia.com.", - "a2415.casalemedia.com.", - "a2416.casalemedia.com.", - "a2417.casalemedia.com.", - "a2418.casalemedia.com.", - "a2419.casalemedia.com.", - "a2420.casalemedia.com.", - "a2421.casalemedia.com.", - "a2422.casalemedia.com.", - "a2423.casalemedia.com.", - "a2425.casalemedia.com.", - "a2426.casalemedia.com.", - "a2427.casalemedia.com.", - "a2428.casalemedia.com.", - "a2429.casalemedia.com.", - "a2430.casalemedia.com.", - "a2431.casalemedia.com.", - "a2432.casalemedia.com.", - "a2433.casalemedia.com.", - "a2434.casalemedia.com.", - "a2435.casalemedia.com.", - "a2436.casalemedia.com.", - "a2437.casalemedia.com.", - "a2438.casalemedia.com.", - "a2439.casalemedia.com.", - "a2440.casalemedia.com.", - "a2441.casalemedia.com.", - "a2442.casalemedia.com.", - "a2443.casalemedia.com.", - "a2444.casalemedia.com.", - "a2445.casalemedia.com.", - "a2446.casalemedia.com.", - "a2447.casalemedia.com.", - "a2448.casalemedia.com.", - "a2449.casalemedia.com.", - "a2450.casalemedia.com.", - "a2451.casalemedia.com.", - "a2452.casalemedia.com.", - "a2453.casalemedia.com.", - "a2454.casalemedia.com.", - "a2456.casalemedia.com.", - "a2457.casalemedia.com.", - "a2458.casalemedia.com.", - "a2459.casalemedia.com.", - "a2460.casalemedia.com.", - "a2461.casalemedia.com.", - "a2462.casalemedia.com.", - "a2463.casalemedia.com.", - "a2464.casalemedia.com.", - "a2466.casalemedia.com.", - "a2467.casalemedia.com.", - "a2469.casalemedia.com.", - "a2470.casalemedia.com.", - "a2471.casalemedia.com.", - "a2473.casalemedia.com.", - "a2474.casalemedia.com.", - "a2475.casalemedia.com.", - "a2476.casalemedia.com.", - "a2477.casalemedia.com.", - "a2478.casalemedia.com.", - "a2479.casalemedia.com.", - "a2480.casalemedia.com.", - "a2481.casalemedia.com.", - "a2483.casalemedia.com.", - "a2484.casalemedia.com.", - "a2485.casalemedia.com.", - "a2486.casalemedia.com.", - "a2488.casalemedia.com.", - "a2489.casalemedia.com.", - "a2490.casalemedia.com.", - "a2492.casalemedia.com.", - "a2493.casalemedia.com.", - "a2494.casalemedia.com.", - "a2495.casalemedia.com.", - "a2496.casalemedia.com.", - "a2497.casalemedia.com.", - "a2498.casalemedia.com.", - "a2499.casalemedia.com.", - "a2500.casalemedia.com.", - "a2501.casalemedia.com.", - "a2502.casalemedia.com.", - "a2503.casalemedia.com.", - "a2504.casalemedia.com.", - "a2505.casalemedia.com.", - "a2506.casalemedia.com.", - "a2507.casalemedia.com.", - "a2508.casalemedia.com.", - "a2509.casalemedia.com.", - "a2510.casalemedia.com.", - "a2511.casalemedia.com.", - "a2512.casalemedia.com.", - "a2513.casalemedia.com.", - "a2514.casalemedia.com.", - "a2515.casalemedia.com.", - "a2516.casalemedia.com.", - "a2517.casalemedia.com.", - "a2518.casalemedia.com.", - "a2519.casalemedia.com.", - "a2521.casalemedia.com.", - "a2522.casalemedia.com.", - "a2523.casalemedia.com.", - "a2524.casalemedia.com.", - "a2525.casalemedia.com.", - "a2526.casalemedia.com.", - "a2527.casalemedia.com.", - "a2528.casalemedia.com.", - "a2529.casalemedia.com.", - "a2681.casalemedia.com.", + "a.quora.com.", + "a1521dca917ff0e871c935c9253afc0626587b2d.cws.conviva.com.", + "a16dda3b33f14e7dbbf0aee44dc53784.pacloudflare.com.", "a2682.casalemedia.com.", "a2683.casalemedia.com.", "a2684.casalemedia.com.", @@ -289,10 +93,10 @@ var FakeECSFQDNs = container.NewMapSet( "a2698.casalemedia.com.", "a2699.casalemedia.com.", "a2700.casalemedia.com.", + "a2701.casalemedia.com.", "a2702.casalemedia.com.", "a2703.casalemedia.com.", "a2704.casalemedia.com.", - "a2705.casalemedia.com.", "a2706.casalemedia.com.", "a2707.casalemedia.com.", "a2708.casalemedia.com.", @@ -330,14 +134,12 @@ var FakeECSFQDNs = container.NewMapSet( "a2740.casalemedia.com.", "a2741.casalemedia.com.", "a2742.casalemedia.com.", - "a2743.casalemedia.com.", "a2744.casalemedia.com.", "a2745.casalemedia.com.", "a2746.casalemedia.com.", "a2747.casalemedia.com.", "a2748.casalemedia.com.", "a2749.casalemedia.com.", - "a2750.casalemedia.com.", "a2751.casalemedia.com.", "a2752.casalemedia.com.", "a2753.casalemedia.com.", @@ -346,19 +148,19 @@ var FakeECSFQDNs = container.NewMapSet( "a2756.casalemedia.com.", "a2757.casalemedia.com.", "a2758.casalemedia.com.", + "a2759.casalemedia.com.", "a2760.casalemedia.com.", "a2761.casalemedia.com.", "a2762.casalemedia.com.", "a2763.casalemedia.com.", "a2764.casalemedia.com.", - "a2765.casalemedia.com.", "a2766.casalemedia.com.", "a2767.casalemedia.com.", "a2768.casalemedia.com.", + "a2769.casalemedia.com.", "a2770.casalemedia.com.", "a2771.casalemedia.com.", "a2772.casalemedia.com.", - "a2773.casalemedia.com.", "a2774.casalemedia.com.", "a2775.casalemedia.com.", "a2776.casalemedia.com.", @@ -368,12 +170,12 @@ var FakeECSFQDNs = container.NewMapSet( "a2780.casalemedia.com.", "a2781.casalemedia.com.", "a2782.casalemedia.com.", + "a2783.casalemedia.com.", "a2784.casalemedia.com.", "a2785.casalemedia.com.", "a2786.casalemedia.com.", "a2787.casalemedia.com.", "a2788.casalemedia.com.", - "a2790.casalemedia.com.", "a2791.casalemedia.com.", "a2793.casalemedia.com.", "a2794.casalemedia.com.", @@ -386,7 +188,6 @@ var FakeECSFQDNs = container.NewMapSet( "a2a5c7f9-3fa0-4182-889a-15aa61acf59b.prmutv.co.", "a3551.casalemedia.com.", "a3552.casalemedia.com.", - "a3553.casalemedia.com.", "a3554.casalemedia.com.", "a3555.casalemedia.com.", "a3556.casalemedia.com.", @@ -403,6 +204,7 @@ var FakeECSFQDNs = container.NewMapSet( "a3568.casalemedia.com.", "a3569.casalemedia.com.", "a3570.casalemedia.com.", + "a3571.casalemedia.com.", "a3572.casalemedia.com.", "a3573.casalemedia.com.", "a3574.casalemedia.com.", @@ -410,16 +212,17 @@ var FakeECSFQDNs = container.NewMapSet( "a3576.casalemedia.com.", "a3577.casalemedia.com.", "a3578.casalemedia.com.", + "a3579.casalemedia.com.", "a3580.casalemedia.com.", "a3581.casalemedia.com.", "a3582.casalemedia.com.", - "a3583.casalemedia.com.", "a3584.casalemedia.com.", "a3585.casalemedia.com.", "a3586.casalemedia.com.", "a3587.casalemedia.com.", "a3588.casalemedia.com.", "a3589.casalemedia.com.", + "a3590.casalemedia.com.", "a3591.casalemedia.com.", "a3592.casalemedia.com.", "a3593.casalemedia.com.", @@ -431,7 +234,6 @@ var FakeECSFQDNs = container.NewMapSet( "a3599.casalemedia.com.", "a3600.casalemedia.com.", "a3601.casalemedia.com.", - "a3602.casalemedia.com.", "a3603.casalemedia.com.", "a3604.casalemedia.com.", "a3605.casalemedia.com.", @@ -443,7 +245,6 @@ var FakeECSFQDNs = container.NewMapSet( "a3611.casalemedia.com.", "a3612.casalemedia.com.", "a3613.casalemedia.com.", - "a3614.casalemedia.com.", "a3615.casalemedia.com.", "a3616.casalemedia.com.", "a3617.casalemedia.com.", @@ -455,17 +256,13 @@ var FakeECSFQDNs = container.NewMapSet( "a3623.casalemedia.com.", "a3624.casalemedia.com.", "a3625.casalemedia.com.", - "a3626.casalemedia.com.", "a3627.casalemedia.com.", - "a3628.casalemedia.com.", "a3629.casalemedia.com.", "a3630.casalemedia.com.", "a3631.casalemedia.com.", "a3632.casalemedia.com.", "a3633.casalemedia.com.", "a3634.casalemedia.com.", - "a3635.casalemedia.com.", - "a3636.casalemedia.com.", "a3637.casalemedia.com.", "a3638.casalemedia.com.", "a3639.casalemedia.com.", @@ -474,11 +271,11 @@ var FakeECSFQDNs = container.NewMapSet( "a3642.casalemedia.com.", "a3643.casalemedia.com.", "a3644.casalemedia.com.", + "a3645.casalemedia.com.", "a3646.casalemedia.com.", "a3647.casalemedia.com.", "a3648.casalemedia.com.", - "a3649.casalemedia.com.", - "a3651.casalemedia.com.", + "a3650.casalemedia.com.", "a3652.casalemedia.com.", "a3653.casalemedia.com.", "a3654.casalemedia.com.", @@ -492,16 +289,14 @@ var FakeECSFQDNs = container.NewMapSet( "a3662.casalemedia.com.", "a3663.casalemedia.com.", "a3664.casalemedia.com.", - "a3665.casalemedia.com.", "a3666.casalemedia.com.", "a3667.casalemedia.com.", "a3668.casalemedia.com.", "a3669.casalemedia.com.", - "a3670.casalemedia.com.", - "a3671.casalemedia.com.", "a3672.casalemedia.com.", "a3673.casalemedia.com.", "a3674.casalemedia.com.", + "a3675.casalemedia.com.", "a3676.casalemedia.com.", "a3677.casalemedia.com.", "a3678.casalemedia.com.", @@ -555,8 +350,206 @@ var FakeECSFQDNs = container.NewMapSet( "a3728.casalemedia.com.", "a3729.casalemedia.com.", "a3730.casalemedia.com.", + "a3881.casalemedia.com.", + "a3882.casalemedia.com.", + "a3883.casalemedia.com.", + "a3884.casalemedia.com.", + "a3885.casalemedia.com.", + "a3886.casalemedia.com.", + "a3887.casalemedia.com.", + "a3888.casalemedia.com.", + "a3889.casalemedia.com.", + "a3890.casalemedia.com.", + "a3891.casalemedia.com.", + "a3892.casalemedia.com.", + "a3893.casalemedia.com.", + "a3894.casalemedia.com.", + "a3895.casalemedia.com.", + "a3896.casalemedia.com.", + "a3897.casalemedia.com.", + "a3898.casalemedia.com.", + "a3899.casalemedia.com.", + "a3900.casalemedia.com.", + "a3901.casalemedia.com.", + "a3902.casalemedia.com.", + "a3903.casalemedia.com.", + "a3904.casalemedia.com.", + "a3905.casalemedia.com.", + "a3906.casalemedia.com.", + "a3907.casalemedia.com.", + "a3908.casalemedia.com.", + "a3909.casalemedia.com.", + "a3910.casalemedia.com.", + "a3911.casalemedia.com.", + "a3912.casalemedia.com.", + "a3913.casalemedia.com.", + "a3914.casalemedia.com.", + "a3915.casalemedia.com.", + "a3916.casalemedia.com.", + "a3918.casalemedia.com.", + "a3919.casalemedia.com.", + "a3920.casalemedia.com.", + "a3922.casalemedia.com.", + "a3923.casalemedia.com.", + "a3924.casalemedia.com.", + "a3925.casalemedia.com.", + "a3926.casalemedia.com.", + "a3927.casalemedia.com.", + "a3928.casalemedia.com.", + "a3929.casalemedia.com.", + "a3930.casalemedia.com.", + "a3931.casalemedia.com.", + "a3932.casalemedia.com.", + "a3933.casalemedia.com.", + "a3934.casalemedia.com.", + "a3935.casalemedia.com.", + "a3936.casalemedia.com.", + "a3937.casalemedia.com.", + "a3938.casalemedia.com.", + "a3939.casalemedia.com.", + "a3940.casalemedia.com.", + "a3941.casalemedia.com.", + "a3942.casalemedia.com.", + "a3943.casalemedia.com.", + "a3944.casalemedia.com.", + "a3945.casalemedia.com.", + "a3946.casalemedia.com.", + "a3947.casalemedia.com.", + "a3948.casalemedia.com.", + "a3949.casalemedia.com.", + "a3950.casalemedia.com.", + "a3951.casalemedia.com.", + "a3952.casalemedia.com.", + "a3953.casalemedia.com.", + "a3954.casalemedia.com.", + "a3955.casalemedia.com.", + "a3956.casalemedia.com.", + "a3957.casalemedia.com.", + "a3958.casalemedia.com.", + "a3959.casalemedia.com.", + "a3960.casalemedia.com.", + "a3961.casalemedia.com.", + "a3962.casalemedia.com.", + "a3963.casalemedia.com.", + "a3964.casalemedia.com.", + "a3965.casalemedia.com.", + "a3966.casalemedia.com.", + "a3967.casalemedia.com.", + "a3968.casalemedia.com.", + "a3969.casalemedia.com.", + "a3970.casalemedia.com.", + "a3971.casalemedia.com.", + "a3972.casalemedia.com.", + "a3973.casalemedia.com.", + "a3974.casalemedia.com.", + "a3976.casalemedia.com.", + "a3977.casalemedia.com.", + "a3978.casalemedia.com.", + "a3980.casalemedia.com.", + "a3981.casalemedia.com.", + "a3982.casalemedia.com.", + "a3983.casalemedia.com.", + "a3984.casalemedia.com.", + "a3985.casalemedia.com.", + "a3987.casalemedia.com.", + "a3988.casalemedia.com.", + "a3989.casalemedia.com.", + "a3990.casalemedia.com.", + "a3991.casalemedia.com.", + "a3992.casalemedia.com.", + "a3993.casalemedia.com.", + "a3994.casalemedia.com.", + "a3995.casalemedia.com.", + "a3996.casalemedia.com.", + "a3997.casalemedia.com.", + "a3998.casalemedia.com.", + "a3999.casalemedia.com.", + "a3ehe.com.", + "a4000.casalemedia.com.", + "a4001.casalemedia.com.", + "a4002.casalemedia.com.", + "a4003.casalemedia.com.", + "a4004.casalemedia.com.", + "a4005.casalemedia.com.", + "a4006.casalemedia.com.", + "a4007.casalemedia.com.", + "a4008.casalemedia.com.", + "a4009.casalemedia.com.", + "a4010.casalemedia.com.", + "a4011.casalemedia.com.", + "a4012.casalemedia.com.", + "a4013.casalemedia.com.", + "a4014.casalemedia.com.", + "a4015.casalemedia.com.", + "a4016.casalemedia.com.", + "a4017.casalemedia.com.", + "a4018.casalemedia.com.", + "a4019.casalemedia.com.", + "a4020.casalemedia.com.", + "a4021.casalemedia.com.", + "a4022.casalemedia.com.", + "a4023.casalemedia.com.", + "a4024.casalemedia.com.", + "a4025.casalemedia.com.", + "a4026.casalemedia.com.", + "a4027.casalemedia.com.", + "a4028.casalemedia.com.", + "a4029.casalemedia.com.", + "a4030.casalemedia.com.", + "a4031.casalemedia.com.", + "a4032.casalemedia.com.", + "a4033.casalemedia.com.", + "a4034.casalemedia.com.", + "a4035.casalemedia.com.", + "a4036.casalemedia.com.", + "a4037.casalemedia.com.", + "a4038.casalemedia.com.", + "a4039.casalemedia.com.", + "a4041.casalemedia.com.", + "a4042.casalemedia.com.", + "a4043.casalemedia.com.", + "a4045.casalemedia.com.", + "a4046.casalemedia.com.", + "a4047.casalemedia.com.", + "a4048.casalemedia.com.", + "a4049.casalemedia.com.", + "a4050.casalemedia.com.", + "a4051.casalemedia.com.", + "a4052.casalemedia.com.", + "a4053.casalemedia.com.", + "a4054.casalemedia.com.", + "a4055.casalemedia.com.", + "a4056.casalemedia.com.", + "a4057.casalemedia.com.", + "a4058.casalemedia.com.", + "a4059.casalemedia.com.", + "a4060.casalemedia.com.", + "a4061.casalemedia.com.", + "a4062.casalemedia.com.", + "a4063.casalemedia.com.", + "a4064.casalemedia.com.", + "a4065.casalemedia.com.", + "a4066.casalemedia.com.", + "a4067.casalemedia.com.", + "a4068.casalemedia.com.", + "a4069.casalemedia.com.", + "a4070.casalemedia.com.", + "a4071.casalemedia.com.", + "a4072.casalemedia.com.", + "a4073.casalemedia.com.", + "a4074.casalemedia.com.", + "a4077.casalemedia.com.", + "a4078.casalemedia.com.", + "a4079.casalemedia.com.", + "a4080.casalemedia.com.", + "a4082.casalemedia.com.", + "a4084.casalemedia.com.", + "a4085.casalemedia.com.", + "a4086.casalemedia.com.", + "a4087.casalemedia.com.", + "a4088.casalemedia.com.", + "a4089.casalemedia.com.", "a5551.casalemedia.com.", - "a5555.casalemedia.com.", "a5556.casalemedia.com.", "a5557.casalemedia.com.", "a5558.casalemedia.com.", @@ -565,10 +558,8 @@ var FakeECSFQDNs = container.NewMapSet( "a5561.casalemedia.com.", "a5562.casalemedia.com.", "a5563.casalemedia.com.", - "a5565.casalemedia.com.", - "a5566.casalemedia.com.", + "a5564.casalemedia.com.", "a5567.casalemedia.com.", - "a5568.casalemedia.com.", "a5569.casalemedia.com.", "a5570.casalemedia.com.", "a5571.casalemedia.com.", @@ -576,12 +567,12 @@ var FakeECSFQDNs = container.NewMapSet( "a5573.casalemedia.com.", "a5574.casalemedia.com.", "a5575.casalemedia.com.", + "a5576.casalemedia.com.", "a5577.casalemedia.com.", "a5578.casalemedia.com.", "a5579.casalemedia.com.", "a5580.casalemedia.com.", "a5581.casalemedia.com.", - "a5582.casalemedia.com.", "a5583.casalemedia.com.", "a5584.casalemedia.com.", "a5585.casalemedia.com.", @@ -590,12 +581,12 @@ var FakeECSFQDNs = container.NewMapSet( "a5588.casalemedia.com.", "a5589.casalemedia.com.", "a5590.casalemedia.com.", + "a5591.casalemedia.com.", "a5593.casalemedia.com.", "a5594.casalemedia.com.", "a5595.casalemedia.com.", "a5596.casalemedia.com.", "a5597.casalemedia.com.", - "a5598.casalemedia.com.", "a5599.casalemedia.com.", "a55a84b3-9632-4869-b625-3d8ef43ed18d.prmutv.co.", "a5600.casalemedia.com.", @@ -608,7 +599,6 @@ var FakeECSFQDNs = container.NewMapSet( "a5607.casalemedia.com.", "a5608.casalemedia.com.", "a5609.casalemedia.com.", - "a5610.casalemedia.com.", "a5611.casalemedia.com.", "a5612.casalemedia.com.", "a5613.casalemedia.com.", @@ -620,10 +610,10 @@ var FakeECSFQDNs = container.NewMapSet( "a5619.casalemedia.com.", "a5620.casalemedia.com.", "a5621.casalemedia.com.", - "a5622.casalemedia.com.", "a5623.casalemedia.com.", "a5624.casalemedia.com.", "a5625.casalemedia.com.", + "a5626.casalemedia.com.", "a5627.casalemedia.com.", "a5628.casalemedia.com.", "a5629.casalemedia.com.", @@ -633,39 +623,40 @@ var FakeECSFQDNs = container.NewMapSet( "a5633.casalemedia.com.", "a5634.casalemedia.com.", "a5635.casalemedia.com.", - "a5636.casalemedia.com.", "a5637.casalemedia.com.", "a5638.casalemedia.com.", "a5639.casalemedia.com.", "a5640.casalemedia.com.", - "a5641.casalemedia.com.", + "a5642.casalemedia.com.", "a5643.casalemedia.com.", + "a5644.casalemedia.com.", "a5645.casalemedia.com.", + "a5647.casalemedia.com.", "a5648.casalemedia.com.", "a5649.casalemedia.com.", + "a5654.casalemedia.com.", "a5656.casalemedia.com.", "a5657.casalemedia.com.", - "a5667.casalemedia.com.", + "a5658.casalemedia.com.", + "a5664.casalemedia.com.", + "a5665.casalemedia.com.", "a5668.casalemedia.com.", "a5671.casalemedia.com.", "a5672.casalemedia.com.", "a5673.casalemedia.com.", - "a5674.casalemedia.com.", "a5675.casalemedia.com.", "a5676.casalemedia.com.", "a5677.casalemedia.com.", "a5678.casalemedia.com.", + "a5679.casalemedia.com.", "a5680.casalemedia.com.", "a5681.casalemedia.com.", "a5682.casalemedia.com.", - "a5684.casalemedia.com.", - "a5685.casalemedia.com.", - "a5686.casalemedia.com.", + "a5683.casalemedia.com.", "a5687.casalemedia.com.", "a5688.casalemedia.com.", "a5689.casalemedia.com.", "a5690.casalemedia.com.", - "a5691.casalemedia.com.", "a5692.casalemedia.com.", "a5693.casalemedia.com.", "a5694.casalemedia.com.", @@ -678,69 +669,118 @@ var FakeECSFQDNs = container.NewMapSet( "a5701.casalemedia.com.", "a5702.casalemedia.com.", "a5703.casalemedia.com.", + "a5704.casalemedia.com.", + "a5705.casalemedia.com.", "a5706.casalemedia.com.", - "a5707.casalemedia.com.", + "a5708.casalemedia.com.", + "a5709.casalemedia.com.", "a5710.casalemedia.com.", + "a5731.casalemedia.com.", + "a5732.casalemedia.com.", + "a5733.casalemedia.com.", + "a5734.casalemedia.com.", + "a5735.casalemedia.com.", + "a5736.casalemedia.com.", + "a5737.casalemedia.com.", + "a5738.casalemedia.com.", + "a5739.casalemedia.com.", + "a5740.casalemedia.com.", + "a5742.casalemedia.com.", + "a5743.casalemedia.com.", + "a5744.casalemedia.com.", + "a5745.casalemedia.com.", + "a5746.casalemedia.com.", + "a5747.casalemedia.com.", + "a5748.casalemedia.com.", + "a5749.casalemedia.com.", + "a5750.casalemedia.com.", + "a5751.casalemedia.com.", + "a5752.casalemedia.com.", + "a5753.casalemedia.com.", + "a5754.casalemedia.com.", + "a5755.casalemedia.com.", + "a5756.casalemedia.com.", + "a5757.casalemedia.com.", + "a5758.casalemedia.com.", + "a5759.casalemedia.com.", + "a5760.casalemedia.com.", + "a5761.casalemedia.com.", + "a5762.casalemedia.com.", + "a5763.casalemedia.com.", + "a5764.casalemedia.com.", + "a5765.casalemedia.com.", + "a5766.casalemedia.com.", + "a5767.casalemedia.com.", + "a5768.casalemedia.com.", + "a5769.casalemedia.com.", + "a5770.casalemedia.com.", + "a5771.casalemedia.com.", + "a5772.casalemedia.com.", + "a5773.casalemedia.com.", + "a5774.casalemedia.com.", + "a5775.casalemedia.com.", "a5776.casalemedia.com.", - "a5777.casalemedia.com.", "a5778.casalemedia.com.", "a5779.casalemedia.com.", "a5780.casalemedia.com.", "a5781.casalemedia.com.", + "a5782.casalemedia.com.", "a5783.casalemedia.com.", "a5784.casalemedia.com.", + "a5785.casalemedia.com.", "a5786.casalemedia.com.", "a5787.casalemedia.com.", "a5788.casalemedia.com.", - "a5789.casalemedia.com.", "a5790.casalemedia.com.", "a5791.casalemedia.com.", + "a5792.casalemedia.com.", "a5793.casalemedia.com.", "a5794.casalemedia.com.", "a5795.casalemedia.com.", "a5796.casalemedia.com.", - "a5797.casalemedia.com.", "a5798.casalemedia.com.", + "a5799.casalemedia.com.", "a5800.casalemedia.com.", "a5801.casalemedia.com.", "a5802.casalemedia.com.", "a5803.casalemedia.com.", "a5804.casalemedia.com.", + "a5805.casalemedia.com.", + "a5806.casalemedia.com.", "a5807.casalemedia.com.", "a5809.casalemedia.com.", "a581.casalemedia.com.", "a5810.casalemedia.com.", "a5811.casalemedia.com.", + "a5812.casalemedia.com.", "a5813.casalemedia.com.", + "a5814.casalemedia.com.", "a5815.casalemedia.com.", + "a582.casalemedia.com.", "a583.casalemedia.com.", "a584.casalemedia.com.", "a585.casalemedia.com.", "a586.casalemedia.com.", "a587.casalemedia.com.", "a588.casalemedia.com.", - "a589.casalemedia.com.", "a590.casalemedia.com.", "a591.casalemedia.com.", "a592.casalemedia.com.", "a593.casalemedia.com.", "a594.casalemedia.com.", "a595.casalemedia.com.", - "a596.casalemedia.com.", "a597.casalemedia.com.", "a598.casalemedia.com.", "a599.casalemedia.com.", "a600.casalemedia.com.", "a601.casalemedia.com.", - "a602.casalemedia.com.", - "a603.casalemedia.com.", "a604.casalemedia.com.", "a605.casalemedia.com.", "a606.casalemedia.com.", "a607.casalemedia.com.", "a608.casalemedia.com.", - "a609.casalemedia.com.", "a610.casalemedia.com.", + "a611.casalemedia.com.", "a612.casalemedia.com.", "a613.casalemedia.com.", "a614.casalemedia.com.", @@ -754,7 +794,6 @@ var FakeECSFQDNs = container.NewMapSet( "a622.casalemedia.com.", "a623.casalemedia.com.", "a624.casalemedia.com.", - "a625.casalemedia.com.", "a626.casalemedia.com.", "a627.casalemedia.com.", "a628.casalemedia.com.", @@ -769,7 +808,6 @@ var FakeECSFQDNs = container.NewMapSet( "a637.casalemedia.com.", "a638.casalemedia.com.", "a639.casalemedia.com.", - "a640.casalemedia.com.", "a921.casalemedia.com.", "a922.casalemedia.com.", "a923.casalemedia.com.", @@ -815,7 +853,6 @@ var FakeECSFQDNs = container.NewMapSet( "a965.casalemedia.com.", "a966.casalemedia.com.", "a967.casalemedia.com.", - "a968.casalemedia.com.", "a969.casalemedia.com.", "a9695278-4085-40b3-9f02-8d4c38a6ff01.prmutv.co.", "a970.casalemedia.com.", @@ -828,9 +865,9 @@ var FakeECSFQDNs = container.NewMapSet( "a977.casalemedia.com.", "a979.casalemedia.com.", "a980.casalemedia.com.", - "a9972578858.cdn.optimizely.com.", "aa.online-metrix.net.", "aa.quantummetric.com.", + "aa.unioneeu.com.", "ab.qq.com.", "aba2c424-419a-4d03-9aed-2dca8a7139e4.prmutv.co.", "abeacdataonrt-stsdk.vivo.com.cn.", @@ -840,81 +877,60 @@ var FakeECSFQDNs = container.NewMapSet( "accela.com.", "accentuate.io.", "access.mp.lura.live.", - "access.ovid.com.", + "accessacloud.com.", "account.box.com.", - "account.live.com.", "account.msa.msidentity.com.", - "account.riotgames.com.", - "accountapi.agoda.com.", - "accounts.binance.info.", - "accounts.illuminateed.net.", - "accounts.logme.in.", - "accounts.shopify.com.", "accurint.com.", "acdn.tinkoff.ru.", - "acertb.com.", "acgvideo.com.", - "achievements.xboxlive.com.", "acme-v02.api.letsencrypt.org.", "acrobits.cz.", - "acs.smartrg.com.", - "acsdk.gameyw.easebar.com.", - "action.dstillery.com.", - "activate.academy.com.", "ad1.adfarm1.adition.com.", - "ad11.adfarm1.adition.com.", - "ad11p.adfarm1.adition.com.", - "ad13.adfarm1.adition.com.", - "ad2.adfarm1.adition.com.", - "ad3.adfarm1.adition.com.", - "ad4.adfarm1.adition.com.", "adapex.io.", - "adapi3.boomplaymusic.com.", "adash.man.aliyuncs.com.", "adblock.telemetry.getadblock.com.", + "adc.bmj.com.", "adcell.com.", "adder.feeder.co.", "addictpodcast.com.", "additionfi.com.", + "adebc6b12f2d428abfe2b66ceace1662.pacloudflare.com.", "adfarm1.adition.com.", - "adgzs.top.", "adiam.tech.", "adition.com.", "aditude.io.", - "adm.wsms.haplat.net.", - "admbk.wsms.haplat.net.", "admixer.com.", - "adoric.com.", + "adokutcontextual.com.", "adrs.org.cn.", + "ads.chtbl.com.", "adsco.re.", "adsolut.in.", "adtarget.market.", "adtarget.me.", - "adtechnacity.com.", "advantage.purpleguys.com.", "advantage2.purpleguys.com.", + "adventisthealthwest-my.sharepoint.com.", "adview.com.", "adx-os.bridgeoos.com.", "adx-sg-req.anythinktech.com.", - "aepenergy.sharepoint.com.", + "adxpremium.services.", "aeries.com.", "afd-cf.www.linkedin.com.", "afd-lnkd.www.linkedin.com.", + "afd-wcs-ramp.www.linkedin.com.", + "ag.dns-finder.com.", "agent-logos.storage.googleapis.com.", "agent.marketingcloudfx.com.", + "agents.fxpasu01.manage.microsoft.us.", "ai-voice.cloudbirds.cn.", "aicdn.com.", "aid.send.microad.jp.", "aidata.io.", + "aiq-in.caranddriver.com.", "airasia.com.", "airtory.com.", - "aisee.tv.", "ajcloud.net.", "akrdinfo.cn.", - "akronchildrens.sharepoint.com.", - "akulaku.com.", - "alarm.wsms.haplat.net.", - "alarmbk.wsms.haplat.net.", "albany.remotepc.com.", "album-sg01a.ocloud.heytapmobi.com.", "alfasense.com.", @@ -929,16 +945,12 @@ var FakeECSFQDNs = container.NewMapSet( "alive3.cloudbirds.cn.", "aliyuncs.com.", "alpha1-ap-public.val.qq.com.", - "alpha1-gp-ping-cq2.val.qq.com.", - "alpha1-gp-ping-gz2.val.qq.com.", - "alpha1-gp-ping-nj2.val.qq.com.", - "alpha1-gp-ping-tj2.val.qq.com.", + "alpha1-nj-gp2-mix3.val.qq.com.", "amd.com.", "amdcopen.m.taobao.com.", "amdcopen.m.taobao.com.gds.alibabadns.com.", - "amino01.mom.", - "amino03.mom.", "ammarkids.online.", + "amp.namequery.com.", "amp.permutive.com.", "amplitudelab.usemotion.com.", "amsterdam.remotepc.com.", @@ -946,33 +958,31 @@ var FakeECSFQDNs = container.NewMapSet( "analytics-2.athome.com.", "analytics-debugger.com.", "analytics-ingress-global.bitmovin.com.", - "analytics.apps.seabroadnet.com.", - "analytics.archive.org.", "analytics.belk.com.", - "analytics.edgekey.net.", "analytics.gnc.com.", "analytics.languagetoolplus.com.", "analytics.pdf24.org.", "analytics.qumucloud.com.", - "analytics.trovit.com.", + "analytics.talbots.com.", "analyticssystems.net.", + "ancestralsupplements.com.", "android.crashsight.wetest.net.", "anlian.co.", "announce.torrentsmd.com.", - "ant.learnscitech.com.", "anthropic.com.", "antstream.com.", + "aocde.com.", "aon.com.", "api-analytics-us3.zepp.com.", - "api-app-slow.bitkeep.fun.", - "api-app-slow.bitkeep.life.", "api-asyncgw-gcc-teams.usgovtrafficmanager.net.", + "api-auth.flysleep.cn.", "api-auth.zztfly.com.", "api-decider.vsco.co.", "api-eu1.hubspot.com.", - "api-gl.store.heytapmobi.com.", + "api-gateway.umami.dev.", "api-glb-aaps1b.smoot.apple.com.", "api-glb-aapse1c.smoot.apple.com.", + "api-glb-aeun1a.smoot.apple.com.", "api-glb-aeus2a.smoot.apple.com.", "api-glb-aeus2b.smoot.apple.com.", "api-glb-aeuw1b.smoot.apple.com.", @@ -985,98 +995,116 @@ var FakeECSFQDNs = container.NewMapSet( "api-glb-ause2c.smoot.apple.com.", "api-glb-ausw2b.smoot.apple.com.", "api-glb-ausw2c.smoot.apple.com.", + "api-lookup-ause2a.smoot.apple.com.", "api-mayi.django.t.taobao.com.", + "api-mifit-de2.zepp.com.", "api-mifit-us3.zepp.com.", - "api-player.musicstylingonline.com.", "api-pos.titank12.com.", "api-preview.luckyorange.com.", + "api-proxy.conveythis.com.", + "api-safari-aapse1c.smoot.apple.com.", + "api-safari-aeun1a.smoot.apple.com.", + "api-safari-aeun1b.smoot.apple.com.", + "api-safari-aeus2a.smoot.apple.com.", + "api-safari-aeus2b.smoot.apple.com.", + "api-safari-aeuw1b.smoot.apple.com.", + "api-safari-ause1a.smoot.apple.com.", + "api-safari-ause1b.smoot.apple.com.", + "api-safari-ause1c.smoot.apple.com.", + "api-safari-ause2a.smoot.apple.com.", + "api-safari-ause2b.smoot.apple.com.", + "api-safari-ause2c.smoot.apple.com.", + "api-safari-ausw2b.smoot.apple.com.", + "api-safari-ausw2c.smoot.apple.com.", + "api-service.shein.com.cdn.cloudflare.net.", "api-sh.django.t.taobao.com.gds.alibabadns.com.", "api-user.dalyfeds.com.", + "api.9hits.com.", + "api.al-array.com.", "api.ams.gcc.teams.microsoft.com.", - "api.axs.com.", "api.bobble.ai.", - "api.boldcommerce.com.", "api.box.com.", "api.btloader.com.", - "api.cb-device-intelligence.com.", + "api.bugreport.huorong.cn.", + "api.ceros.com.", "api.cloud.tenda.com.cn.", "api.config-security.com.", "api.crobox.com.", "api.crush.163.com.", + "api.crxcavator.io.", "api.dealersocket.com.", "api.deepl.com.", "api.eponesh.com.", "api.glanceapis.com.", "api.gleap.io.", - "api.gowish.com.", "api.gravitec.media.", - "api.gx.me.", "api.hetangsmart.com.", "api.hqt0w.com.", - "api.icalendars.app.", "api.ipgeolocation.io.", - "api.k.163.com.", + "api.kingdata.ksyun.com.", "api.kinogram.best.", "api.lightboxcdn.com.", "api.loyalhealth.com.", "api.lytics.io.", - "api.mangacoin.net.", "api.mida.so.", "api.moyoung.com.", "api.my.jbi.global.", + "api.nkryu17dc.com.", "api.onedrive.com.", + "api.open-meteo.com.", "api.permutive.app.", "api.permutive.com.", "api.pgf-thek63.com.", "api.playlnk.io.", + "api.polaris.al-array.com.", "api.popin.cc.", - "api.qingcigame.com.", "api.queryly.com.", - "api.qwant.com.", - "api.rakuten.com.", "api.reverso.net.", "api.ringcentral.biz.", "api.rollbar.com.", + "api.scorchads.com.", "api.smartdeploy.com.", "api.snapchat.com.", - "api.swishapps.ai.", - "api.tapcart.com.", - "api.textnow.me.", + "api.tracking.al-array.com.", "api.transitapp.com.", + "api.tunnelbear.com.", "api.tx4.pw.adn.cloud.", - "api.u17tz.com.", "api.ultimaker.com.", "api.ultimate-guitar.com.", "api.unity.com.", - "api.urbanoutfitters.com.", "api.us.minga.io.", + "api.us2.backdrop.cloud.", "api.userlike.com.", "api.vieon.vn.", + "api.voicemod.net.", "api.vsco.co.", "api.workjam.com.", + "api.x1skf.com.", "api.xiaoyi.com.", - "api.y41w4.com.", + "api.yosmart.com.", + "api.zmcyu9ypy.com.", "api000.backblazeb2.com.", "api001.backblazeb2.com.", "api002.backblazeb2.com.", + "apibay.org.", "apiblink.ru.", "apiisgp.ezvizlife.com.", "apiisgp.hik-connect.com.", "apimgmttmgpxfqy6dfjiqfsk6t67i30fgsnfhah4rrjw51coy3.trafficmanager.net.", "apis.live.net.", - "apk.v-mate.mobi.", - "apk.vidmate.net.", "aplo-evnt.com.", - "apm.gotokeep.com.", "app-atl.five9.com.", "app-eu1.hubspot.com.", "app-scl.five9.com.", "app-student.atitesting.com.", "app.adoric-om.com.", "app.box.com.", + "app.carnow.com.cdn.cloudflare.net.", "app.cart-bot.net.", "app.cybba.solutions.", "app.ee-share.com.", + "app.firmguard.com.", + "app.jobvite.com.", "app.paces.jbi.global.", "app.pendo.qgenda.com.", "app.retinavue.net.", @@ -1085,18 +1113,17 @@ var FakeECSFQDNs = container.NewMapSet( "app.talkjs.com.", "app.wdesk.com.", "app.zoom.us.", + "app.zoominfo.com.", "appcafe.gtm.starbucks.com.", "appcafe.starbucks.com.", - "appconf.mail.163.com.", "appdump.nie.easebar.com.", + "apphub.tracker.al-array.com.", "applog.matrix.easebar.com.", "appocean.media.", - "apponline.research.qq.com.", + "appriver.com.", "apps.wix.com.", - "appstoreonrt-stsdk.vivo.com.cn.", "appstoreort-stsdk.vivo.com.cn.", - "apro-api.collegeboard.org.", - "apv-static.minute.ly.", + "aralego.net.", "arenabg.com.", "aristotleinsight.com.", "arm-frontdoor-edge-geo.trafficmanager.net.", @@ -1104,35 +1131,18 @@ var FakeECSFQDNs = container.NewMapSet( "arms-retcode-sg.aliyuncs.com.", "as-api.asm.skype.com.", "as-prod.asyncgw.teams.microsoft.com.", - "as.footballbros.io.", + "as.xiaohongshu.com.", "asanalytics.booking.com.", "asheville.remotepc.com.", "ashleyfurniture.aiproxies.com.", - "asia-adlog.vivoglobal.com.", - "asia-browser.vivoglobal.com.", - "asia-cota.vivoglobal.com.", "asia-ex-adlog.vivoglobal.com.", - "asia-gsearch.vivoglobal.com.", - "asia-news-abroad-backstage-interface.vivoglobal.com.", - "asia-news-abroad.vivo.com.", - "asia-p.vivoglobal.com.", - "asia-ro-up.vivoglobal.com.", "asia-rommc-api.vivoglobal.com.", - "asia-romsp-unifyconfig.vivoglobal.com.", - "asia-st-romsp.vivoglobal.com.", - "asia-st-sl.vivoglobal.com.", - "asia-st-sysupgrade.vivoglobal.com.", - "asia-stp.vivoglobal.com.", + "asia-southeast2-idm-corp-prd.cloudfunctions.net.", "asia-timer-appstore.vivoglobal.com.", "asia-timesync.vivoglobal.com.", "asia-upload-appstore.vivoglobal.com.", "asia-usrsys-api.vivoglobal.com.", - "asia-vgcmdm-api-tha.vivoglobal.com.", - "asia-vnote.vivoglobal.com.", - "asia-vpushonrt-stsdk.vivoglobal.com.", - "asia-vpushort-stsdk.vivoglobal.com.", "asia-wifi.vivoglobal.com.", - "asia.edge.rms.si.riotgames.com.", "asia.remotepc.com.", "asm-api-golocal-geo-am-teams.trafficmanager.net.", "asm-api-golocal-geo-as-teams.trafficmanager.net.", @@ -1141,15 +1151,11 @@ var FakeECSFQDNs = container.NewMapSet( "asm-api-prod-geo-am-skype.trafficmanager.net.", "asm-api-prod-geo-as-skype.trafficmanager.net.", "asm-api-prod-geo-eu-skype.trafficmanager.net.", - "asr.openssp.ru.", "asset.fwcdn3.com.", "asset.fwpub1.com.", - "asset.fwscripts.com.", - "assets.adobetarget.com.", "assets.api.stairwell.com.", - "assets.pinterest.com.", - "assetshare.basspro.com.", - "assistant-dre.op.hicloud.com.", + "assets.ovid.com.", + "assets2.procore.com.", "astemo-am.spectrum.colortokens.com.", "async-motiondetection-us-1d.oss-us-west-1.aliyuncs.com.", "asyncim.zoom.us.", @@ -1159,71 +1165,74 @@ var FakeECSFQDNs = container.NewMapSet( "atlanta4.remotepc.com.", "atlassianblog.wpengine.com.", "atlsbc04ag1.atl.five9.com.", - "atzscr.itsupport247.net.", "au-prod.asyncgw.teams.microsoft.com.", - "au.footballbros.io.", + "au.ff.avast.sec.miui.com.", "auc-collabrtc.officeapps.live.com.", "auckland.remotepc.com.", + "audienceye.com.", + "audio-private.canva.com.", "audio.vivintsky.com.", + "audioscrobbler.com.", "audiostatlog.cc.easebar.com.", - "augmentation.osi.office.net.", "australiaeast.api.cognitive.microsoft.com.", "auth-l7.bereal.com.", + "auth.esports.rpg.riotgames.com.", "auth.jbisumari.org.", - "auth.laureate.net.", + "auth.riotgames.com.", + "auth.services.adobe.com.", + "automate.itsasap.com.", "autotrack.studyquicks.com.", "avalanche.autotrader.co.uk.", "aws-aus-e-rdvz.g.nssvc.net.", "aws-bz-s-rdvz.g.nssvc.net.", + "aws-us-e-rdvz.g.nssvc.net.", + "axb7sdy1ruuf98ot.apple.holadns.com.", + "axb7sdy1ruuf98ot.apple.martianinc.co.", + "axb7sdy1ruuf98ot.apple.okamiboss.com.", "az-us-sc-features.netscalergateway.net.", "azchicago.remotepc.com.", "azchicago2.remotepc.com.", "azeus1-client-s.gateway.messenger.live.com.", - "azioncdn.net.", "azscus1-client-s.gateway.messenger.live.com.", - "azure.clmbosean.space.", "azwcus1-client-s.gateway.messenger.live.com.", "azwus1-client-s.gateway.messenger.live.com.", "azwus2-client-s.gateway.messenger.live.com.", - "b.vx323.com.", - "b1-nldc1.zemanta.com.", "b1t-nldc1.zemanta.com.", "b2b.filesyscrm.com.", "bablosoft.com.", "backend-l.deepl.com.", + "backend.deepl.com.", "badambiz.com.", "badoo.app.", "bahrain.remotepc.com.", "baishan-cloud.net.", "baltimore.remotepc.com.", "bam.nr-data.net.cdn.cloudflare.net.", - "bancomermovil.com.", "bangalore2.remotepc.com.", "bangalore3.remotepc.com.", "bangalore4.remotepc.com.", "bangkok.remotepc.com.", - "bankozarks-my.sharepoint.com.", + "banners-inventory-weighted-geo.b.hyprmx.com.", "barstoolsports.com.", "bd1cec50-00d1-4ce9-9572-785857419a1e.prmutv.co.", - "bdfed.stitch.mlbinfra.com.", - "bea.gov.", + "bdreporting.com.", + "beacon.aimtell.com.", "beacon.qq.com.", "beacons4.gvt2.com.", "beacons5.gvt2.com.", - "becpsn-my.sharepoint.com.", + "beanstack.com.", "bee.tc.easebar.com.", "beizi.biz.", "belgium.remotepc.com.", "belgrad.remotepc.com.", "bend.remotepc.com.", + "betlive.com.", "bgtfs.transitapp.com.", "bi-tracker-global.rivergame.net.", - "biddr.brealtime.com.cdn.cloudflare.net.", "bidster.net.", "bifrost.vivaldi.com.", "bigdata.talkie-ai.com.", "bik.gov.tr.", - "bisdus-my.sharepoint.com.", "bl6pap003.storage.live.com.", "bl6pap004.storage.live.com.", "black-cat.crypto.com.", @@ -1231,10 +1240,8 @@ var FakeECSFQDNs = container.NewMapSet( "blackbox.dropbox-dns.com.", "block64.com.", "bloomerang.co.", - "bluedot.is.autonavi.com.gds.alibabadns.com.", "bluffdale.remotepc.com.", "blz04pap005.storage.live.com.", - "blz04pap006.storage.live.com.", "bmaus.bumble.com.", "bn02pap001.storage.live.com.", "bnz05pap001.storage.live.com.", @@ -1244,6 +1251,7 @@ var FakeECSFQDNs = container.NewMapSet( "boardgamearena.net.", "bobble.ai.", "bokep.work.", + "bookriot.com.", "books-analytics-events.apple.com.", "books-personalization-server.apple.com.", "booksy.com.", @@ -1257,48 +1265,43 @@ var FakeECSFQDNs = container.NewMapSet( "bratislava.remotepc.com.", "brazilsouth.api.cognitive.microsoft.com.", "breitbart.com.", - "bridge.tonapi.io.", - "broadsimp.site.", + "bridgetonpsnj.aristotleinsight.com.", "broadstreetads.com.", "broker-ws-prod-cag-sg.vasdgame.com.", - "bscedge.com.", - "bsprings.remotepc.com.", + "broker.jagat.io.", "bssrvc66.com.", - "bsw-ig.criteo.com.", "bt.moack.co.kr.", "btrace.qq.com.", + "bubble.io.", "bucharest.remotepc.com.", "bucharest1.remotepc.com.", "budapest.remotepc.com.", - "bugly.qcloud.com.", "bugreport.huorong.cn.", "bundler.nice-team.net.", - "buy.music.apple.com.", "bytecdn.cn.", "bytedance.net.", "c.4dex.io.", - "c.aklamator.com.", "c.fakespot.io.", "c.pub.network.", + "c.tadst.com.", "c1.ttcache.com.", "c2.ttcache.com.", "c2c.wechat.com.", "c3.ttcache.com.", "c4.ttcache.com.", - "c7l.cyberhaven.io.", "c8ee9446-97ed-462f-a5e9-1af66c8e9104.prmutv.co.", "ca-prod.asyncgw.teams.microsoft.com.", - "ca.gov.", + "ca.pinterest.com.", "ca.rogers.rcs.telephony.goog.", "ca000.backblaze.com.", "ca001.backblaze.com.", "ca002.backblaze.com.", + "ca3-excel-collab.officeapps.live.com.", "ca80a1adb12a4fbdac5ffcbc944e9a61.pacloudflare.com.", "cac-collabrtc.officeapps.live.com.", - "cache.dciwx.com.", "cachenetworks.com.", - "caldav.163.com.", "california.remotepc.com.", + "caltech.edu.", "camstore.vsco.co.", "canada.remotepc.com.", "canadacentral.api.cognitive.microsoft.com.", @@ -1310,21 +1313,24 @@ var FakeECSFQDNs = container.NewMapSet( "car-cloud-cn.net.", "cardiff.remotepc.com.", "care.novaicare.com.", + "carrollcountynh.gov.", + "cartsee-form-c.cartx.cloud.", "cartx.cloud.", "carystudio.com.", + "cas-us8.wfs.cloud.", "cat1.hbwrapper.com.", "cat2.hbwrapper.com.", "cat3.hbwrapper.com.", "cavai.com.", "cbs.com.", - "cbsipv4.shuzilm.cn.", "cc.easebar.com.", - "cdml02.contentdm.oclc.org.", "cdn-audio-gcp-media.getepic.com.", "cdn-cname.pendo.io.", "cdn-gcp-media-drm.getepic.com.", "cdn-gcp-media.getepic.com.", - "cdn-gcp.getepic.com.", + "cdn-gpd.x-plarium.com.", + "cdn-prod.seismic.com.", + "cdn-prod.wdesk.com.", "cdn-us.algoliaradar.com.", "cdn-video-gcp-media.getepic.com.", "cdn.adjust.com.", @@ -1333,33 +1339,35 @@ var FakeECSFQDNs = container.NewMapSet( "cdn.chargeafter.com.", "cdn.conveythis.com.", "cdn.deepintent.com.", - "cdn.engine.4dsply.com.", - "cdn.exitbee.com.", - "cdn.flashtalking.com.edgekey.net.", "cdn.ftd.agency.", - "cdn.gowish.com.", "cdn.groupbycloud.com.", "cdn.hw.gcloudcs.com.", + "cdn.ingest-lr.com.", "cdn.instapagemetrics.com.", - "cdn.jst.ai.", - "cdn.mscdirect.com.", + "cdn.komiku.id.", + "cdn.onesignal.com.", "cdn.overleaf.com.", - "cdn.pandora.xiaomi.com.", "cdn.qq.com.", - "cdn.resonate.com.", + "cdn.shopifycdn.net.", "cdn.sierrapacificgroup.com.", "cdn.skcrtxr.com.", "cdn.t-bank-app.ru.", "cdn.tapdb-dev.com.", + "cdn.tbank.ru.", "cdn.us.minga.io.", "cdn.uxfeedback.ru.", - "cdn01.boxcdn.net.", + "cdn.web.prd.q4inc.com.cdn.cloudflare.net.", + "cdn01.humeysha.com.", + "cdn02.humeysha.com.", + "cdn1.roadster.com.", "cdn1.wixdns.net.", + "cdn3.onlineaccess1.com.", "cdngslb.com.", "cdntm.hsbc.co.uk.", "cdnwidget.com.", - "cds.swishapps.ai.", + "cds.glanceapis.com.", "cdsi.signal.org.", + "ceconnection.lww.com.", "cedexis-test.com.", "cedock.com.", "cef.com.br.", @@ -1373,103 +1381,96 @@ var FakeECSFQDNs = container.NewMapSet( "center07.deltaork.com.", "center08.deltaork.com.", "center09.deltaork.com.", + "center10.deltaork.com.", + "center11.deltaork.com.", + "center12.deltaork.com.", + "center13.deltaork.com.", + "center14.deltaork.com.", + "center15.deltaork.com.", + "center16.deltaork.com.", + "center17.deltaork.com.", + "center18.deltaork.com.", + "center19.deltaork.com.", "centralindia.api.cognitive.microsoft.com.", "centralus.api.cognitive.microsoft.com.", "centrexit.com.", - "certs.t-bank-app.ru.", - "cf-lb-web-spectrum.www.deepl.com.", + "cf.iad-03.braze.com.cdn.cloudflare.net.", "cfa.fidelity.com.", - "cgi.twns.qq.com.", + "cfgc.zztfly.com.", + "cform.loyalhealth.com.", "changehealthcare.com.", "chargeafter.com.", - "chat.openai.com.cdn.cloudflare.net.", - "check.ragpets.com.", + "charlotte.remotepc.com.", + "charter.asapp.com.", "check2.lloydsbank.co.uk.", - "check2.ragpets.com.", - "checkout-service.global-e.com.", "chennai.remotepc.com.", - "chi01pap001.storage.live.com.", - "chi01pap002.storage.live.com.", "chicago2.remotepc.com.", "childrens.zoom.us.", "china-dayunlinks.obs.cn-south-1.myhuaweicloud.com.", - "chinacache.net.", - "chinaccl-a.haplat.net.", - "chinaccl-b.haplat.net.", - "chinaccl-c.haplat.net.", "chrome.com.", "chtsite.com.", "chub1.imp.us.contentkeeper.net.", - "chujingapp.com.", + "chubbgroup-my.sharepoint.com.", "cicd-release-api.dalyfeds.com.", "cinarra.com.", + "cinemark.com.", + "cis.citrix.com.", + "cisa.gov.", "cisco.app.box.com.", + "cisco.sharepoint.com.", "citrix-cloud-content.customer.pendo.io.", "citrix-cloud-data.customer.pendo.io.", "citrix-sharefile-content.customer.pendo.io.", "citrix-sharefile-data.customer.pendo.io.", "citrix.com.", "cl-data.ads.heytapmobi.com.", - "classroom6x.gitlab.io.", "claude.ai.", "cleveland.remotepc.com.", - "clevelandclinic-my.sharepoint.com.", - "click-eu.preclknu.com.", - "click.pclk.name.", - "click.preclknu.com.", + "click-v4.mainexdircllk.com.", "clickiocmp.com.", - "clickstrck.com.", "client-log.box.com.", "client-s.gateway.messenger.live.com.", "client-tracking.omiapp.me.", "client-updates.lumu.io.", - "client.mail.163.com.", - "clientlog3.music.163.com.", + "clientlogsf.music.163.com.", "cloud-agent.policypak.com.", "cloud-config-service.rtc.aliyuncs.com.", - "cloud-links.net.", - "cloud-rest.lenovomm.com.", - "cloud.huawei.ru.", "cloud.ibm.com.", "cloud.vmp.onezapp.com.", "cloud.zoom.us.", "cloudbirds.cn.", "clouddata.turbowarp.org.", - "cloudflareperf.com.", + "cloudflare.com.", "cloudlinks.cn.", "cloudscdn.info.", "cm-x.mgid.com.", - "cmgate.vip.qq.com.", "cmpassport.com.", + "cms-assets.teacherspayteachers.com.", "cn-hangzhou.oss-cdn.aliyun-inc.com.", "cn-mib2high-mbbservices.audi-connect.cn.", "cn-mib2plus.mbbservices-1a.audi-connect.cn.", - "cn.gcloudcs.com.", "cname.pendo.io.", "cnplug.ttlock.com.", + "cnt.xhprograms.site.", "cnzz.com.", - "co-vcode-od.vivoglobal.com.", "codacloud.net.", "code.etracker.com.", - "codis-bak.ngb.haplat.net.", - "codis.ngb.haplat.net.", - "collabrtc-geo.rtc.trafficmanager.net.", - "collabrtc.officeapps.live.com.", + "code42.com.", "collect.quickcep.com.", - "collect.trendyol.com.", "collector.quillbot.com.", "collector.vhx.tv.", "collector.wdp.brave.com.", "collector.xhamster.com.", - "collector.xhwear.life.", + "collector.xhamster.desi.", + "collector.xhprograms.site.", "columbus.remotepc.com.", "com.yangyi19.com.", "cometglobal.cf.t3cloud.pb.com.", "cometservd1.pb.com.", + "comicbook.com.", + "commerce.nbcuni.com.", "common-afd.fe.1drv.com.", "common-afdrk.fe.1drv.com.", - "common.ltwebstatic.com.", - "community.thescore.com.", "compiles.overleafusercontent.com.", "config-security.com.", "config.a-m-p.xyz.", @@ -1477,15 +1478,13 @@ var FakeECSFQDNs = container.NewMapSet( "config.config-factory.com.", "config.content-settings.com.", "config.office.net.", + "config.silk.al-array.com.", "config.y5en.com.", "config2.cmpassport.com.", "configdl.teamviewer.com.", - "configuration.apple.com.edgekey.net.", - "connect.garenanow.com.", - "connect.garmin.com.", "connectivitycheck.unisoc.com.", + "connectivitycheck.unisoc.com.c.yundunwaf5.com.", "contacts.zoho.com.", - "contendvc.cnouyi.pizza.", "content.assist.chromeriver.com.", "content.bhrpendo.bamboohr.com.", "content.citizensbankonline.com.", @@ -1496,19 +1495,20 @@ var FakeECSFQDNs = container.NewMapSet( "content.ebanking-services.com.", "content.fisglobal.com.", "content.gap.com.", + "content.guide-app.zoominfo.co.", "content.help.explorelearning.com.", "content.maxconnector.com.", "content.pendo.careporthealth.com.", "content.pendo.follettdestiny.com.", "content.pendo.saashr.com.", - "content.pendo.udsrv.com.", "content.productinsights.blackline.com.", "content.readiness.imaginelearning.com.", - "content22.bancanet.banamex.com.", + "content.ssctech.com.", + "content.tracking.billtrust.com.", "content22.bmo.com.", - "content22.citibankonline.com.", "content22.citicards.com.", "content22.online.citi.com.", + "contentexchange.me.", "contentlmsp.student.atitesting.com.", "context.reverso.net.", "control-out.mna.qq.com.", @@ -1520,10 +1520,7 @@ var FakeECSFQDNs = container.NewMapSet( "core.omiapp.me.", "cosmos.azure.com.", "countly.mail.163.com.", - "coursehero.com.", - "covers.vitalbook.com.", "cpisolutions.com.", - "cpm.adsolut.in.", "cpm.appocean.media.", "cpm.aserve1.net.", "cpm.qortex.ai.", @@ -1535,22 +1532,18 @@ var FakeECSFQDNs = container.NewMapSet( "crashlytics.com.", "crashsight.qq.com.", "crashsight.wetest.net.", + "creative.myavlive.com.", "creator.pdf24.org.", "creditmaven.com.", - "crlocsp.cn.", "croatia.remotepc.com.", "crobox.com.", "crobox.io.", "crossforward.com.", "crowd.transitapp.com.", - "crt.comodoca.com.cdn.cloudflare.net.", - "crutchfield.com.", - "crutchfieldonline.com.", "cs.globalsun.io.", "cs.nex8.net.", "cs.playdigo.com.", "csdn.net.", - "css.dhresource.com.", "cstse02.ultipro.com.", "cstsew02.ultipro.com.", "cstsn02.ultipro.com.", @@ -1558,33 +1551,33 @@ var FakeECSFQDNs = container.NewMapSet( "cti.roku.com.", "ctmail.com.", "cts.appmeta.store.", + "ctt-er.hnzkclouds.com.", "cummins.zoom.us.", + "custom-dialogs.thescore.com.", "customer.homedepot.com.", "cvs.quantummetric.com.", "cwogzftn.usw.stape.io.", + "cybba.solutions.", + "cyberghostvpn.com.", "czechrepublic.remotepc.com.", - "d.applvn.com.", "d.docs.live.net.", "d.pub.network.", "d2fb08da-1c03-4c8a-978f-ad8a96b4c31f.partner.permutive.app.", "d2fb08da-1c03-4c8a-978f-ad8a96b4c31f.prmutv.co.", "d3-pr-cu-tm-secapi.trafficmanager.net.", "d6691a17-6fdb-4d26-85d6-b3dd27f55f08.prmutv.co.", - "d82f7a30-751a-4689-b7e9-19336a89ab46.prmutv.co.", "d837da8d.cloudsrv.minerva-labs.com.", - "da.footballbros.io.", + "da.bridgeoos.com.", "da.mosspf.com.", - "da.toponadss.com.", "dacdn.visualwebsiteoptimizer.com.", "daemon.nanoleaf.me.", + "dal-cdsltm-2b.dataremote.com.", "dallas.remotepc.com.", "dallas2.remotepc.com.", "dallas3.remotepc.com.", "dallas4.remotepc.com.", "dallas5.remotepc.com.", - "dantri.com.vn.", - "dashboard.meraki.com.", - "dashi.163.com.", + "daraz.com.", "data-detect.nie.easebar.com.", "data.analytics.thomsonreuters.com.", "data.analytics.ux.quickbase.com.", @@ -1598,94 +1591,99 @@ var FakeECSFQDNs = container.NewMapSet( "data.guide-app.zoominfo.co.", "data.guides.oncoursesystems.com.", "data.hockeystack.com.", + "data.info.costar.com.", "data.investing.com.", "data.ipd.goto.com.", "data.kuiniuca.com.", "data.meitu.com.", + "data.nelnet.studentaid.gov.", "data.p3nd0.sproutsocial.com.", + "data.pathfinder.gohighlevel.com.", "data.pendo-cdn.pluralsight.com.", "data.pendo-cobalt.westlaw.com.", "data.pendo-tracking.seismic.com.", "data.pendo.careporthealth.com.", + "data.pendo.everfi.net.", + "data.pendo.follettdestiny.com.", "data.pendo.gomotive.com.", + "data.pendo.perfectserve.com.", "data.pendo.saashr.com.", "data.pendo.udsrv.com.", "data.pendoanalytics.dayforcehcm.com.", + "data.productanalytics.coconutcalendar.com.", "data.productinsights.blackline.com.", "data.queryly.com.", "data.readiness.imaginelearning.com.", "data.tracking.billtrust.com.", + "data.traffmonetizer.com.", "data.useranalytics.global.datasite.com.", "data00.adlooxtracking.com.", + "datacollection.uve.weibo.com.", "datasink.cloudlinks.cn.", "datasite.com.", "datatheorem.com.", - "datatrans.com.", "dayunlinks.cn.", - "dc-o.api.leiniao.com.", "dc.ads.linkedin.com.", - "dc.cftls.t.co.", "dc.di.atlas.samsung.com.", "dc.dqa.samsung.com.", "dc.sigmob.cn.", "dc.wondershare.com.", + "dca-cdsltm-2b.dataremote.com.", + "dcs-live-uc1.mp.lura.live.", + "dcs-live-ue1.mp.lura.live.", + "dcs-live-ue4.mp.lura.live.", + "dcs-live-uw1.mp.lura.live.", "dcs-live.mp.lura.live.", "dcs-mcdn.mp.lura.live.", "dcs-png.mp.lura.live.", "dcs-vod.mp.lura.live.", - "dcs.cloud.samknows.com.", - "dcs110-mcdn.mp.lura.live.", "ddata.huntingtonbank.com.", "ddnsclient.ivview.net.", "de-prod.asyncgw.teams.microsoft.com.", "de.dt.rcs.telephony.goog.", "dealmoon.com.", - "debug.nordvpn.com.", "dec-collabrtc.officeapps.live.com.", "decagon.ai.", "delivery.upremium.asia.", - "deliveryhero.io.", "dell-prod.actioniq.mr-in.com.", "delta.quantummetric.com.", - "demeter-int-ecom-collect.trendyol.com.", - "demeter-tr-core-collect.trendyol.com.", "denver.remotepc.com.", "dev.av380.net.", "dev.visualwebsiteoptimizer.com.", + "devc.flysleep.cn.", "devc.zztfly.com.", "deviceapi.ca1.absolute.com.", "deviceops.hstgps.com.", - "dewu.com.", - "dfaklj.tech.", "dhs.gov.", "dialpad.com.", + "diauth.garmin.com.", "dicontent.ckapis.com.", "dict.deepl.com.", - "dict.ntes53.netease.com.", - "dictvip-business.youdao.com.", - "digiapp.vietcombank.com.vn.", "digiboy.ir.", "digitalcardservice.com.", - "dinosaurgame.app.", "dir.4.401402081.west-gcloud.codm.activision.com.", + "direct.quic-core-proxy-useast4-v3.useast4.byteglb.com.", + "direct.quic-proxy-gcpsg-v3.gcpsg.byteglb.com.", + "discord.easebar.com.", "discovery.ringcentral.biz.", "dispatcher.omiapp.me.", "dispatchosglobal.yuanshen.com.", "distservp1.pb.com.", "divide.dalyfeds.com.", + "dl.teamviewer.com.", "dl2.discordapp.net.", "dls-udc.dqa.samsung.com.", "dls.di.atlas.samsung.com.", "dm-us.hybrid.ai.", - "dm.wo.com.cn.", + "dm597cl.cloudatacdn.com.", "dmv.ca.gov.", - "dmwww.geo.dmcdn.net.", "dns-e.ns4v.icu.", "dns-tunnel-check.googlezip.net.", + "dns.adguard.com.", + "dns.cloudflare.com.", "dns.rubyfish.cn.", "dns1.nettica.com.", "dns101.register.com.", - "dns23.llnwi.net.", "doceditor.wrike.com.", "docs.live.net.", "docs.zoom.us.", @@ -1694,20 +1692,18 @@ var FakeECSFQDNs = container.NewMapSet( "donewyork1.remotepc.com.", "donewyork2.remotepc.com.", "donewyork3.remotepc.com.", + "dorkbox.com.", "dosfo1.remotepc.com.", "dosfo2.remotepc.com.", "douyin.starrydyn.com.", + "dowjones-prod.actioniq.mr-in.com.", "download.2.401402081.west-gcloud.codm.activision.com.", - "download.lenovo.com.edgekey.net.", "downloadcenter.genetec.com.", - "doximity-res.cloudinary.com.", + "downloads.preyproject.com.", "dp.barclaysus.com.", "dpf.authorize.net.", - "dpool.sina.com.cn.", "dreame.tech.", "drfdisvc.walmart.com.", - "drsquatch.com.", - "ds-cdn.com.", "ds.gsma.com.", "dsa-eu.hybrid.ai.", "dsm01pap001.storage.live.com.", @@ -1718,15 +1714,11 @@ var FakeECSFQDNs = container.NewMapSet( "dsm01pap009.storage.live.com.", "dsm04pap002.storage.live.com.", "dsp-trk.eskimi.com.", - "dsp-trvm.eskimi.com.", - "dspcluster.adfarm1.adition.com.", - "dspx.tv.", "dstillery.com.", + "dsu-b.shalltry.com.", "dtscout.com.", "dubai.remotepc.com.", "dublin.remotepc.com.", - "dun.163yun.com.", - "dypnsapi.aliyuncs.com.", "dz.cyberhaven.io.", "dzfread.cn.", "e-189.21cn.com.", @@ -1734,6 +1726,7 @@ var FakeECSFQDNs = container.NewMapSet( "e.cdnwidget.com.", "e.userflow.com.", "e.viously.com.", + "e11.ultipro.com.", "e1cef1f0-495f-4973-ba1c-880786e73a66.prmutv.co.", "e2c1.gcp.gvt2.com.", "e2c10.gcp.gvt2.com.", @@ -1874,15 +1867,15 @@ var FakeECSFQDNs = container.NewMapSet( "e43.ultipro.com.", "e488cdb0-e7cb-4d91-9648-60d437d8e491.prmutv.co.", "e5de3d23065c4748b155c28e6fa36f3e.pacloudflare.com.", - "ea.footballbros.io.", "eafddirect.msedge.net.", "eago9.cyberhaven.io.", "easeus.com.", "eastasia.api.cognitive.microsoft.com.", - "eastmoney.com.", "eastus.api.cognitive.microsoft.com.", "eastus2.api.cognitive.microsoft.com.", + "ecatholic.com.", "ecom.wixapps.net.", + "ecosec.on.epicgames.com.cdn.cloudflare.net.", "ecs-gallatin-c2s.trafficmanager.net.", "ed.link.", "edevice.toshiba-solutions.com.", @@ -1890,26 +1883,22 @@ var FakeECSFQDNs = container.NewMapSet( "edgedl.me.gvt1.com.", "edgelocation.ivanticloud.com.", "editor.wix.com.", - "editorial.femaledaily.com.", "edna.id.", - "education-certification.youdao.com.", "ee-share.com.", "efercro.com.", "efs.ultipro.com.", "egateway.ultipro.com.", + "ehmc-my.sharepoint.com.", "ei.dyn-rev.app.", - "eic-ngfts.lge.com.", - "elaracaring.sharepoint.com.", - "elemecdn.com.", - "em-frontend-assets.airtrfx.com.", "emailaptitude.com.", + "emailopen.com.", "empower-retirement.com.", + "empower.com.", "endpointprotector.com.", "engage.wixapps.net.", "engagementapi.skype.com.", "enplug.com.", "ent.box.com.", - "entitlement.auth.adobe.com.", "envoy-ios-prod.getepic.com.", "envysion.com.", "epdg.vowifi.cspire.com.", @@ -1917,9 +1906,9 @@ var FakeECSFQDNs = container.NewMapSet( "eponesh.com.", "eportal.fda.gov.ph.", "epubgame.com.", + "erpapimanagementservice.azure-api.net.", "errortracking.deepl.com.", "esignlive.com.", - "essence.com.", "etracker.com.", "etsv2.datalake.gameloft.com.", "eu-aa.online-metrix.net.", @@ -1927,42 +1916,35 @@ var FakeECSFQDNs = container.NewMapSet( "eu-prod.asyncgw.teams.microsoft.com.", "eu-push.api.intl.miui.com.", "eu.global.market.xiaomi.com.", - "eu.lers.loyalty.riotgames.com.", "eu.statusapi.micloud.xiaomi.net.", "eu.whatfix.com.", - "eu1.badoo.com.", - "eu1.bumble.com.", "eu1a-excel-collab.officeapps.live.com.", "eu1a-powerpoint-collab.officeapps.live.com.", + "eu1a-word-collab.officeapps.live.com.", "eu2a-excel-collab.officeapps.live.com.", "eu2a-powerpoint-collab.officeapps.live.com.", + "eu2a-word-collab.officeapps.live.com.", + "eu2s-excel-collab.officeapps.live.com.", "eu4-excel-collab.officeapps.live.com.", "eu4-powerpoint-collab.officeapps.live.com.", - "euc-collabrtc-geo.rtc.trafficmanager.net.", - "euc-collabrtc.officeapps.live.com.", + "eu4-word-collab.officeapps.live.com.", "euc-excel-collab.officeapps.live.com.", "euc-powerpoint-collab.officeapps.live.com.", "euc-word-collab.officeapps.live.com.", - "euler-saas-cn.heytapmobi.com.", "europe-west1-skyuk-uk-pa-tds-prod.cloudfunctions.net.", "europe.remotepc.com.", - "eus2.his.arc.azure.com.", "euw1.chat.si.riotgames.com.", "eve.gameloft.com.", "event.evtm.53.com.", "events.glanceapis.com.", - "events.polarbyte.com.", - "events.swishapps.ai.", "ex-adreq-asia.vivoglobal.com.", "exappupgrade.vivoglobal.com.", + "excel-collab-geo.ocs.trafficmanager.net.", "excel-collab.officeapps.live.com.", - "exceptionless.io.", - "exit.streamoptim.com.", "exodus.desync.com.", "exp.host.", - "experimentation.deepl.com.", - "exponential.com.", - "ext.wisesecapp.com.", + "expedia.quantummetric.com.", + "expo.olo.com.", "extension.faro.speechify.dev.", "extension.savvy.security.", "external-ams2-1.xx.fbcdn.net.", @@ -1972,6 +1954,7 @@ var FakeECSFQDNs = container.NewMapSet( "external-atl3-3.xx.fbcdn.net.", "external-bos5-1.xx.fbcdn.net.", "external-den2-1.xx.fbcdn.net.", + "external-det1-1.xx.fbcdn.net.", "external-dfw5-1.xx.fbcdn.net.", "external-dfw5-2.xx.fbcdn.net.", "external-hou1-1.xx.fbcdn.net.", @@ -1989,83 +1972,76 @@ var FakeECSFQDNs = container.NewMapSet( "external-man2-1.xx.fbcdn.net.", "external-mia3-1.xx.fbcdn.net.", "external-mia3-2.xx.fbcdn.net.", + "external-mia3-3.xx.fbcdn.net.", "external-msp1-1.xx.fbcdn.net.", "external-ord5-1.xx.fbcdn.net.", "external-ord5-2.xx.fbcdn.net.", "external-ord5-3.xx.fbcdn.net.", "external-phx1-1.xx.fbcdn.net.", - "external-qro1-1.xx.fbcdn.net.", "external-sea1-1.xx.fbcdn.net.", "external-sjc3-1.xx.fbcdn.net.", - "eyewind.cn.", "f002.backblazeb2.com.", + "f590d326fefb4f688b74b268f60589a7.pacloudflare.com.", "f9tg.com.", - "fa000000074.resources.office.net.", - "fa000000132.resources.office.net.", - "fa000000137.resources.office.net.", + "fa3fca7ce79f4b81a39f216e916397d5.pacloudflare.com.", "faas.marktplaats.nl.", - "factor.reg.163.com.", "factset.com.", "fanatics.ent.box.com.", "fanduel.quantummetric.com.", "fanyiegg.youdao.com.", - "fastlane.rubiconproject.com.", "fdccpaadaptor.forddirectservices.com.", + "fdl.flysleep.cn.", "fdl.zztfly.com.", - "fe.xiaohongshu.com.", "fed.federate365.com.", + "feedbackify.com.", "feeder.co.", "feelinsonice.l.google.com.", - "fef.amsub0302.manage.microsoft.com.", - "fef.fxpasu01.manage.microsoft.us.", - "fef.msua09.manage.microsoft.com.", - "fef.msub06.manage.microsoft.com.", - "fef.msub07.manage.microsoft.com.", - "feikua.net.", + "felixistderbeste.de.", "fema.gov.", - "femaledaily.com.", "fengkongcloud.com.", "fgwn01.ultipro.com.", + "fhs.play.king.com.", "fi.telephony.goog.", "field59.com.", - "fifa15.content.easports.com.", - "fikloshk1.com.", "fil-pusa01.app.blackbaud.net.", - "filemail.com.", "files.jotform.com.", - "files.pcpitstop.com.", "filesyscrm.com.", + "filter.revrtb.net.", "finalsite.com.", "finalsite.net.", - "fincen.gov.", + "fire.irs.gov.", + "firebase-auth.vsco.co.", "fireeye.com.", + "firmguard.com.", + "fiscal.treasury.gov.", "five9.com.", - "fiverr-res.cloudinary.com.", - "flashcards.vitalsource.com.", + "fl.cambiumtds.com.", "flashjoin.net.", "flip.to.", "flixcdn.com.", + "floors.nitropay.com.", "flypgs.com.", + "flyspirit.sharepoint.com.", "fm.printaudit.com.", "fn.us.ipqscdn.com.", "fo.iemiq.com.", "foisonad.com.", - "fontawesome.com.cdn.cloudflare.net.", "forcesafesearch.google.com.", - "form.jotform.com.", "forms-eu1.hscollectedforms.net.", "forms-eu1.hsforms.com.", "forms-eu1.hubspot.com.", - "forms.hsforms.com.", "fortisimperious.com.", "fortworth.remotepc.com.", - "foundation-ipv4.youdao.com.", + "fp-be-proximus.rcs.telephony.goog.", "fp-ca-bell.rcs.telephony.goog.", "fp-ca-telus.rcs.telephony.goog.", "fp-de-carrier-vodafone.rcs.telephony.goog.", "fp-de-telefonica.rcs.telephony.goog.", + "fp-gb-3.rcs.telephony.goog.", "fp-gb-ee.rcs.telephony.goog.", + "fp-gb-o2.rcs.telephony.goog.", "fp-us-att.rcs.telephony.goog.", + "fp-us-carrier-dish.rcs.telephony.goog.", "fp-us-carrier-spectrum.rcs.telephony.goog.", "fp-us-cspire.rcs.telephony.goog.", "fp-us-tmobile.rcs.telephony.goog.", @@ -2085,51 +2061,46 @@ var FakeECSFQDNs = container.NewMapSet( "fr-prod.asyncgw.teams.microsoft.com.", "fran.frvr.com.", "francecentral.api.cognitive.microsoft.com.", - "franecki.net.", "frankfurt.remotepc.com.", - "free.publictracker.xyz.", "fremont.remotepc.com.", - "freseniusmedicalcare.com.", "fs.ultiproworkplace.com.", + "ft-sentry.hetangsmart.com.", "ftke02.ultipro.com.", "ftkew02.ultipro.com.", "ftkn01.ultipro.com.", "ftkn02.ultipro.com.", "fxltsbl.com.", + "g.mongobrain.app.", "g10498469755.co.", - "g1584674684.co.", - "g9hc4.cn.", + "g4.algbid.app.", + "g4.bidbrain.app.", + "g4.mongobrain.app.", + "g4.rtbrain.app.", "ga.badambiz.com.", - "galaxyappstore.com.", - "game.zuiqiangyingyu.net.", "gameloft.com.", "gamemonkey.org.", "gamepigeon.net.", "gateway.ultiproworkplace.com.", - "gb-vodafone.rcs.telephony.goog.", "gb.ee.rcs.telephony.goog.", "gb.o2.rcs.telephony.goog.", - "gbc-word-edit.officeapps.live.com.", "gccmod.ecs.office.com.", "gcdn.co.", + "gce-beacons.gcp.gvt2.com.", "gcloud.qq.com.", "gcloudcs.com.", "gcloudsdk.com.", "gcs.sc-cdn.net.", - "gdc-reqs-a.ngb.haplat.net.", "gdid.datalake.gameloft.com.", "geappl.io.", "geckoterminal.com.", - "geico-sync.quantummetric.com.", "geniusmonkey.com.", "geo-dra.platform.hicloud.com.", - "geoip.apps.getjoy.ai.", "geoplugin.net.", - "geovn0mhn4u98k.josyliving.com.", "germanywestcentral.api.cognitive.microsoft.com.", + "get-xmore-link3s.com.", "getadmiral.com.", "getbutton.io.", - "getjoy.ai.", + "getgreenshot.org.", "gettopple.com.", "getui.net.", "gifer.com.", @@ -2137,71 +2108,72 @@ var FakeECSFQDNs = container.NewMapSet( "gigabyte.com.", "gitlab.com.", "gla.gameloft.com.", - "global-tokenserver-la.headline.uodoo.com.", "global.datasite.com.", "globalsigncdn.com.cdn.cloudflare.net.", "globalsun.io.", + "globalvideoquery.fortinet.net.", "gme.qcloud.com.", - "gnc.com.", - "gnss-eph.oss-cn-hangzhou.aliyuncs.com.", - "go.activengage.com.", + "gmfinancial.com.", + "gms.machineq.net.", "go.pdfforge.org.", "goaffpro.com.", + "gohighlevel.com.", "gonines.com.", "google.org.", "googledomains.com.", "googlesync.permutive.com.", + "googls-api.com.", "gosport.remotepc.com.", "gov-bam.nr-data.net.", "gov-bam.nr-data.net.cdn.cloudflare.net.", - "gowish.com.", "grab.zoom.us.", "gravite.net.", "gravitec.net.", "greenville.remotepc.com.", + "grist.org.", "group-ib.com.", "grow.me.", "grpc.vivintsky.com.", - "gslb.xiaohongshu.com.", - "gspe19-ssl.ls.apple.com.", + "gs.eponesh.com.", "gtm.deepl.com.", "gtm.vividseats.com.", - "gw5.push.mcp.weibo.cn.", + "guidingcare.com.", "gwadar.cn.", "gyazo.com.", "gz0.googleusercontent.com.", "h-5h8i3ud8.online-metrix.net.", "h-adp.online-metrix.net.", - "h-bestbuy.online-metrix.net.", "h-discover.online-metrix.net.", - "h-e04kqxof.online-metrix.net.", "h-ebay.online-metrix.net.", "h-homedepot.online-metrix.net.", "h-online.citi.online-metrix.net.", + "h-rbs-bank.online-metrix.net.", "h-sdk.online-metrix.net.", "h-signifyd.online-metrix.net.", "h-v60nf4oj-qfp.online-metrix.net.", "h-walmart.online-metrix.net.", "h.app.wdesk.com.", "h.online-metrix.net.", - "h30-deploy.obviyo.net.", + "h104216-dcdn.mp.lura.live.", + "h104216-fcdn.mp.lura.live.", + "h104216-gcdn.mp.lura.live.", + "h104216-hcdn.mp.lura.live.", + "h104216-kcdn.mp.lura.live.", + "h107833-ecdn.mp.lura.live.", "h64.online-metrix.net.", + "h72-ms-prod.netease.com.", "hamina.remotepc.com.", "hanoi.remotepc.com.", "hapsee.cn.", "hapseemate.cn.", + "harlandclarke.com.", "harry.lu.", "hawaii.remotepc.com.", "hbopenbid-apac-v2.pubmnet.com.", "hbwrapper.com.", - "hds.ksc.kaspersky.com.", "hecheck.bitmyanmar.info.", - "hello.idocdn.com.", "helloid.com.", - "help-ar.apple.com.edgekey.net.", - "henryco-my.sharepoint.com.", "hetangsmart.com.", - "heylink.me.", "heytapdownload.com.", "heytapmobi.com.", "hft-prod.actioniq.mr-in.com.", @@ -2213,158 +2185,160 @@ var FakeECSFQDNs = container.NewMapSet( "hk.gcloudcs.com.", "hk.ntp.org.cn.", "hk.wechat.com.", + "hnzkclouds.com.", "holykjvbible.com.", "home.highwire.org.", + "homeadvisor.com.", + "homedepot-app.quantummetric.com.", "homedepot.quantummetric.com.", "honeywell.com.", + "hongkong.remotepc.com.", "hornetsecurity.com.", "hpfal.deepl.com.", - "hpkaj.deepl.com.", "hpplay.cn.", + "hpslb.deepl.com.", + "hrsa.gov.", "hstgps.com.", "html.it.", "html5.qq.com.", - "htms.heytapmobi.com.", "httpdns.y5en.com.", "huan.tv.", "huaweicloud.com.", "hubble.netease.com.", - "hubcloud.com.cn.", "hubspotemail.net.", "huion.cn.", - "hulkapps-wishlist.nyc3.digitaloceanspaces.com.", + "humix.com.cdn.cloudflare.net.", "huorong.cn.", "hw.gcloudcs.com.", "hwapps-o.api.leiniao.com.", "hwtvmanage-o.api.leiniao.com.", "hybrid.ai.", - "hzmklvdieo.com.", "i-sg01a.ocloud.heytapmobi.com.", "i.comfrt.com.", + "i.discogs.com.", "i.inspi-dsp.com.", "i.magazine.heytapmobi.com.", "i.one-bid.com.", "i.qchannel03.cn.", - "i6-vn.weather.oppomobile.com.", + "i.voe.sx.", + "i.vsco.co.", "ialicdn.com.", "iappscontent.courts.state.ny.us.", + "iberiaparishsdla.aristotleinsight.com.", "ibm.account.box.com.", "ibm.box.com.", "ibsrv.net.", - "icalendars.app.", - "icanhazip.tacticalrmm.io.", "icare.infranetgroup.com.", + "ice.gov.", "ichano.cn.", "iconmonstr.com.", "id-telkom.rcs.telephony.goog.", "id-timer-appstore.vivoglobal.com.", - "id.xhwear.life.", + "id.3plearning.com.", + "id.xhprograms.site.", "idahofalls.remotepc.com.", "iddallas1.remotepc.com.", "iddenver.remotepc.com.", - "ideafyi.oss-us-west-1.aliyuncs.com.", "idlondon.remotepc.com.", "idnewyork1.remotepc.com.", - "idocdn.com.", + "idqqimg.com.", "idr.cdnwidget.com.", "ids.cdnwidget.com.", "iemiq.com.", "igame.gcloudcs.com.", "ijoysoftconnect.com.", - "ikki.youdao.com.", - "illuminate.zendesk.com.", "ilog-sea-aliyun.alipayplus.com.", + "im.vsco.co.", + "image-auto-captioning-computer-vision.cognitiveservices.azure.com.", "image-sc.richrelevance.com.", - "image-service.onefootball.com.", "image.myqcloud.com.", "image.online.adp.com.", + "images.fptplay53.net.", + "images.spot.im.", "imagetrendelite.com.", - "imeclient.openspeech.cn.", - "img.cdn4dd.com.", - "img.onesignal.com.", - "img.shields.io.", + "imap.earthlink.net.", "img2021.navyfederal.org.", "img9.target.com.", "imghst-de.com.", "imgs.signifyd.com.", - "imotech.site.", - "imou-sg-ali-online-paas-iot-private-picture.oss-ap-southeast-1.aliyuncs.com.", - "imou-sg3-ali-online-paas-private-picture.oss-ap-southeast-1.aliyuncs.com.", - "imoulife.com.", + "immomo.com.", + "imodules.com.", + "imoim.net.", "imp.admeking.com.", "impactify.media.", "imptrk.siteplug.com.", + "ims-prod07.adobelogin.com.", "in-api.asm.skype.com.", "in-prod.asyncgw.teams.microsoft.com.", - "in-vpushonrt-stsdk.vivoglobal.com.", "in.global.market.xiaomi.com.", "in.gov.", + "in.pinterest.com.", "in.visitors.live.", "inc-collabrtc.officeapps.live.com.", "indianapolis.remotepc.com.", "inf.miui.com.", + "infoldgames.com.", + "infrastructure-command-api.newrelic.com.", + "ingov.zendesk.com.", "inneraudioms.cc.easebar.com.", "innity.com.", "innity.net.", - "ins-tgrfs7t3.ias.tencent-cloud.net.", + "insightadz.com.", + "insights-collector.cell.nr-data.net.", "inskinad.com.", "inspi-dsp.com.", - "inspirebrands-sync.quantummetric.com.", "inspirebrands.quantummetric.com.", "instantmessaging-pa-jms-ap.googleapis.com.", "instantmessaging-pa-jms-eu.googleapis.com.", "instantmessaging-pa-jms-preprod-us.googleapis.com.", "instantmessaging-pa-jms-us.googleapis.com.", "instatus.com.", - "internetdownloadmanager.com.", - "intl-im-conn.iq.com.", "intuit.zoom.us.", "ios.crashsight.wetest.net.", "iosack.tuisong.baidu.com.", + "iot.hillrom.com.", "iot.services-edge.paloaltonetworks.com.", + "iowa-uscentral-replayclient-x20na.easebar.com.", "iowa.remotepc.com.", "ip-api.com.", + "ip-tools.tanjingpaas.com.", "ipfs.io.", - "ipmeta.io.", "iprofiles.apple.com.", "iprom.net.", "ipv4.cadc.absolute.com.", "ipv4.geojs.io.", + "ipv4.rer.lol.", "ipv4.sdiptest.com.", "ipv4.tracker.harry.lu.", "ipv6-3.sdiptest.com.", + "ipv6.shuzilm.cn.", "iq.com.", "iscorp.com.", "istanbul.remotepc.com.", "istatmenus.app.", + "isuzu01-my.sharepoint.com.", + "ita-free.www.deepl.com.", "italynorth.api.cognitive.microsoft.com.", "itc.cn.", "itch.io.", "itch.zone.", - "itemorder.com.", "itm.cloud.com.", "itoon.org.", "ittpx.eskimi.com.", "itzmx.com.", "ivalua.com.", - "ivt.np.community.playstation.net.", "ivview.com.", "ivview.net.", "izatcloud.net.", - "j.6sc.co.", "jabfm.org.", - "jackhenry.com.", "japanwest.api.cognitive.microsoft.com.", - "jbhunt-my.sharepoint.com.", + "jazzpharmaceuticals.cyberhaven.io.", "jdadelivers.com.", "jdcloud.com.", "jhahosted.com.", - "jishiyuchat.com.", + "jigsaw.vitalsource.com.", "jitsu.com.", "johannesburg.remotepc.com.", - "josyliving.com.", - "jotfor.ms.", - "journals.plos.org.", "jp-prod.asyncgw.teams.microsoft.com.", "jp.cinarra.com.", "jp1.chat.si.riotgames.com.", @@ -2381,40 +2355,34 @@ var FakeECSFQDNs = container.NewMapSet( "js-eu1.hsleadflows.net.", "js-eu1.hubspot.com.", "js.eruptr.io.", - "js.volumental.com.", - "jsonatm.broker.tplay.qq.com.", + "jsapi.lightboxcdn.com.", "jss.starbucks.com.", "jssprod-starbucks.trafficmanager.net.", "jtt.rsmjournals.com.", "junglefrog.com.", "justice.gov.", "jxappgtw.jhahosted.com.", - "k.163.com.", - "k.apiairasia.com.", - "k8s1-event-tracker-fr.lb.indexww.com.", - "k8s1-event-tracker-la.lb.indexww.com.", "k8s1-event-tracker-ny.lb.indexww.com.", "k8s1-event-tracker-sg.lb.indexww.com.", - "k8s1-event-tracker-sj.lb.indexww.com.", - "k8s1-event-tracker-ty.lb.indexww.com.", "k8s1-event-tracker-va.lb.indexww.com.", - "k8s1-la-ext-haproxy.lb.indexww.com.", "k8s1-ny-ext-haproxy.lb.indexww.com.", "k8s1-sj-ext-haproxy.lb.indexww.com.", + "ka-aus1.contentsquare.net.", "kajicam.com.", + "kids.britannica.com.", "kiev.remotepc.com.", + "kiprotect.com.", "kirkland.zoom.us.", - "kitchenaid.com.", + "kit-uploads.fontawesome.com.", "kiwisizing.com.", "klagenfurt.remotepc.com.", "knightlab.com.", "knoxville.remotepc.com.", "komect.com.", "komiku.id.", - "kopipait.store.", "koreacentral.api.cognitive.microsoft.com.", + "kps2yp94aqw5yi5d2.ay.delivery.", "kstatic.googleusercontent.com.", - "kumcams.com.", "kunlunaq.com.", "kunlunar.com.", "kunlunca.com.", @@ -2427,7 +2395,6 @@ var FakeECSFQDNs = container.NewMapSet( "kurogame.xyz.", "kwimgs.com.", "kzhi.tech.", - "l.deepintent.com.", "la.remotepc.com.", "la1.chat.si.riotgames.com.", "la10.remotepc.com.", @@ -2436,7 +2403,6 @@ var FakeECSFQDNs = container.NewMapSet( "la2.remotepc.com.", "la3.remotepc.com.", "la4.remotepc.com.", - "la4lbg.uae2grp.ucweb.com.", "la8.remotepc.com.", "la9.remotepc.com.", "labtech.myitpros.com.", @@ -2444,24 +2410,23 @@ var FakeECSFQDNs = container.NewMapSet( "lalapush.com.", "lan.sdk.linkedin.com.", "lansing.remotepc.com.", - "lansweeper.com.", "larksuite.com.", + "last.fm.", + "lavasoft.com.", "lax.remotepc.com.", "layerxsecurity.com.", "lazpay-fe-kyc-module-file.oss-ap-southeast-1.aliyuncs.com.", - "lb-sin.mgid.com.", "ldap.google.com.", - "ldgslb.com.", "ldmnq.com.", "leadmanagerfx.com.", + "legacy.com.cdn.cloudflare.net.", + "lego.report-uri.com.", "leihuo.netease.com.", "leiniao.com.", "levect.com.", "level10gc.com.", - "leveldata.poki.io.", - "lianmeng.360.cn.", + "lexicon.33across.com.", "liblynx.com.", - "libretexts.org.", "license.gonative.io.", "license.litespeedtech.com.", "license.unity3d.com.", @@ -2472,54 +2437,37 @@ var FakeECSFQDNs = container.NewMapSet( "lima.remotepc.com.", "lineicons.com.", "linguee.com.", - "link-ga-hz-azure.yunxinfw.com.", "link-vision-picture-sg-v2.oss-ap-southeast-1.aliyuncs.com.", + "linkedin.com.cdn.cloudflare.net.", "lisbon.remotepc.com.", "lissabon.remotepc.com.", "list.tronlink.org.", "litedev.sgp.hik-connect.com.", "litespeedtech.com.", + "littler-my.sharepoint.com.", "live.126.net.", - "live.ngb.haplat.net.", "live.shopee.com.br.", - "live2.ngb.haplat.net.", - "live3.ngb.haplat.net.", - "live4.ngb.haplat.net.", - "live5.ngb.haplat.net.", - "live6.ngb.haplat.net.", - "livect.haplat.net.", - "livekilleenisd.sharepoint.com.", - "livemediahost.com.", - "liveoversea10.ngb.haplat.net.", - "liveoversea5.ngb.haplat.net.", - "liveoversea6.ngb.haplat.net.", - "liveoversea7.ngb.haplat.net.", - "liveoversea8.ngb.haplat.net.", - "liveoversea9.ngb.haplat.net.", - "liverpool.groupbycloud.com.", + "liveutmb-my.sharepoint.com.", "ljubljana.remotepc.com.", "loaduk.betfred.com.", "loandepot.zoom.us.", "local.adguard.org.", - "local.info.g9hc4.cn.", "log-api.newrelic.com.cdn.cloudflare.net.", - "log-auth.zztfly.com.", + "log-auth.flysleep.cn.", "log.getadblock.com.", "log.mile.so.", "log.webmaxlogger.net.", - "log.xiaoyi.com.", "log.zoom.us.", - "log1.cmpassport.com.", "log2.cmpassport.com.", "logging-service-prod.getepic.com.", "logging.mp.lura.live.", "loggly.com.", - "login.ringcentral.com.", + "login.cbc.ca.", + "login.pointclickcare.com.", "login.teamviewer.com.", - "login.vivaldi.net.", + "login6.cambiumtds.com.", + "logus.xiaoyi.com.", "logx.optimizely.com.", - "lokalise.co.", - "lol.sw.game.qq.com.", "london.remotepc.com.", "london2.remotepc.com.", "london3.remotepc.com.", @@ -2528,42 +2476,46 @@ var FakeECSFQDNs = container.NewMapSet( "london6.remotepc.com.", "london8.remotepc.com.", "long.tv.", - "look.360.cn.", + "lotus-dsp.ru.", "lpa.ds.gsma.com.", "lplfinancial.app.box.com.", "lsagentrelay.lansweeper.com.", "ltfl.librarything.com.", - "ludashi.com.", + "ltmpt.id.", "luxembourg.remotepc.com.", + "ly200-cdn.com.", "lycraservice-pa-cam-prod.googleapis.com.", "lyric.alarmnet.com.", + "m.sogo.com.", "m1.ubianet.com.", - "m110601-fcdn.mp.lura.live.", "m2.ubianet.com.", - "m3.d.meituan.net.", + "m2uenlnen6g1spdnv2516hv5f56rd3hm-data.customer.pendo.io.", "m3.ubianet.com.", "m4.ubianet.com.", "m5.ubianet.com.", + "m6.d.meituan.net.", "m6.ubianet.com.", "macclog-as.rj.link.", "madrid.remotepc.com.", "maers.adrs.org.cn.", - "magichue.net.", + "magic.link.", "maidenhead.remotepc.com.", "mail.superhuman.com.", "mailinblue.com.", "maintenanceconnection.com.", "malware-filter.gitlab.io.", "mam.manage.microsoft.us.", - "manage-selfhost.microsoft.com.", "manage.wix.com.", + "manage1.esna.com.", + "management-2.dataremote.com.", "management.azure.com.", "management.privatelink.azure.com.", "manassas.remotepc.com.", "manchester.remotepc.com.", - "marketplace.canva.com.", + "markets.jpmorgan.com.", "marmot-cloud.com.", "marseille.remotepc.com.", + "marvelrivals.com.", "master1.teamviewer.com.", "master10.teamviewer.com.", "master11.teamviewer.com.", @@ -2573,18 +2525,20 @@ var FakeECSFQDNs = container.NewMapSet( "master15.teamviewer.com.", "master16.teamviewer.com.", "master2.teamviewer.com.", - "master3.teamviewer.com.", "master4.teamviewer.com.", "master5.teamviewer.com.", "master6.teamviewer.com.", "master7.teamviewer.com.", "master8.teamviewer.com.", "master9.teamviewer.com.", - "match.adfarm1.adition.com.", - "max-l.mediav.com.", + "match.contentexchange.me.", + "matrix.netease.com.", + "maxfinishseveral.com.", "mbboauth-1c.prd.cn.vwg-connect.cn.", "mcallen.remotepc.com.", + "mcds.dalyfeds.com.", "mcount.easebar.com.", + "mdap.tngdigital.com.my.", "meari-oss-us.oss-us-west-1.aliyuncs.com.", "meari-us.oss-us-west-1.aliyuncs.com.", "medellin.remotepc.com.", @@ -2600,12 +2554,11 @@ var FakeECSFQDNs = container.NewMapSet( "media-bos5-1.cdn.whatsapp.net.", "media-bru2-1.cdn.whatsapp.net.", "media-cdg4-1.cdn.whatsapp.net.", - "media-cdg4-2.cdn.whatsapp.net.", - "media-cdg4-3.cdn.whatsapp.net.", "media-cgk1-1.cdn.whatsapp.net.", "media-cgk1-2.cdn.whatsapp.net.", "media-cgk2-1.cdn.whatsapp.net.", "media-den2-1.cdn.whatsapp.net.", + "media-det1-1.cdn.whatsapp.net.", "media-dfw5-1.cdn.whatsapp.net.", "media-dfw5-2.cdn.whatsapp.net.", "media-dus1-1.cdn.whatsapp.net.", @@ -2628,7 +2581,6 @@ var FakeECSFQDNs = container.NewMapSet( "media-iad3-1.cdn.whatsapp.net.", "media-iad3-2.cdn.whatsapp.net.", "media-ist1-1.cdn.whatsapp.net.", - "media-ist1-2.cdn.whatsapp.net.", "media-kul2-1.cdn.whatsapp.net.", "media-kul2-2.cdn.whatsapp.net.", "media-kul3-1.cdn.whatsapp.net.", @@ -2641,13 +2593,12 @@ var FakeECSFQDNs = container.NewMapSet( "media-lhr6-2.cdn.whatsapp.net.", "media-lhr8-1.cdn.whatsapp.net.", "media-lhr8-2.cdn.whatsapp.net.", - "media-lim1-1.cdn.whatsapp.net.", - "media-lis1-1.cdn.whatsapp.net.", "media-los2-1.cdn.whatsapp.net.", "media-man2-1.cdn.whatsapp.net.", "media-mct1-1.cdn.whatsapp.net.", "media-mia3-1.cdn.whatsapp.net.", "media-mia3-2.cdn.whatsapp.net.", + "media-mia3-3.cdn.whatsapp.net.", "media-msp1-1.cdn.whatsapp.net.", "media-mty2-1.cdn.whatsapp.net.", "media-muc2-1.cdn.whatsapp.net.", @@ -2660,7 +2611,6 @@ var FakeECSFQDNs = container.NewMapSet( "media-qro1-2.cdn.whatsapp.net.", "media-scl2-1.cdn.whatsapp.net.", "media-sea1-1.cdn.whatsapp.net.", - "media-shxixix-yijia21.sn7oss.ctyunxs.cn.", "media-sin11-1.cdn.whatsapp.net.", "media-sin11-2.cdn.whatsapp.net.", "media-sin2-1.cdn.whatsapp.net.", @@ -2670,60 +2620,51 @@ var FakeECSFQDNs = container.NewMapSet( "media-sin6-3.cdn.whatsapp.net.", "media-sin6-4.cdn.whatsapp.net.", "media-sjc3-1.cdn.whatsapp.net.", - "media-sof1-1.cdn.whatsapp.net.", "media-vie1-1.cdn.whatsapp.net.", - "media.defense.gov.", - "media.pearsoncmg.com.", + "media-yyz1-1.cdn.whatsapp.net.", "media.ringcentral.com.", "media.superhuman.com.", "media.tinkoff.ru.", "mediacloud.xiaohongshu.com.", "mediav.com.", "melbourne.remotepc.com.", - "members.onepeloton.com.", "memphis.remotepc.com.", - "messaging-api.shopifyapps.com.", "metadata.decagon.ai.", - "metric-api.cell.nr-data.net.", - "metric.picodiglobal.com.", "metrics-dre.dt.dbankcloud.cn.", "metrics-dre.dt.dbankcloud.com.", "metrics-dre.dt.hihonorcloud.com.", + "metrics.aimetric.net.", "metrics5.data.hicloud.com.", "mexicocity.remotepc.com.", + "mf.b37mrtl.ru.", "mgbsdknaeast.matrix.easebar.com.", "mgtv.com.", "miami.remotepc.com.", "miami2.remotepc.com.", - "miami3.remotepc.com.", - "miami4.remotepc.com.", "mib2clu8.car-cloud-cn.net.", "microad.jp.", - "microfun.cn.", "microvirt.com.", "mid4.linkedin.com.", "mida.so.", - "migu.cn.", + "middleburycsin.aristotleinsight.com.", "milan.remotepc.com.", + "milestoneinternet.com.cdn.cloudflare.net.", + "milwaukeetool.com.", "mimir2.vivaldi.com.", "min-api.cryptocompare.com.", - "mini.browser.360.cn.", "mintkeyboard.com.", - "mirror5.internetdownloadmanager.com.", "mixi.media.", - "mobavenue.com.", - "mobile-api.opentable.com.", - "mobile-bank.cdn-tinkoff.ru.", + "mm.fcix.net.", "mobile-collector.newrelic.com.cdn.cloudflare.net.", "mobile-l7.bereal.com.", "mobile-protect-api.securetheorem.com.", - "mobile.shuzilm.cn.", - "mobileapi.us.afterpay.com.", "mobiledataplan-pa.googleapis.com.", "mobilemaps-pa-gz.googleapis.com.", "mobilemaps.googleapis.com.", + "mobilesettings1.okta-emea.com.", "modelportrait.xiaohongshu.com.", "modesto.remotepc.com.", + "mon0-misc-lf.amemv.com.", "moni-onrt-stsdk.vivo.com.cn.", "monitor.fraudblocker.com.", "monitoring.getelevar.com.", @@ -2734,61 +2675,62 @@ var FakeECSFQDNs = container.NewMapSet( "morningstar.zoom.us.", "motiondetection-us-1d.oss-us-west-1.aliyuncs.com.", "motiondetection-us-7d.oss-us-west-1.aliyuncs.com.", - "motiondetection-us.oss-us-west-1.aliyuncs.com.", "mouser.com.", - "mp.360.cn.", - "mrisoftware.com.", - "ms.applvn.com.", + "mqtt.strongline.commure.com.", "ms1app.pb.com.", - "ms4.applvn.com.", "msdl.microsoft.com.", "msf.3g.qq.com.", "msg-img-hk.oss-cn-hongkong.aliyuncs.com.", - "mssdk22-normal-useast1a.tiktokv.com.", - "mssdk22-normal-useast2a.tiktokv.com.", - "mtrace.qq.com.", + "msp.meituan.com.", + "mstate-my.sharepoint.com.", "mumbai.remotepc.com.", + "mumu.nie.netease.com.", "munich.remotepc.com.", "muscache.cn.", "music.163.com.163jiasu.com.", + "musical.ly.", "musicps.p2p.qq.com.", "musicpunch.p2p.qq.com.", "musicstylingonline.com.", - "mw.footballbros.io.", + "musteritemsilcisi.co.", + "mvision-us.moodmedia.com.", "mx-vcode-od.vivoglobal.com.", "mx.amx.rcs.telephony.goog.", - "mx.pinterest.com.", "mxp-pusa01.app.blackbaud.net.", "mxptint.net.", "my.canva.site.", - "my.dealersocket.com.", "my.getadmiral.com.", "my.jbi.global.", "my.nalpeiron.com.", + "my.olo.com.", + "mydrive.connect.aig.", "myisolved.com.", "myporn.club.", "myqcloud.com.", - "mystery-game-tile.poki.io.", - "myworkdaycdn.com.cn.", - "n-1-ashx.ad-m.net.", - "n-2-ashx.ad-m.net.", - "n-7-ashx.ad-m.net.", + "myvscloud.com.", + "n22.ultipro.com.", "n33.ultipro.com.", "n35.ultipro.com.", - "na113.epm.cyberark.com.", + "na.account.riotgames.com.", + "na.vg.ac.pvp.net.cdn.cloudflare.net.", "na2.chat.si.riotgames.com.", "nab.com.au.", "najva.com.", - "nam.veta.naver.com.", + "nalpeiron.com.", "namequery.com.", "naperville.remotepc.com.", + "napps.zoom.us.", "nashville.remotepc.com.", "nationalmap.gov.", - "nationalreview.com.", - "nativecos.com.", - "nc-pod1-smp-device.apple.com.", + "native.binance.info.", + "navan.com.", + "nawzryhwatm.broker.amsoveasea.com.", + "nbxc.com.", "nc.com.", "ncentral.centrexit.com.", + "nclkbnh4ojwsxl77.apple.holadns.com.", + "nclkbnh4ojwsxl77.apple.martianinc.co.", + "nclkbnh4ojwsxl77.apple.okamiboss.com.", "nearme.com.cn.", "nechicago.remotepc.com.", "neonataltherapists.com.", @@ -2801,7 +2743,6 @@ var FakeECSFQDNs = container.NewMapSet( "network-check.sybo.net.", "newcontinuum.net.", "neworleans.remotepc.com.", - "news-af-2.feednews.com.", "news-af.feednews.com.", "news-client.apple.com.", "news-events.apple.com.", @@ -2812,24 +2753,22 @@ var FakeECSFQDNs = container.NewMapSet( "newyork.remotepc.com.", "newyork2.remotepc.com.", "newyork3.remotepc.com.", - "nex.163.com.", + "nexrtb.com.", "nexstar.amp.permutive.com.", - "nextinsure.com.", "nexx360.io.", "ng1.angus.mrisoftware.com.", "ngb.haplat.net.", + "nhwimp.izooto.com.", "nice-team.net.", "nie.netease.com.", - "ninjakiwi.com.", - "nist.gov.", "nitroapps.co.", "nitropay.com.", + "nitrotype.com.", "noc.computerhelpnj.com.", "node.setupad.com.", "nokia.com.", "nordcurrent.com.", "northcentralus.api.cognitive.microsoft.com.", - "northerntool.com.", "northeurope.api.cognitive.microsoft.com.", "norwayeast.api.cognitive.microsoft.com.", "notes-analytics-events.apple.com.", @@ -2882,6 +2821,7 @@ var FakeECSFQDNs = container.NewMapSet( "ntes53.netease.com.", "ntp.aliyun.com.", "ntp.arlo.com.", + "ntp.arlo.com.cdn.cloudflare.net.", "ntp.org.cn.", "ntp1.aliyun.com.", "ntp1.huan.tv.", @@ -2890,6 +2830,7 @@ var FakeECSFQDNs = container.NewMapSet( "ntp4.aliyun.com.", "ntp5.aliyun.com.", "ntp6.aliyun.com.", + "ntp7.aliyun.com.", "nuremberg.remotepc.com.", "nv.gov.", "nvu-prd.mqtt.ivanticloud.com.", @@ -2897,61 +2838,62 @@ var FakeECSFQDNs = container.NewMapSet( "nwr.mmcdn.com.", "nwr.static.mmcdn.com.", "nws-platform.zoom.us.", + "nws.zoom.us.", "nwsalert.onelouder.com.", "nycourts.gov.", "nycrt.marphezis.com.", "obihai.telephony.goog.", "obs.ap-southeast-3.myhuaweicloud.com.", - "obs.line-apps.com.", "observability-l7.bereal.com.", "observability.bereal.com.", "obsproject.com.", "obus-dc20058-cn.heytapmobi.com.", "obus-dc20123-cn.heytapmobi.com.", + "obus-dc20157-cn.heytapmobi.com.", "obus-dctech-cn.heytapmobi.com.", "oclc.org.", "ocloud.oppomobile.com.", "ocps-xfer.kronos.net.", "ocsp.identrust.com.", + "odds.dc1.ovid.com.", "odrs.fda.gov.ph.", "odw7bf.dood.video.", "oec22-normal-alisg.tokopediax.com.", + "oec22-normal-useast2a.tiktokv.com.", "office.microsoft.com.", - "officepreviewredir.microsoft.com.", - "offline-pkg-api.dalyfeds.com.", "ogma-l7.bereal.com.", - "ogsvc.pgoriginad.com.", "ojp.gov.", "okko.tv.", "ollama.com.", + "omaha.formlabs.com.", "omiapp.me.", "omitech.site.", "on-hwapps-o.api.leiniao.com.", + "one-line.com.", "one.newrelic.com.", + "one.one.one.one.", "onekey1.cmpassport.com.", "oneplus.net.", "onethingpcs.com.", "onezapp.com.", - "online-store-web.shopifyapps.com.", - "online.kugou.com.", "onlinewebfonts.com.", "op.mykonf.com.", "opamarketplace.com.", - "open.acgtracker.com.", "open.oppomobile.com.", + "openathens.ovid.com.", "opencmp.net.", "opendsp.ru.", "opex-service-cn.allawntech.com.", "oppo.com.", "oppomobile.com.", + "opps-api.getwarmly.com.", "optimize.ulinq.asia.", "optimize.urekamedia.com.", "optimizely.com.", + "optumads.com.", "orangehire.com.au.", - "orderdeadline.com.", "oregon.remotepc.com.", "origin.fe-image-cache-ttp.useast8.byteglb.com.", - "originplatform.com.", "orlando.remotepc.com.", "os7lm.6kvses.com.", "osaka.remotepc.com.", @@ -2963,23 +2905,15 @@ var FakeECSFQDNs = container.NewMapSet( "oss-us-east-1.aliyuncs.com.", "oss-us-west-1.aliyuncs.com.", "otc.t-systems.com.", - "otlp.nr-data.net.", "ott.deepl.com.", "overleaf.com.", "overleafusercontent.com.", - "overseasccl-a.haplat.net.", - "overseasccl-b.haplat.net.", - "overseasccl-c.haplat.net.", - "overseasccl-d.haplat.net.", - "overseasccl-major-a.haplat.net.", - "overseasccl-major-b.haplat.net.", - "overseasccl-major-c.haplat.net.", "ovh.maxhost.io.", "p.adlooxtracking.com.", "p.vsco.co.", "p0-pu-private-useast8.tiktokv.us.", "p107609.cedexis-test.com.", - "p18-buy.itunes.apple.com.", + "p14.d.meituan.net.", "p20304.cedexis-test.com.", "p20305.cedexis-test.com.", "p20306.cedexis-test.com.", @@ -2989,12 +2923,10 @@ var FakeECSFQDNs = container.NewMapSet( "p20311.cedexis-test.com.", "p20314.cedexis-test.com.", "p20315.cedexis-test.com.", - "p23-buy.itunes.apple.com.", "p2p-cal-2.anker-in.com.", "p2p-cal-3.anker-in.com.", "p2p-cal.anker-in.com.", "p2p-ohi-2.anker-in.com.", - "p2p-sgp.anker-in.com.", "p2p-vir.anker-in.com.", "p2p.qq.com.", "p2p2.cloudbirds.cn.", @@ -3016,18 +2948,18 @@ var FakeECSFQDNs = container.NewMapSet( "p34856.cedexis-test.com.", "p34858.cedexis-test.com.", "p35883.cedexis-test.com.", - "p36-buy.itunes.apple.com.", - "p37-buy.itunes.apple.com.", - "p38635.cedexis-test.com.", "p39604.cedexis-test.com.", "p40255.cedexis-test.com.", + "p40256.cedexis-test.com.", "p40259.cedexis-test.com.", "p40264.cedexis-test.com.", + "p40265.cedexis-test.com.", "p40266.cedexis-test.com.", "p40267.cedexis-test.com.", "p40480.cedexis-test.com.", "p40488.cedexis-test.com.", "p40491.cedexis-test.com.", + "p40952.cedexis-test.com.", "p41237.cedexis-test.com.", "p41238.cedexis-test.com.", "p41905.cedexis-test.com.", @@ -3038,9 +2970,7 @@ var FakeECSFQDNs = container.NewMapSet( "p48436.cedexis-test.com.", "p4p.arenabg.com.", "p52066.cedexis-test.com.", - "p56-buy.itunes.apple.com.", "p56745.cedexis-test.com.", - "p7-buy.itunes.apple.com.", "p76593.cedexis-test.com.", "p86075.cedexis-test.com.", "p86077.cedexis-test.com.", @@ -3053,46 +2983,42 @@ var FakeECSFQDNs = container.NewMapSet( "pai.googlezip.net.", "palermo.remotepc.com.", "palm.tech.", + "panera-app.quantummetric.com.", "panorama.wixapps.net.", "paris.remotepc.com.", "partnerboost.com.", "pasadena.remotepc.com.", "passportalmsp.com.", - "patagonia-us.attn.tv.", + "pay.classy.org.", "pay.datatrans.com.", + "paydns.wechat.com.", "payment.api.speechify.com.", "payment.omiapp.me.", "pbdlsp1.pb.com.", "pbe1.chat.si.riotgames.com.", "pbs.btloader.com.", - "pc-store.lenovomm.cn.", "pc.crashsight.wetest.net.", "pc.perfsight.wetest.net.", "pcdn.brave.com.", + "pcm-apim-eastus-01.azure-api.net.", "pd.cdnwidget.com.", "pdf24.org.", "pdfforge.org.", - "peakpx.com.", + "pe0733.ci.managedwhitelisting.com.", "peopleadmin.com.", "perf-eu1.hsforms.com.", "perfsight.qq.com.", "perfsight.wetest.net.", "perkspot-api.perkspot.com.", "permutive.businessinsider.com.", - "permutive.wired.com.", - "perr.brightvpn.com.", + "permutive.newyorker.com.", "pf.intuit.com.", "pharos.studyquicks.com.", "phoenix.remotepc.com.", "phoenix2.remotepc.com.", "phonebridge.zoho.com.", - "photoroom.com.", "phx02pap002.storage.live.com.", - "phx02pap003.storage.live.com.", - "phx02pap004.storage.live.com.", - "phx02pap005.storage.live.com.", "phx02pap006.storage.live.com.", - "phx02pap008.storage.live.com.", "pi2850.ci.managedwhitelisting.com.", "pic.rutubelist.ru.", "piicmgvmss.polaris.com.", @@ -3101,48 +3027,48 @@ var FakeECSFQDNs = container.NewMapSet( "ping.getadblock.com.", "pingler.com.", "pingma.qq.com.", - "pingmesh.bigo.sg.", "pitk.unioneeu.com.", "pittsburgh.remotepc.com.", "pix.cdnwidget.com.", "pixel-sync.trafficmanager.net.", - "pixel-us-west.rubiconproject.com.", - "pixel.adlooxtracking.com.", "pixel.gliacloud.com.", - "pizzaedition.one.", - "pk-live.cn.", + "pks.dp.holadns.com.", + "pks.dp.martianinc.co.", + "pks.dp.okamiboss.com.", "pla-prod-scu-apim-01.azure-api.net.", - "platform-alib.linkedin.cn.w.kunlunaq.com.", + "planner.cloud.microsoft.", + "platform-alib.linkedin.cn.", "playstream.media.", - "plotline.so.", "plrm.zone.", - "plt-gw-us.xiaoyi.com.", + "plt-api-us.xiaoyi.com.", "pm.geniusmonkey.com.", - "pods.officeapps.live.com.", "poizon.com.", "polandcentral.api.cognitive.microsoft.com.", + "polarcdn-terrax.com.", "polling.zoom.us.", "polymarket.com.", "pop-convert.com.", - "popmenucloud.com.", "popt.in.", - "portal.us.ubianet.com.", "portals.mobi.", "portland.remotepc.com.", "posthog.com.", "pov.spectrum.net.", "powerpoint-collab.officeapps.live.com.", + "pp.cadc.absolute.com.", + "pp.ringcentral.biz.", "ppgames.net.", "ppos.com.", + "prebid-ny.casalemedia.com.", + "prebid-sj.casalemedia.com.", "prebid-va.casalemedia.com.", + "prebid.anyclip.com.", "prebid.trustedstack.com.", "prebidserver.pixfuture.com.", + "prediction.cmab.optimizely.com.", "premium.xvpn.io.", "printaudit.com.", - "printfriendly.com.", "privy.io.", "pro-glswish-aks-tm.trafficmanager.net.", - "pro-swishapps-aks-tm.trafficmanager.net.", "procore.com.", "prod-client-api.v.aaplimg.com.", "prod-default.lb.logrocket.network.", @@ -3158,9 +3084,7 @@ var FakeECSFQDNs = container.NewMapSet( "prod.api.letsencrypt.org.", "production.kabutoservices.com.", "profiler-collector.dalyfeds.com.", - "project-limelight.com.", - "projects.gitlab.io.", - "promotions.newegg.com.", + "prolearning.nwea.org.", "proquest.com.", "protonvpn.com.", "provaltech.com.", @@ -3170,20 +3094,24 @@ var FakeECSFQDNs = container.NewMapSet( "pub.affilimateapis.com.", "pub.network.", "public-api.uxfeedback.ru.", + "public-cdn-s3-us-west-2.oss-us-east-1.aliyuncs.com.", "publicfaas.vasdgame.com.", "publictracker.xyz.", + "pubsub.checkvideo.net.", "puffer.6.401402081.west-gcloud.codm.activision.com.", + "pull-acdn-int.s.bytefcdn-oversea.com.", "pull-cmaf-f77-sg01.tiktokcdn.com.", "pull-cmaf-f77-tt01.tiktokcdn-us.com.", "pull-cmaf-f77-tt02.tiktokcdn-us.com.", "pull-cmaf-f77-tt03.fcdn.eu.tiktokcdn.com.", "pull-cmaf-f77-tt03.tiktokcdn.com.", "pull-cmaf-f77-va01.tiktokcdn.com.", - "pull-hls-f77-sg01.tiktokcdn.com.", + "pull-f5-gcp01.tiktokcdn.com.c.bytefcdn-oversea.com.", + "pull-f5-tt04.tiktokrow-cdn.com.", + "pull-o5-va01.tiktokcdn.com.c.bytefcdn-oversea.com.", + "pull-q5-va01.tiktokrow-cdn.com.c.bytefcdn-oversea.com.", "punch.p2p.qq.com.", - "push-ads-cn.heytapmobi.com.", "push-row.zui.com.", - "push-rtmp-l95.douyincdn.com.ctdns.cn.", "push.omiapp.me.", "pushcrew.com.", "pushimg.com.", @@ -3212,18 +3140,13 @@ var FakeECSFQDNs = container.NewMapSet( "qikify.com.", "qiniudns.com.", "qiniup.com.", - "qiyukf.com.", "qookkagames.com.", + "qpic.cn.", "qq.com.cn.", "qualcomm.cn.", "qualcomm.com.", - "qualys.ca.", - "qualys.com.", - "qualys.eu.", - "qualys.in.", - "quantamagazine.org.", - "quantil.com.", "quantummetric.com.", + "questdiagnostics.sharepoint.com.", "quicinc.com.", "quickcep.com.", "qxwz.com.", @@ -3231,220 +3154,216 @@ var FakeECSFQDNs = container.NewMapSet( "r.intake-lr.com.", "r.logr-ingest.com.", "r.logrocket.io.", + "r.lr-hv-in.com.", "r.lr-in-prod.com.", "r.lr-in.com.", "r.lr-ingest.com.", "r.lr-ingest.io.", "r.lr-intake.com.", "r.lrkt-in.com.", - "r.office.microsoft.com.", "r.superhuman.com.", - "r1---sn-a5mekn6r.c.2mdn.net.", + "r0ckeet.com.", + "r1---sn-5uaeznes.c.2mdn.net.", + "r1---sn-5ualdnsk.c.2mdn.net.", + "r1---sn-a5msen76.c.2mdn.net.", "r1---sn-a5msener.c.2mdn.net.", - "r1---sn-a5msenle.c.2mdn.net.", + "r1---sn-ab5l6ndy.c.2mdn.net.", "r1---sn-ab5l6nk6.c.2mdn.net.", "r1---sn-ab5l6nkd.c.2mdn.net.", "r1---sn-ab5l6nr6.c.2mdn.net.", "r1---sn-ab5l6nrd.c.2mdn.net.", - "r1---sn-ab5l6nrl.c.2mdn.net.", + "r1---sn-ab5l6nrk.c.2mdn.net.", + "r1---sn-ab5l6nrr.c.2mdn.net.", "r1---sn-ab5l6nrs.c.2mdn.net.", - "r1---sn-ab5l6nrz.c.2mdn.net.", "r1---sn-ab5sznzd.c.2mdn.net.", - "r1---sn-ab5sznze.c.2mdn.net.", "r1---sn-ab5sznzk.c.2mdn.net.", + "r1---sn-ab5sznzl.c.2mdn.net.", "r1---sn-ab5sznzr.c.2mdn.net.", "r1---sn-ab5sznzs.c.2mdn.net.", "r1---sn-ab5sznzy.c.2mdn.net.", + "r1---sn-hp57ynl6.c.2mdn.net.", + "r1---sn-hp57ynly.c.2mdn.net.", + "r1---sn-nx57ynsk.c.2mdn.net.", "r1---sn-p5qddn7r.c.2mdn.net.", + "r1---sn-p5qlsndr.c.2mdn.net.", "r1---sn-p5qs7n6d.c.2mdn.net.", "r1---sn-p5qs7nsk.c.2mdn.net.", + "r1---sn-p5qs7nsr.c.2mdn.net.", "r1---sn-p5qs7nzr.c.2mdn.net.", "r1---sn-q4fl6n6y.c.2mdn.net.", - "r1---sn-q4fl6n6z.c.2mdn.net.", - "r1---sn-q4fl6nd7.c.2mdn.net.", - "r1---sn-q4flrney.c.2mdn.net.", - "r1---sn-q4flrnez.c.2mdn.net.", - "r1---sn-q4fzen7r.c.2mdn.net.", "r1---sn-vgqskn66.c.2mdn.net.", "r1---sn-vgqskn67.c.2mdn.net.", - "r1---sn-vgqskn6d.c.2mdn.net.", - "r1---sn-vgqskn6s.c.2mdn.net.", - "r1---sn-vgqsknes.c.2mdn.net.", - "r1---sn-vgqsknld.c.2mdn.net.", - "r1---sn-vgqsknly.c.2mdn.net.", - "r1---sn-vgqskns7.c.2mdn.net.", - "r1---sn-vgqsknse.c.2mdn.net.", - "r1---sn-vgqsknz7.c.2mdn.net.", + "r1---sn-vgqsknlk.c.2mdn.net.", + "r1---sn-vgqsknlr.c.2mdn.net.", "r1---sn-vgqsknzl.c.2mdn.net.", "r1---sn-vgqsknzs.c.2mdn.net.", + "r1---sn-vgqsknzy.c.2mdn.net.", + "r1---sn-vgqsrn67.c.2mdn.net.", "r1---sn-vgqsrn6e.c.2mdn.net.", "r1---sn-vgqsrne6.c.2mdn.net.", "r1---sn-vgqsrnl6.c.2mdn.net.", - "r1---sn-vgqsrnld.c.2mdn.net.", "r1---sn-vgqsrnls.c.2mdn.net.", - "r1---sn-vgqsrnlz.c.2mdn.net.", - "r1---sn-vgqsrnsy.c.2mdn.net.", + "r1---sn-vgqsrnsd.c.2mdn.net.", "r1---sn-vgqsrnzd.c.2mdn.net.", - "r1---sn-vgqsrnzr.c.2mdn.net.", - "r1---sn-vgqsrnzs.c.2mdn.net.", + "r1---sn-vgqsrnzk.c.2mdn.net.", "r1---sn-vgqsrnzz.c.2mdn.net.", + "r2---sn-a5mlrnlz.c.2mdn.net.", "r2---sn-ab5l6ndr.c.2mdn.net.", - "r2---sn-ab5l6ndy.c.2mdn.net.", "r2---sn-ab5l6nk6.c.2mdn.net.", "r2---sn-ab5l6nkd.c.2mdn.net.", "r2---sn-ab5l6nr6.c.2mdn.net.", "r2---sn-ab5l6nrd.c.2mdn.net.", "r2---sn-ab5l6nrk.c.2mdn.net.", - "r2---sn-ab5l6nrl.c.2mdn.net.", "r2---sn-ab5l6nrr.c.2mdn.net.", "r2---sn-ab5l6nrs.c.2mdn.net.", "r2---sn-ab5l6nrz.c.2mdn.net.", "r2---sn-ab5sznz6.c.2mdn.net.", "r2---sn-ab5sznzd.c.2mdn.net.", + "r2---sn-ab5sznze.c.2mdn.net.", "r2---sn-ab5sznzk.c.2mdn.net.", "r2---sn-ab5sznzl.c.2mdn.net.", + "r2---sn-ab5sznzr.c.2mdn.net.", + "r2---sn-ab5sznzs.c.2mdn.net.", "r2---sn-ab5sznzy.c.2mdn.net.", + "r2---sn-ab5sznzz.c.2mdn.net.", "r2---sn-ajab55-55.c.2mdn.net.", - "r2---sn-hp57ynly.c.2mdn.net.", - "r2---sn-n2uxaxjvh-j5xs.gvt1.com.", - "r2---sn-p5qddn7r.c.2mdn.net.", + "r2---sn-nh5gujvh-h4xe.gvt1.com.", + "r2---sn-p5qddn7d.c.2mdn.net.", "r2---sn-p5qlsn6l.c.2mdn.net.", - "r2---sn-p5qlsn7d.c.2mdn.net.", - "r2---sn-p5qlsn7l.c.2mdn.net.", - "r2---sn-p5qs7nsr.c.2mdn.net.", - "r2---sn-p5qs7nzy.c.2mdn.net.", - "r2---sn-q4fl6n6s.c.2mdn.net.", - "r2---sn-q4fl6ndz.c.2mdn.net.", - "r2---sn-q4fl6nsd.c.2mdn.net.", - "r2---sn-q4fl6nzy.c.2mdn.net.", + "r2---sn-p5qlsn7s.c.2mdn.net.", + "r2---sn-p5qlsndr.c.2mdn.net.", + "r2---sn-p5qlsny6.c.2mdn.net.", + "r2---sn-p5qs7nsk.c.2mdn.net.", + "r2---sn-p5qs7nzk.c.2mdn.net.", + "r2---sn-p5qs7nzr.c.2mdn.net.", + "r2---sn-q4fl6n66.c.2mdn.net.", + "r2---sn-q4flrnek.c.2mdn.net.", + "r2---sn-q4flrnl6.c.2mdn.net.", "r2---sn-q4flrnsl.c.2mdn.net.", - "r2---sn-q4fzen7e.c.2mdn.net.", - "r2---sn-q4fzene7.c.2mdn.net.", - "r2---sn-q4fzenee.c.2mdn.net.", + "r2---sn-q4fzen7y.c.2mdn.net.", "r2---sn-vgqskn66.c.2mdn.net.", "r2---sn-vgqskn6s.c.2mdn.net.", "r2---sn-vgqskn6z.c.2mdn.net.", - "r2---sn-vgqsknek.c.2mdn.net.", - "r2---sn-vgqsknz7.c.2mdn.net.", - "r2---sn-vgqsrn66.c.2mdn.net.", + "r2---sn-vgqsknld.c.2mdn.net.", + "r2---sn-vgqsknlr.c.2mdn.net.", + "r2---sn-vgqsknsk.c.2mdn.net.", + "r2---sn-vgqsknzs.c.2mdn.net.", + "r2---sn-vgqsknzz.c.2mdn.net.", "r2---sn-vgqsrn67.c.2mdn.net.", "r2---sn-vgqsrn6z.c.2mdn.net.", - "r2---sn-vgqsrne6.c.2mdn.net.", - "r2---sn-vgqsrnls.c.2mdn.net.", + "r2---sn-vgqsrnez.c.2mdn.net.", + "r2---sn-vgqsrnll.c.2mdn.net.", "r2---sn-vgqsrnlz.c.2mdn.net.", - "r2---sn-vgqsrnz7.c.2mdn.net.", + "r2---sn-vgqsrnsr.c.2mdn.net.", "r2---sn-vgqsrnzd.c.2mdn.net.", - "r2---sn-vgqsrnzs.c.2mdn.net.", + "r2---sn-vgqsrnzk.c.2mdn.net.", + "r2---sn-vgqsrnzr.c.2mdn.net.", + "r2---sn-vgqsrnzz.c.2mdn.net.", + "r3---sn-5uaeznes.c.2mdn.net.", + "r3---sn-5uaeznlz.c.2mdn.net.", + "r3---sn-5ualdnsy.c.2mdn.net.", + "r3---sn-5ualdnze.c.2mdn.net.", + "r3---sn-a5mekndl.c.2mdn.net.", "r3---sn-ab5l6ndy.c.2mdn.net.", - "r3---sn-ab5l6nk6.c.2mdn.net.", "r3---sn-ab5l6nkd.c.2mdn.net.", "r3---sn-ab5l6nr6.c.2mdn.net.", - "r3---sn-ab5l6nrd.c.2mdn.net.", "r3---sn-ab5l6nrk.c.2mdn.net.", "r3---sn-ab5l6nrl.c.2mdn.net.", - "r3---sn-ab5l6nrr.c.2mdn.net.", "r3---sn-ab5l6nrs.c.2mdn.net.", "r3---sn-ab5l6nrz.c.2mdn.net.", - "r3---sn-ab5sznz6.c.2mdn.net.", "r3---sn-ab5sznzd.c.2mdn.net.", + "r3---sn-ab5sznze.c.2mdn.net.", "r3---sn-ab5sznzk.c.2mdn.net.", - "r3---sn-ab5sznzl.c.2mdn.net.", "r3---sn-ab5sznzr.c.2mdn.net.", "r3---sn-ab5sznzs.c.2mdn.net.", "r3---sn-ab5sznzz.c.2mdn.net.", - "r3---sn-hp57kndk.c.2mdn.net.", - "r3---sn-p5qddn7d.c.2mdn.net.", + "r3---sn-hp57kn6y.c.2mdn.net.", + "r3---sn-jxopj-nh4e.gvt1.com.", + "r3---sn-nx57ynsk.c.2mdn.net.", "r3---sn-p5qddn7k.c.2mdn.net.", - "r3---sn-p5qddn7r.c.2mdn.net.", - "r3---sn-p5qlsn7s.c.2mdn.net.", - "r3---sn-p5qlsndk.c.2mdn.net.", - "r3---sn-p5qs7n6d.c.2mdn.net.", - "r3---sn-p5qs7nsr.c.2mdn.net.", + "r3---sn-p5qs7nsk.c.2mdn.net.", + "r3---sn-p5qs7nzr.c.2mdn.net.", "r3---sn-p5qs7nzy.c.2mdn.net.", - "r3---sn-q4fl6ns6.c.2mdn.net.", - "r3---sn-q4flrnel.c.2mdn.net.", - "r3---sn-q4flrnld.c.2mdn.net.", + "r3---sn-q4fl6n66.c.2mdn.net.", + "r3---sn-q4fl6nsl.c.2mdn.net.", + "r3---sn-q4flrney.c.2mdn.net.", "r3---sn-q4flrnlz.c.2mdn.net.", "r3---sn-q4flrnsl.c.2mdn.net.", - "r3---sn-q4flrnss.c.2mdn.net.", "r3---sn-q4fzen7l.c.2mdn.net.", + "r3---sn-q4fzenee.c.2mdn.net.", "r3---sn-vgqskn66.c.2mdn.net.", - "r3---sn-vgqskn67.c.2mdn.net.", + "r3---sn-vgqskn6d.c.2mdn.net.", "r3---sn-vgqskn6s.c.2mdn.net.", - "r3---sn-vgqskned.c.2mdn.net.", - "r3---sn-vgqsknes.c.2mdn.net.", - "r3---sn-vgqsknlk.c.2mdn.net.", - "r3---sn-vgqsknll.c.2mdn.net.", - "r3---sn-vgqsknlz.c.2mdn.net.", - "r3---sn-vgqskns7.c.2mdn.net.", + "r3---sn-vgqsknld.c.2mdn.net.", + "r3---sn-vgqsknlr.c.2mdn.net.", + "r3---sn-vgqsknly.c.2mdn.net.", "r3---sn-vgqsknse.c.2mdn.net.", "r3---sn-vgqsknsk.c.2mdn.net.", - "r3---sn-vgqsknz7.c.2mdn.net.", - "r3---sn-vgqsknzd.c.2mdn.net.", - "r3---sn-vgqsknzk.c.2mdn.net.", "r3---sn-vgqsknzl.c.2mdn.net.", - "r3---sn-vgqsknzr.c.2mdn.net.", "r3---sn-vgqsknzs.c.2mdn.net.", - "r3---sn-vgqsknzy.c.2mdn.net.", "r3---sn-vgqsrn6l.c.2mdn.net.", - "r3---sn-vgqsrnl6.c.2mdn.net.", - "r3---sn-vgqsrnll.c.2mdn.net.", - "r3---sn-vgqsrnls.c.2mdn.net.", - "r3---sn-vgqsrnlz.c.2mdn.net.", - "r3---sn-vgqsrns6.c.2mdn.net.", + "r3---sn-vgqsrnlk.c.2mdn.net.", + "r3---sn-vgqsrnsr.c.2mdn.net.", + "r3---sn-vgqsrnz7.c.2mdn.net.", "r3---sn-vgqsrnzd.c.2mdn.net.", - "r3---sn-vgqsrnzz.c.2mdn.net.", - "r4---sn-a5mekndl.c.2mdn.net.", - "r4---sn-ab5l6ndy.c.2mdn.net.", + "r3---sn-vgqsrnzk.c.2mdn.net.", + "r4---sn-5ualdnll.c.2mdn.net.", + "r4---sn-ab5l6ndr.c.2mdn.net.", "r4---sn-ab5l6nk6.c.2mdn.net.", - "r4---sn-ab5l6nkd.c.2mdn.net.", "r4---sn-ab5l6nr6.c.2mdn.net.", "r4---sn-ab5l6nrd.c.2mdn.net.", "r4---sn-ab5l6nrk.c.2mdn.net.", "r4---sn-ab5l6nrl.c.2mdn.net.", "r4---sn-ab5l6nrr.c.2mdn.net.", "r4---sn-ab5l6nrz.c.2mdn.net.", - "r4---sn-ab5sznld.c.2mdn.net.", "r4---sn-ab5sznly.c.2mdn.net.", + "r4---sn-ab5sznz6.c.2mdn.net.", "r4---sn-ab5sznzd.c.2mdn.net.", "r4---sn-ab5sznze.c.2mdn.net.", "r4---sn-ab5sznzk.c.2mdn.net.", "r4---sn-ab5sznzl.c.2mdn.net.", "r4---sn-ab5sznzr.c.2mdn.net.", "r4---sn-ab5sznzy.c.2mdn.net.", - "r4---sn-ab5sznzz.c.2mdn.net.", - "r4---sn-p5qddn76.c.2mdn.net.", - "r4---sn-p5qddn7z.c.2mdn.net.", - "r4---sn-p5qlsn6l.c.2mdn.net.", - "r4---sn-p5qlsnrr.c.2mdn.net.", + "r4---sn-hp57knds.c.2mdn.net.", + "r4---sn-p5qlsn76.c.2mdn.net.", + "r4---sn-p5qlsn7d.c.2mdn.net.", + "r4---sn-p5qlsn7s.c.2mdn.net.", + "r4---sn-p5qlsndr.c.2mdn.net.", "r4---sn-p5qs7n6d.c.2mdn.net.", + "r4---sn-p5qs7nsk.c.2mdn.net.", + "r4---sn-p5qs7nzk.c.2mdn.net.", + "r4---sn-p5qs7nzr.c.2mdn.net.", "r4---sn-q4fl6n66.c.2mdn.net.", "r4---sn-q4fl6n6d.c.2mdn.net.", - "r4---sn-q4fl6nd7.c.2mdn.net.", - "r4---sn-q4fl6ndl.c.2mdn.net.", - "r4---sn-q4fl6nz7.c.2mdn.net.", - "r4---sn-q4flrn7k.c.2mdn.net.", + "r4---sn-q4fl6nlz.c.2mdn.net.", + "r4---sn-q4fl6ns7.c.2mdn.net.", + "r4---sn-q4flrn7r.c.2mdn.net.", + "r4---sn-q4flrne7.c.2mdn.net.", + "r4---sn-q4flrnez.c.2mdn.net.", "r4---sn-q4flrnld.c.2mdn.net.", - "r4---sn-q4flrnss.c.2mdn.net.", + "r4---sn-q4flrnlz.c.2mdn.net.", + "r4---sn-q4fzen7l.c.2mdn.net.", "r4---sn-vgqskn66.c.2mdn.net.", - "r4---sn-vgqskn6d.c.2mdn.net.", "r4---sn-vgqskn6s.c.2mdn.net.", + "r4---sn-vgqskned.c.2mdn.net.", "r4---sn-vgqskns7.c.2mdn.net.", "r4---sn-vgqsknz7.c.2mdn.net.", - "r4---sn-vgqsknze.c.2mdn.net.", - "r4---sn-vgqsknzk.c.2mdn.net.", + "r4---sn-vgqsknzr.c.2mdn.net.", "r4---sn-vgqsknzs.c.2mdn.net.", - "r4---sn-vgqsknzz.c.2mdn.net.", - "r4---sn-vgqsrn67.c.2mdn.net.", "r4---sn-vgqsrn6z.c.2mdn.net.", - "r4---sn-vgqsrnls.c.2mdn.net.", + "r4---sn-vgqsrnes.c.2mdn.net.", + "r4---sn-vgqsrnlk.c.2mdn.net.", "r4---sn-vgqsrnlz.c.2mdn.net.", - "r4---sn-vgqsrns6.c.2mdn.net.", - "r4---sn-vgqsrnzd.c.2mdn.net.", + "r4---sn-vgqsrnsd.c.2mdn.net.", + "r4---sn-vgqsrnz6.c.2mdn.net.", + "r4---sn-vgqsrnz7.c.2mdn.net.", "r4---sn-vgqsrnzk.c.2mdn.net.", - "r4---sn-vgqsrnzs.c.2mdn.net.", "r4---sn-vgqsrnzz.c.2mdn.net.", "r4.visualwebsiteoptimizer.com.", + "r5---sn-5uaezned.c.2mdn.net.", + "r5---sn-5ualdns6.c.2mdn.net.", + "r5---sn-ab5l6ndr.c.2mdn.net.", + "r5---sn-ab5l6ndy.c.2mdn.net.", "r5---sn-ab5l6nk6.c.2mdn.net.", "r5---sn-ab5l6nkd.c.2mdn.net.", "r5---sn-ab5l6nr6.c.2mdn.net.", @@ -3459,52 +3378,53 @@ var FakeECSFQDNs = container.NewMapSet( "r5---sn-ab5sznze.c.2mdn.net.", "r5---sn-ab5sznzk.c.2mdn.net.", "r5---sn-ab5sznzl.c.2mdn.net.", - "r5---sn-ab5sznzr.c.2mdn.net.", - "r5---sn-ajab55-55.c.2mdn.net.", - "r5---sn-p5qddn7d.c.2mdn.net.", - "r5---sn-p5qddn7k.c.2mdn.net.", + "r5---sn-ab5sznzs.c.2mdn.net.", + "r5---sn-ab5sznzy.c.2mdn.net.", + "r5---sn-p5qddn76.c.2mdn.net.", "r5---sn-p5qlsn6l.c.2mdn.net.", - "r5---sn-p5qlsn7d.c.2mdn.net.", "r5---sn-p5qlsn7l.c.2mdn.net.", "r5---sn-p5qlsn7s.c.2mdn.net.", - "r5---sn-p5qlsnrr.c.2mdn.net.", - "r5---sn-p5qlsny6.c.2mdn.net.", + "r5---sn-p5qs7n6d.c.2mdn.net.", + "r5---sn-p5qs7nsk.c.2mdn.net.", "r5---sn-p5qs7nzk.c.2mdn.net.", "r5---sn-p5qs7nzy.c.2mdn.net.", - "r5---sn-q4fl6nss.c.2mdn.net.", - "r5---sn-q4flrnsd.c.2mdn.net.", - "r5---sn-q4flrnsl.c.2mdn.net.", + "r5---sn-q4fl6nd7.c.2mdn.net.", + "r5---sn-q4fl6nsk.c.2mdn.net.", + "r5---sn-q4flrn7k.c.2mdn.net.", + "r5---sn-q4flrnsk.c.2mdn.net.", "r5---sn-q4fzen7l.c.2mdn.net.", "r5---sn-qjp5q5-55.c.2mdn.net.", + "r5---sn-vgqskn66.c.2mdn.net.", "r5---sn-vgqskn67.c.2mdn.net.", - "r5---sn-vgqskn6d.c.2mdn.net.", - "r5---sn-vgqskn6s.c.2mdn.net.", - "r5---sn-vgqsknz7.c.2mdn.net.", - "r5---sn-vgqsknzs.c.2mdn.net.", - "r5---sn-vgqsrn66.c.2mdn.net.", - "r5---sn-vgqsrn67.c.2mdn.net.", - "r5---sn-vgqsrn6z.c.2mdn.net.", + "r5---sn-vgqsknld.c.2mdn.net.", + "r5---sn-vgqsknlk.c.2mdn.net.", + "r5---sn-vgqsknll.c.2mdn.net.", + "r5---sn-vgqsknls.c.2mdn.net.", + "r5---sn-vgqsknly.c.2mdn.net.", + "r5---sn-vgqsknz6.c.2mdn.net.", + "r5---sn-vgqsknzd.c.2mdn.net.", + "r5---sn-vgqsknze.c.2mdn.net.", + "r5---sn-vgqsknzk.c.2mdn.net.", + "r5---sn-vgqsknzr.c.2mdn.net.", + "r5---sn-vgqsknzz.c.2mdn.net.", "r5---sn-vgqsrne6.c.2mdn.net.", - "r5---sn-vgqsrned.c.2mdn.net.", - "r5---sn-vgqsrnez.c.2mdn.net.", - "r5---sn-vgqsrnl6.c.2mdn.net.", + "r5---sn-vgqsrnes.c.2mdn.net.", + "r5---sn-vgqsrnld.c.2mdn.net.", + "r5---sn-vgqsrnlk.c.2mdn.net.", "r5---sn-vgqsrnls.c.2mdn.net.", - "r5---sn-vgqsrnlz.c.2mdn.net.", - "r5---sn-vgqsrnsr.c.2mdn.net.", - "r5---sn-vgqsrnsy.c.2mdn.net.", "r5---sn-vgqsrnzd.c.2mdn.net.", - "r5---sn-vgqsrnzy.c.2mdn.net.", + "r5---sn-vgqsrnzk.c.2mdn.net.", "r5.visualwebsiteoptimizer.com.", "r6.visualwebsiteoptimizer.com.", + "raccorp.sharepoint.com.", "radar.cedexis.com.", "radar.com.", "radyushin.com.", - "railway.app.", "raleigh.remotepc.com.", + "randomhouse.app.box.com.", "rba-screen.healthsafe-id.com.", "rba.onehealthcareid.com.", "rbdata.boostymark.com.", - "rbhs7ex3.onequince.com.", "rbm-us.storage.googleapis.com.", "rbmeuulvihtwm2eltjhwimi2.httpschecker.net.", "rcs-acs-att-us.jibe.google.com.", @@ -3515,7 +3435,6 @@ var FakeECSFQDNs = container.NewMapSet( "rcs-copper-us.googleapis.com.", "rcs.telephony.goog.", "rctiplus.id.", - "rd.com.", "readingplus.com.", "realtime-data-api.transitapp.com.", "realtime.luckyorange.com.", @@ -3523,53 +3442,56 @@ var FakeECSFQDNs = container.NewMapSet( "recombee.com.", "recruiting.ultipro.com.", "recruiting2.ultipro.com.", - "reflector.makerbot.com.", - "refpaucqkl.top.", + "referralrock.com.", "regions.com.", "registration.prna01.cmdagent.trafficmanager.net.", - "reichelcormier.bid.", - "related.queryly.com.", "relay.shhnowisnottheti.me.", - "relieffoot.com.", "remote-config.gslb.sgw.shopeemobile.com.", "remote.control4.com.", "repo.zabbix.com.", + "requality.android.shouji.sogou.com.", "request-global.czilladx.com.", - "request.adx.ws.", - "res-5.cloudinary.com.", + "request.czilladx.com.", "resideo.com.", "resource.digitalinsight.com.", + "restaurantguru.com.", + "restauth.opentable.com.", "restproxy-analytics.ascendlearning.com.", "restrict.youtube.com.", "restrictmoderate.youtube.com.", - "retailrocket.net.", "retcode-us-west-1.arms.aliyuncs.com.", "rethinkad.com.", "retinavue.net.", "retirementpartner.com.", - "retrobowl25.com.", - "reverso.net.", "ri9864.ci.managedwhitelisting.com.", "richrelevance.com.", - "ridge.com.", + "riot-geo.pas.si.riotgames.com.", "ripamatic.com.", "rivergame.net.", "riverside.remotepc.com.", "rl.progressive.com.", "rl.quantummetric.com.", "rlm.haokan.mobi.", + "rmm.trustapex.com.", + "rmm2.jmark.com.", "rms-dra.platform.dbankcloud.com.", "rn-resource-app.xiaohongshu.com.", - "roaming-eu.officeapps.live.com.", + "robokiller.com.", "roborock.com.", + "rocketsutoledo-my.sharepoint.com.", + "rockhillssch.aristotleinsight.com.", "rockylinux.org.", + "romsp-unifyconfig.vivo.com.cn.", + "roockmobile.com.", + "routeone.net.", "router.teamviewer.com.", "roxy.azurefd.net.", "rpt.cedexis.com.", - "rq.upgrade.cmpc.cmcm.com.", - "rq.wh.cmcm.com.", "rr1---sn-0nnpbo5a-bggl.googlevideo.com.", "rr1---sn-0op8v4h5pox-cbgl.googlevideo.com.", + "rr1---sn-2aqu-hoaly.googlevideo.com.", + "rr1---sn-2aqu-hoas7.googlevideo.com.", + "rr1---sn-2aqu-hoasz.googlevideo.com.", "rr1---sn-2imern76.googlevideo.com.", "rr1---sn-2imern7d.googlevideo.com.", "rr1---sn-2imeyn7k.googlevideo.com.", @@ -3577,6 +3499,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-2napbiu-p5ie.gvt1.com.", "rr1---sn-2oaig5-55.googlevideo.com.", "rr1---sn-2pmxapm0n-gpjl.googlevideo.com.", + "rr1---sn-2pmxapm0n-gpjs.googlevideo.com.", "rr1---sn-30a7rne6.googlevideo.com.", "rr1---sn-30a7rned.googlevideo.com.", "rr1---sn-30a7rnek.googlevideo.com.", @@ -3584,8 +3507,8 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-30a7ynek.googlevideo.com.", "rr1---sn-30a7yner.googlevideo.com.", "rr1---sn-30a7yney.googlevideo.com.", - "rr1---sn-30a7ynl7.googlevideo.com.", "rr1---sn-3jpm-hjpe.googlevideo.com.", + "rr1---sn-3jpm-hjpe.gvt1.com.", "rr1---sn-4g5e6ns6.googlevideo.com.", "rr1---sn-4g5e6ns7.googlevideo.com.", "rr1---sn-4g5e6nsd.googlevideo.com.", @@ -3608,7 +3531,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-4g5edndk.googlevideo.com.", "rr1---sn-4g5edndl.googlevideo.com.", "rr1---sn-4g5edndr.googlevideo.com.", - "rr1---sn-4g5ednds.googlevideo.com.", "rr1---sn-4g5edndy.googlevideo.com.", "rr1---sn-4g5edndz.googlevideo.com.", "rr1---sn-4g5ednkl.googlevideo.com.", @@ -3630,15 +3552,20 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-4g5lznek.googlevideo.com.", "rr1---sn-4g5lzner.googlevideo.com.", "rr1---sn-4g5lznes.googlevideo.com.", + "rr1---sn-4g5lzney.googlevideo.com.", "rr1---sn-4g5lznez.googlevideo.com.", "rr1---sn-4g5lznl6.googlevideo.com.", "rr1---sn-4g5lznl7.googlevideo.com.", - "rr1---sn-4g5lznl7.gvt1.com.", "rr1---sn-4g5lznle.googlevideo.com.", "rr1---sn-4g5lznls.googlevideo.com.", "rr1---sn-4g5lznlz.googlevideo.com.", "rr1---sn-4pgnuapbiu-hiul.googlevideo.com.", - "rr1---sn-5axnug5-hxm6.googlevideo.com.", + "rr1---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr1---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr1---sn-5abxgpxuxaxjvh-9n4s.googlevideo.com.", + "rr1---sn-5abxgpxuxaxjvh-9n4z.googlevideo.com.", + "rr1---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", + "rr1---sn-5goeenez.googlevideo.com.", "rr1---sn-5gxo-in8l.googlevideo.com.", "rr1---sn-5gxo-in8s.googlevideo.com.", "rr1---sn-5hne6n6e.googlevideo.com.", @@ -3648,12 +3575,11 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-5hne6nsk.googlevideo.com.", "rr1---sn-5hne6nsr.googlevideo.com.", "rr1---sn-5hne6nsy.googlevideo.com.", - "rr1---sn-5hne6nsy.gvt1.com.", "rr1---sn-5hne6nsz.googlevideo.com.", "rr1---sn-5hne6nz6.googlevideo.com.", "rr1---sn-5hne6nzd.googlevideo.com.", "rr1---sn-5hne6nzk.googlevideo.com.", - "rr1---sn-5hne6nzk.gvt1.com.", + "rr1---sn-5hne6nzs.googlevideo.com.", "rr1---sn-5hne6nzy.googlevideo.com.", "rr1---sn-5hnednss.googlevideo.com.", "rr1---sn-5hnednsz.googlevideo.com.", @@ -3681,7 +3607,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-5uaeznse.googlevideo.com.", "rr1---sn-5uaeznsl.googlevideo.com.", "rr1---sn-5uaeznss.googlevideo.com.", - "rr1---sn-5uaezny6.googlevideo.com.", "rr1---sn-5uaeznyz.googlevideo.com.", "rr1---sn-5uaeznze.googlevideo.com.", "rr1---sn-5ualdnle.googlevideo.com.", @@ -3700,8 +3625,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-5ualdnsz.googlevideo.com.", "rr1---sn-5ualdnz7.googlevideo.com.", "rr1---sn-5ualdnze.googlevideo.com.", - "rr1---sn-8qj-nbo66.googlevideo.com.", - "rr1---sn-8qj-nbo6y.googlevideo.com.", "rr1---sn-8xgp1vo-2iae7.googlevideo.com.", "rr1---sn-8xgp1vo-a5ml.googlevideo.com.", "rr1---sn-8xgp1vo-ab56.googlevideo.com.", @@ -3710,14 +3633,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-8xgp1vo-ab5l.googlevideo.com.", "rr1---sn-8xgp1vo-ab5s.googlevideo.com.", "rr1---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr1---sn-8xgp1vo-p5ie.googlevideo.com.", - "rr1---sn-8xgp1vo-poql.googlevideo.com.", + "rr1---sn-8xgp1vo-p5qe.googlevideo.com.", + "rr1---sn-8xgp1vo-p5qe7.googlevideo.com.", + "rr1---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr1---sn-8xgp1vo-p5qs.googlevideo.com.", + "rr1---sn-8xgp1vo-p5qy.googlevideo.com.", "rr1---sn-8xgp1vo-vgqe.googlevideo.com.", "rr1---sn-8xgp1vo-xfge.googlevideo.com.", - "rr1---sn-8xgp1vo-xfgl.googlevideo.com.", "rr1---sn-8xgp1vo-xfgs.googlevideo.com.", "rr1---sn-9gv76n7e.googlevideo.com.", - "rr1---sn-9gv76n7l.googlevideo.com.", "rr1---sn-9gv76n7s.googlevideo.com.", "rr1---sn-9gv76n7z.googlevideo.com.", "rr1---sn-9gv7ene6.googlevideo.com.", @@ -3728,34 +3652,28 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-9gv7zn7y.googlevideo.com.", "rr1---sn-a5m7lnl6.googlevideo.com.", "rr1---sn-a5m7lnld.googlevideo.com.", - "rr1---sn-a5m7lnld.gvt1.com.", "rr1---sn-a5mekn6d.googlevideo.com.", "rr1---sn-a5mekn6k.googlevideo.com.", "rr1---sn-a5mekn6l.googlevideo.com.", - "rr1---sn-a5mekn6l.gvt1.com.", "rr1---sn-a5mekn6r.googlevideo.com.", "rr1---sn-a5mekn6s.googlevideo.com.", - "rr1---sn-a5mekn6s.gvt1.com.", "rr1---sn-a5mekn6z.googlevideo.com.", - "rr1---sn-a5mekn6z.gvt1.com.", "rr1---sn-a5meknd6.googlevideo.com.", "rr1---sn-a5meknde.googlevideo.com.", "rr1---sn-a5mekndl.googlevideo.com.", - "rr1---sn-a5mekndl.gvt1.com.", "rr1---sn-a5meknds.googlevideo.com.", "rr1---sn-a5mekndz.googlevideo.com.", "rr1---sn-a5meknsd.googlevideo.com.", "rr1---sn-a5meknsy.googlevideo.com.", + "rr1---sn-a5meknzk.googlevideo.com.", "rr1---sn-a5meknzl.googlevideo.com.", "rr1---sn-a5meknzr.googlevideo.com.", - "rr1---sn-a5meknzr.gvt1.com.", "rr1---sn-a5meknzs.googlevideo.com.", "rr1---sn-a5mlrnek.googlevideo.com.", "rr1---sn-a5mlrnl6.googlevideo.com.", "rr1---sn-a5mlrnll.googlevideo.com.", "rr1---sn-a5mlrnls.googlevideo.com.", "rr1---sn-a5mlrnlz.googlevideo.com.", - "rr1---sn-a5mlrnlz.gvt1.com.", "rr1---sn-a5msen76.googlevideo.com.", "rr1---sn-a5msen7l.googlevideo.com.", "rr1---sn-a5msen7s.googlevideo.com.", @@ -3764,15 +3682,12 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-a5msener.googlevideo.com.", "rr1---sn-a5msenes.googlevideo.com.", "rr1---sn-a5msenl7.googlevideo.com.", - "rr1---sn-a5msenl7.gvt1.com.", "rr1---sn-a5msenle.googlevideo.com.", "rr1---sn-a5msenll.googlevideo.com.", "rr1---sn-ab5l6ndr.googlevideo.com.", "rr1---sn-ab5l6ndy.googlevideo.com.", "rr1---sn-ab5l6nk6.googlevideo.com.", - "rr1---sn-ab5l6nk6.gvt1.com.", "rr1---sn-ab5l6nkd.googlevideo.com.", - "rr1---sn-ab5l6nkd.gvt1.com.", "rr1---sn-ab5l6nr6.googlevideo.com.", "rr1---sn-ab5l6nrd.googlevideo.com.", "rr1---sn-ab5l6nrk.googlevideo.com.", @@ -3784,11 +3699,9 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-ab5sznly.googlevideo.com.", "rr1---sn-ab5sznz6.googlevideo.com.", "rr1---sn-ab5sznzd.googlevideo.com.", - "rr1---sn-ab5sznzd.gvt1.com.", "rr1---sn-ab5sznze.googlevideo.com.", - "rr1---sn-ab5sznze.gvt1.com.", "rr1---sn-ab5sznzk.googlevideo.com.", - "rr1---sn-ab5sznzk.gvt1.com.", + "rr1---sn-ab5sznzl.googlevideo.com.", "rr1---sn-ab5sznzr.googlevideo.com.", "rr1---sn-ab5sznzs.googlevideo.com.", "rr1---sn-ab5sznzy.googlevideo.com.", @@ -3803,11 +3716,9 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-aigl6nsd.googlevideo.com.", "rr1---sn-aigl6nsk.googlevideo.com.", "rr1---sn-aigl6nsr.googlevideo.com.", - "rr1---sn-aigl6nz7.googlevideo.com.", "rr1---sn-aigl6nze.googlevideo.com.", "rr1---sn-aigl6nzk.googlevideo.com.", "rr1---sn-aigl6nzl.googlevideo.com.", - "rr1---sn-aigl6nzl.gvt1.com.", "rr1---sn-aigl6nzr.googlevideo.com.", "rr1---sn-aigl6nzs.googlevideo.com.", "rr1---sn-aigzrn76.googlevideo.com.", @@ -3826,17 +3737,14 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-aigzrnz7.googlevideo.com.", "rr1---sn-aigzrnze.googlevideo.com.", "rr1---sn-aj5ua5-5c.googlevideo.com.", - "rr1---sn-ajab55-55.googlevideo.com.", "rr1---sn-avbpj-cq5e.googlevideo.com.", - "rr1---sn-bg5oqxjvh-50nz.googlevideo.com.", "rr1---sn-bg5oqxjvh-jg2s.googlevideo.com.", "rr1---sn-bg5oqxjvh-xa2s.googlevideo.com.", - "rr1---sn-c0q7lns7.googlevideo.com.", - "rr1---sn-c0q7lnsl.googlevideo.com.", - "rr1---sn-c0q7lnz7.googlevideo.com.", + "rr1---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr1---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", + "rr1---sn-bvvbaxivnuxqjvhj5nu-hp5s.googlevideo.com.", "rr1---sn-cvb7lne7.googlevideo.com.", "rr1---sn-cvb7lnee.googlevideo.com.", - "rr1---sn-cvb7lnls.googlevideo.com.", "rr1---sn-cvb7lnlz.googlevideo.com.", "rr1---sn-cvb7sn7r.googlevideo.com.", "rr1---sn-fpnjoxu-h3xl.googlevideo.com.", @@ -3844,45 +3752,34 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-gpuuxg-hxhl.googlevideo.com.", "rr1---sn-gpuuxg-hxhs.googlevideo.com.", "rr1---sn-gpuuxg-hxhz.googlevideo.com.", - "rr1---sn-hjoj-jaul.googlevideo.com.", "rr1---sn-hjoj-poul.googlevideo.com.", "rr1---sn-hoa7kn76.googlevideo.com.", "rr1---sn-hoa7kn7z.googlevideo.com.", "rr1---sn-hoa7rn76.googlevideo.com.", "rr1---sn-hoa7rn7z.googlevideo.com.", "rr1---sn-hp57kn6r.googlevideo.com.", - "rr1---sn-hp57kn6r.gvt1.com.", "rr1---sn-hp57kn6y.googlevideo.com.", - "rr1---sn-hp57kn6y.gvt1.com.", "rr1---sn-hp57knd6.googlevideo.com.", - "rr1---sn-hp57knd6.gvt1.com.", "rr1---sn-hp57kndd.googlevideo.com.", - "rr1---sn-hp57kndd.gvt1.com.", "rr1---sn-hp57kndk.googlevideo.com.", - "rr1---sn-hp57kndk.gvt1.com.", "rr1---sn-hp57kndr.googlevideo.com.", - "rr1---sn-hp57kndr.gvt1.com.", "rr1---sn-hp57knds.googlevideo.com.", "rr1---sn-hp57knds.gvt1.com.", "rr1---sn-hp57kndy.googlevideo.com.", - "rr1---sn-hp57kndy.gvt1.com.", "rr1---sn-hp57kndz.googlevideo.com.", "rr1---sn-hp57yn7r.googlevideo.com.", "rr1---sn-hp57yn7y.googlevideo.com.", "rr1---sn-hp57yne7.googlevideo.com.", "rr1---sn-hp57ynee.googlevideo.com.", "rr1---sn-hp57ynl6.googlevideo.com.", + "rr1---sn-hp57ynlr.googlevideo.com.", "rr1---sn-hp57ynly.googlevideo.com.", - "rr1---sn-hp57ynly.gvt1.com.", "rr1---sn-hp57yns7.googlevideo.com.", "rr1---sn-hp57ynse.googlevideo.com.", - "rr1---sn-hp57ynse.gvt1.com.", "rr1---sn-hp57ynsl.googlevideo.com.", "rr1---sn-hp57ynss.googlevideo.com.", - "rr1---sn-huxaqvv-ubqe.googlevideo.com.", - "rr1---sn-huxaqvv-ubqe.gvt1.com.", - "rr1---sn-huxaqvv-ubql.googlevideo.com.", - "rr1---sn-huxaqvv-ubql.gvt1.com.", + "rr1---sn-hpqfxnu-oaxe.googlevideo.com.", + "rr1---sn-hpqfxnu-oaxl.googlevideo.com.", "rr1---sn-hxgpu-qufs.googlevideo.com.", "rr1---sn-i3b7kn6s.googlevideo.com.", "rr1---sn-i3b7knld.googlevideo.com.", @@ -3892,27 +3789,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-i3b7knse.googlevideo.com.", "rr1---sn-i3b7knsl.googlevideo.com.", "rr1---sn-i3b7knzl.googlevideo.com.", - "rr1---sn-i3b7knzs.googlevideo.com.", "rr1---sn-i3belne6.googlevideo.com.", "rr1---sn-i3belney.googlevideo.com.", - "rr1---sn-i3belnl6.googlevideo.com.", "rr1---sn-i3belnl7.googlevideo.com.", - "rr1---sn-i3belnll.googlevideo.com.", - "rr1---sn-i3belnls.googlevideo.com.", "rr1---sn-i3belnlz.googlevideo.com.", "rr1---sn-i3bssn7e.googlevideo.com.", - "rr1---sn-i5f5ppuxa-ioal.googlevideo.com.", - "rr1---sn-i5f5ppuxa-ioas.googlevideo.com.", "rr1---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr1---sn-jn2pgx4pcxg-w5oz.googlevideo.com.", "rr1---sn-jvhj5nu-2iae.googlevideo.com.", - "rr1---sn-jvhj5nu-2ial.googlevideo.com.", "rr1---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr1---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr1---sn-jvhj5nu-qufe.googlevideo.com.", - "rr1---sn-jvhj5nu-qufl.googlevideo.com.", - "rr1---sn-jvhj5nu-qufs.googlevideo.com.", - "rr1---sn-jvhj5nu-qufz.googlevideo.com.", "rr1---sn-jvooxqouf3-cqaz.googlevideo.com.", "rr1---sn-jxopj-n5oe.googlevideo.com.", "rr1---sn-muxa-2iae.googlevideo.com.", @@ -3933,7 +3818,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-nh5gujvh-h4xe.gvt1.com.", "rr1---sn-npoe7ndl.googlevideo.com.", "rr1---sn-npoe7nds.googlevideo.com.", - "rr1---sn-npoe7nds.gvt1.com.", "rr1---sn-npoe7ne6.googlevideo.com.", "rr1---sn-npoe7ne7.googlevideo.com.", "rr1---sn-npoe7ned.googlevideo.com.", @@ -3958,6 +3842,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-npoeenee.googlevideo.com.", "rr1---sn-npoeenek.googlevideo.com.", "rr1---sn-npoeener.googlevideo.com.", + "rr1---sn-npoeeney.googlevideo.com.", "rr1---sn-npoeenez.googlevideo.com.", "rr1---sn-npoeenl7.googlevideo.com.", "rr1---sn-npoeenle.googlevideo.com.", @@ -3966,26 +3851,27 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-npoeenly.googlevideo.com.", "rr1---sn-npoeens7.googlevideo.com.", "rr1---sn-npoldn76.googlevideo.com.", - "rr1---sn-npoldn7d.googlevideo.com.", "rr1---sn-npoldn7e.googlevideo.com.", "rr1---sn-npoldn7l.googlevideo.com.", "rr1---sn-npoldn7s.googlevideo.com.", - "rr1---sn-npoldn7s.gvt1.com.", "rr1---sn-npoldn7y.googlevideo.com.", "rr1---sn-npoldn7z.googlevideo.com.", "rr1---sn-npoldne7.googlevideo.com.", - "rr1---sn-nuagpm-nuae.googlevideo.com.", "rr1---sn-nv0uixgo-5ual.googlevideo.com.", "rr1---sn-nx57ynlk.googlevideo.com.", "rr1---sn-nx57ynsd.googlevideo.com.", "rr1---sn-nx57ynse.googlevideo.com.", "rr1---sn-nx57ynsk.googlevideo.com.", + "rr1---sn-nx57ynsk.gvt1.com.", "rr1---sn-nx57ynsl.googlevideo.com.", "rr1---sn-nx57ynss.googlevideo.com.", "rr1---sn-nx57ynsz.googlevideo.com.", "rr1---sn-nx5s7n76.googlevideo.com.", "rr1---sn-nx5s7n7d.googlevideo.com.", + "rr1---sn-nx5s7n7s.googlevideo.com.", "rr1---sn-nx5s7n7y.googlevideo.com.", + "rr1---sn-nx5s7n7z.googlevideo.com.", + "rr1---sn-nx5s7nee.googlevideo.com.", "rr1---sn-nx5s7nel.googlevideo.com.", "rr1---sn-o097znsd.googlevideo.com.", "rr1---sn-o097znse.googlevideo.com.", @@ -3993,6 +3879,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-o097znsl.googlevideo.com.", "rr1---sn-o097znsr.googlevideo.com.", "rr1---sn-o097znss.googlevideo.com.", + "rr1---sn-o097znsz.googlevideo.com.", "rr1---sn-o097znz7.googlevideo.com.", "rr1---sn-o097znzd.googlevideo.com.", "rr1---sn-o097znze.googlevideo.com.", @@ -4019,86 +3906,79 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-p5qlsny6.googlevideo.com.", "rr1---sn-p5qs7n6d.googlevideo.com.", "rr1---sn-p5qs7nsk.googlevideo.com.", + "rr1---sn-p5qs7nsk.gvt1.com.", "rr1---sn-p5qs7nsr.googlevideo.com.", - "rr1---sn-p5qs7nsr.gvt1.com.", "rr1---sn-p5qs7nzk.googlevideo.com.", "rr1---sn-p5qs7nzr.googlevideo.com.", "rr1---sn-p5qs7nzy.googlevideo.com.", - "rr1---sn-p5qs7nzy.gvt1.com.", "rr1---sn-paapovpnjxou0gt-nual.googlevideo.com.", "rr1---sn-paapovpnjxou0gt-nuas.googlevideo.com.", + "rr1---sn-paapovpnjxou0gt-nuaz.googlevideo.com.", "rr1---sn-pjnpu-5hfe.googlevideo.com.", "rr1---sn-pobpb-poql.googlevideo.com.", "rr1---sn-q4fl6n66.googlevideo.com.", - "rr1---sn-q4fl6n66.gvt1.com.", "rr1---sn-q4fl6n6d.googlevideo.com.", + "rr1---sn-q4fl6n6d.gvt1.com.", "rr1---sn-q4fl6n6r.googlevideo.com.", + "rr1---sn-q4fl6n6r.gvt1.com.", "rr1---sn-q4fl6n6s.googlevideo.com.", - "rr1---sn-q4fl6n6s.gvt1.com.", "rr1---sn-q4fl6n6y.googlevideo.com.", - "rr1---sn-q4fl6n6y.gvt1.com.", "rr1---sn-q4fl6n6z.googlevideo.com.", - "rr1---sn-q4fl6n6z.gvt1.com.", "rr1---sn-q4fl6nd7.googlevideo.com.", + "rr1---sn-q4fl6nd7.gvt1.com.", "rr1---sn-q4fl6nde.googlevideo.com.", "rr1---sn-q4fl6nde.gvt1.com.", "rr1---sn-q4fl6ndl.googlevideo.com.", - "rr1---sn-q4fl6ndl.gvt1.com.", "rr1---sn-q4fl6nds.googlevideo.com.", - "rr1---sn-q4fl6nds.gvt1.com.", "rr1---sn-q4fl6ndz.googlevideo.com.", "rr1---sn-q4fl6nlz.googlevideo.com.", "rr1---sn-q4fl6ns6.googlevideo.com.", - "rr1---sn-q4fl6ns6.gvt1.com.", "rr1---sn-q4fl6ns7.googlevideo.com.", "rr1---sn-q4fl6nsd.googlevideo.com.", "rr1---sn-q4fl6nsk.googlevideo.com.", - "rr1---sn-q4fl6nsk.gvt1.com.", "rr1---sn-q4fl6nsl.googlevideo.com.", - "rr1---sn-q4fl6nsl.gvt1.com.", "rr1---sn-q4fl6nsr.googlevideo.com.", "rr1---sn-q4fl6nsr.gvt1.com.", "rr1---sn-q4fl6nss.googlevideo.com.", "rr1---sn-q4fl6nsy.googlevideo.com.", + "rr1---sn-q4fl6nsy.gvt1.com.", + "rr1---sn-q4fl6nz6.googlevideo.com.", "rr1---sn-q4fl6nz7.googlevideo.com.", - "rr1---sn-q4fl6nz7.gvt1.com.", "rr1---sn-q4fl6nzy.googlevideo.com.", "rr1---sn-q4fl6nzy.gvt1.com.", "rr1---sn-q4flrn7k.googlevideo.com.", "rr1---sn-q4flrn7r.googlevideo.com.", "rr1---sn-q4flrn7y.googlevideo.com.", "rr1---sn-q4flrne6.googlevideo.com.", + "rr1---sn-q4flrne7.googlevideo.com.", "rr1---sn-q4flrnee.googlevideo.com.", + "rr1---sn-q4flrnee.gvt1.com.", "rr1---sn-q4flrnek.googlevideo.com.", "rr1---sn-q4flrnel.googlevideo.com.", - "rr1---sn-q4flrner.googlevideo.com.", "rr1---sn-q4flrnes.googlevideo.com.", "rr1---sn-q4flrney.googlevideo.com.", + "rr1---sn-q4flrney.gvt1.com.", "rr1---sn-q4flrnez.googlevideo.com.", - "rr1---sn-q4flrnez.gvt1.com.", "rr1---sn-q4flrnl6.googlevideo.com.", - "rr1---sn-q4flrnl6.gvt1.com.", "rr1---sn-q4flrnl7.googlevideo.com.", "rr1---sn-q4flrnld.googlevideo.com.", - "rr1---sn-q4flrnld.gvt1.com.", "rr1---sn-q4flrnle.googlevideo.com.", + "rr1---sn-q4flrnle.gvt1.com.", "rr1---sn-q4flrnlz.googlevideo.com.", - "rr1---sn-q4flrnlz.gvt1.com.", "rr1---sn-q4flrnsd.googlevideo.com.", - "rr1---sn-q4flrnsd.gvt1.com.", "rr1---sn-q4flrnsk.googlevideo.com.", - "rr1---sn-q4flrnsk.gvt1.com.", "rr1---sn-q4flrnsl.googlevideo.com.", "rr1---sn-q4flrnsl.gvt1.com.", "rr1---sn-q4flrnss.googlevideo.com.", - "rr1---sn-q4fzen7e.googlevideo.com.", "rr1---sn-q4fzen7l.googlevideo.com.", "rr1---sn-q4fzen7l.gvt1.com.", - "rr1---sn-q4fzen7r.googlevideo.com.", "rr1---sn-q4fzen7s.googlevideo.com.", + "rr1---sn-q4fzen7s.gvt1.com.", "rr1---sn-q4fzen7y.googlevideo.com.", "rr1---sn-q4fzene7.googlevideo.com.", + "rr1---sn-q4fzene7.gvt1.com.", "rr1---sn-q4fzenee.googlevideo.com.", + "rr1---sn-q4fzenee.gvt1.com.", "rr1---sn-qjp5q5-55.googlevideo.com.", "rr1---sn-qxo7rn7k.googlevideo.com.", "rr1---sn-qxo7rn7r.googlevideo.com.", @@ -4106,17 +3986,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-qxoedn7k.googlevideo.com.", "rr1---sn-qxoedne7.googlevideo.com.", "rr1---sn-qxoednee.googlevideo.com.", - "rr1---sn-uxmqx2uv4po4v-50nl.googlevideo.com.", "rr1---sn-v53a5oqnji-4oul.googlevideo.com.", "rr1---sn-v5goxu-jhi6.googlevideo.com.", "rr1---sn-v5goxu-jhil.googlevideo.com.", "rr1---sn-v5goxu-jhiz.googlevideo.com.", + "rr1---sn-v5goxu-jhiz.gvt1.com.", "rr1---sn-vgqskn66.googlevideo.com.", "rr1---sn-vgqskn67.googlevideo.com.", "rr1---sn-vgqskn6d.googlevideo.com.", - "rr1---sn-vgqskn6d.gvt1.com.", "rr1---sn-vgqskn6s.googlevideo.com.", - "rr1---sn-vgqskn6s.gvt1.com.", "rr1---sn-vgqskn6z.googlevideo.com.", "rr1---sn-vgqskne6.googlevideo.com.", "rr1---sn-vgqskned.googlevideo.com.", @@ -4127,14 +4005,12 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-vgqsknlk.googlevideo.com.", "rr1---sn-vgqsknll.googlevideo.com.", "rr1---sn-vgqsknlr.googlevideo.com.", - "rr1---sn-vgqsknlr.gvt1.com.", "rr1---sn-vgqsknls.googlevideo.com.", "rr1---sn-vgqsknly.googlevideo.com.", + "rr1---sn-vgqsknlz.googlevideo.com.", "rr1---sn-vgqskns7.googlevideo.com.", - "rr1---sn-vgqsknse.googlevideo.com.", "rr1---sn-vgqsknsk.googlevideo.com.", "rr1---sn-vgqsknz6.googlevideo.com.", - "rr1---sn-vgqsknz6.gvt1.com.", "rr1---sn-vgqsknz7.googlevideo.com.", "rr1---sn-vgqsknzd.googlevideo.com.", "rr1---sn-vgqsknze.googlevideo.com.", @@ -4147,10 +4023,8 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-vgqsrn66.googlevideo.com.", "rr1---sn-vgqsrn67.googlevideo.com.", "rr1---sn-vgqsrn6e.googlevideo.com.", - "rr1---sn-vgqsrn6e.gvt1.com.", "rr1---sn-vgqsrn6l.googlevideo.com.", "rr1---sn-vgqsrn6z.googlevideo.com.", - "rr1---sn-vgqsrn6z.gvt1.com.", "rr1---sn-vgqsrne6.googlevideo.com.", "rr1---sn-vgqsrned.googlevideo.com.", "rr1---sn-vgqsrnek.googlevideo.com.", @@ -4160,7 +4034,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-vgqsrnld.googlevideo.com.", "rr1---sn-vgqsrnlk.googlevideo.com.", "rr1---sn-vgqsrnll.googlevideo.com.", - "rr1---sn-vgqsrnll.gvt1.com.", "rr1---sn-vgqsrnls.googlevideo.com.", "rr1---sn-vgqsrnlz.googlevideo.com.", "rr1---sn-vgqsrns6.googlevideo.com.", @@ -4168,35 +4041,55 @@ var FakeECSFQDNs = container.NewMapSet( "rr1---sn-vgqsrnsr.googlevideo.com.", "rr1---sn-vgqsrnsy.googlevideo.com.", "rr1---sn-vgqsrnz6.googlevideo.com.", - "rr1---sn-vgqsrnz6.gvt1.com.", "rr1---sn-vgqsrnz7.googlevideo.com.", - "rr1---sn-vgqsrnz7.gvt1.com.", "rr1---sn-vgqsrnzd.googlevideo.com.", "rr1---sn-vgqsrnzk.googlevideo.com.", "rr1---sn-vgqsrnzr.googlevideo.com.", "rr1---sn-vgqsrnzs.googlevideo.com.", "rr1---sn-vgqsrnzy.googlevideo.com.", "rr1---sn-vgqsrnzz.googlevideo.com.", + "rr1---sn-vgqsrnzz.gvt1.com.", "rr1---sn-vnix5o-28ql.googlevideo.com.", "rr1---sn-voxoxu-v3jl.googlevideo.com.", "rr1---sn-voxoxu-v3js.googlevideo.com.", "rr1---sn-xo5-co5l.googlevideo.com.", - "rr1.sn-q4fl6n6r.googlevideo.com.", - "rr1.sn-q4fl6nss.googlevideo.com.", - "rr1.sn-q4flrne6.googlevideo.com.", - "rr1.sn-q4flrnee.googlevideo.com.", - "rr1.sn-q4flrnl7.googlevideo.com.", - "rr1.sn-q4flrnsl.googlevideo.com.", + "rr1.sn-4g5e6nze.googlevideo.com.", + "rr1.sn-4g5edndk.googlevideo.com.", + "rr1.sn-5hnekn7s.googlevideo.com.", + "rr1.sn-aigl6nsd.googlevideo.com.", + "rr1.sn-hp57knds.googlevideo.com.", + "rr1.sn-p5qs7nsk.googlevideo.com.", + "rr1.sn-q4fl6n6y.googlevideo.com.", + "rr1.sn-q4fl6nsd.googlevideo.com.", + "rr1.sn-q4fl6nsl.googlevideo.com.", + "rr1.sn-q4fl6nz7.googlevideo.com.", + "rr1.sn-q4flrne7.googlevideo.com.", + "rr1.sn-q4flrnl6.googlevideo.com.", + "rr1.sn-q4flrnss.googlevideo.com.", + "rr1.sn-q4fzen7l.googlevideo.com.", + "rr1.sn-q4fzenee.googlevideo.com.", + "rr1.sn-vgqsrnzz.googlevideo.com.", + "rr10---sn-8xgp1vo-ab56.googlevideo.com.", + "rr10---sn-8xgp1vo-ab5d.googlevideo.com.", + "rr10---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr11---sn-8xgp1vo-ab56.googlevideo.com.", + "rr11---sn-8xgp1vo-p5qee.googlevideo.com.", "rr2---sn-0nnpbo5a-bggl.googlevideo.com.", "rr2---sn-0op8v4h5pox-cbgl.googlevideo.com.", + "rr2---sn-2aqu-hoaly.googlevideo.com.", + "rr2---sn-2aqu-hoasz.googlevideo.com.", + "rr2---sn-2aqu-jxcr.googlevideo.com.", "rr2---sn-2imern76.googlevideo.com.", "rr2---sn-2imern7d.googlevideo.com.", - "rr2---sn-2imern7d.gvt1.com.", "rr2---sn-2imeyn7k.googlevideo.com.", "rr2---sn-2napbiu-p5ie.googlevideo.com.", "rr2---sn-2napbiu-p5ie.gvt1.com.", + "rr2---sn-2oaig5-55.googlevideo.com.", "rr2---sn-2pmxapm0n-gpjl.googlevideo.com.", + "rr2---sn-2pmxapm0n-gpjs.googlevideo.com.", "rr2---sn-30a7rne6.googlevideo.com.", + "rr2---sn-30a7rned.googlevideo.com.", + "rr2---sn-30a7rnek.googlevideo.com.", "rr2---sn-30a7rner.googlevideo.com.", "rr2---sn-30a7ynek.googlevideo.com.", "rr2---sn-30a7yner.googlevideo.com.", @@ -4241,7 +4134,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-4g5ednss.googlevideo.com.", "rr2---sn-4g5ednsy.googlevideo.com.", "rr2---sn-4g5ednsz.googlevideo.com.", - "rr2---sn-4g5ednz7.googlevideo.com.", "rr2---sn-4g5lzne6.googlevideo.com.", "rr2---sn-4g5lzned.googlevideo.com.", "rr2---sn-4g5lznek.googlevideo.com.", @@ -4255,7 +4147,10 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-4g5lznls.googlevideo.com.", "rr2---sn-4g5lznlz.googlevideo.com.", "rr2---sn-4pgnuapbiu-hiul.googlevideo.com.", - "rr2---sn-5axnug5-hxm6.googlevideo.com.", + "rr2---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr2---sn-5abxgpxuxaxjvh-9n4s.googlevideo.com.", + "rr2---sn-5abxgpxuxaxjvh-9n4z.googlevideo.com.", + "rr2---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", "rr2---sn-5gxo-in8l.googlevideo.com.", "rr2---sn-5gxo-in8s.googlevideo.com.", "rr2---sn-5hne6n6e.googlevideo.com.", @@ -4267,10 +4162,8 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-5hne6nsy.googlevideo.com.", "rr2---sn-5hne6nsz.googlevideo.com.", "rr2---sn-5hne6nz6.googlevideo.com.", - "rr2---sn-5hne6nz6.gvt1.com.", "rr2---sn-5hne6nzd.googlevideo.com.", - "rr2---sn-5hne6nzk.googlevideo.com.", - "rr2---sn-5hne6nzk.gvt1.com.", + "rr2---sn-5hne6nzs.googlevideo.com.", "rr2---sn-5hne6nzy.googlevideo.com.", "rr2---sn-5hnednss.googlevideo.com.", "rr2---sn-5hnednsz.googlevideo.com.", @@ -4279,10 +4172,10 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-5hnekn7l.googlevideo.com.", "rr2---sn-5hnekn7s.googlevideo.com.", "rr2---sn-5hnekn7z.googlevideo.com.", + "rr2---sn-5hneknee.googlevideo.com.", "rr2---sn-5hneknek.googlevideo.com.", "rr2---sn-5hneknes.googlevideo.com.", "rr2---sn-5jn5a5n35-5ojs.googlevideo.com.", - "rr2---sn-5pgnugx5h-hn2s.googlevideo.com.", "rr2---sn-5pgnugx5h-hn2z.googlevideo.com.", "rr2---sn-5uaezndd.googlevideo.com.", "rr2---sn-5uaezne6.googlevideo.com.", @@ -4293,6 +4186,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-5uaeznl6.googlevideo.com.", "rr2---sn-5uaeznld.googlevideo.com.", "rr2---sn-5uaeznls.googlevideo.com.", + "rr2---sn-5uaeznlz.googlevideo.com.", "rr2---sn-5uaezns7.googlevideo.com.", "rr2---sn-5uaeznse.googlevideo.com.", "rr2---sn-5uaeznsl.googlevideo.com.", @@ -4316,8 +4210,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-5ualdnsz.googlevideo.com.", "rr2---sn-5ualdnz7.googlevideo.com.", "rr2---sn-5ualdnze.googlevideo.com.", - "rr2---sn-8qj-nbo66.googlevideo.com.", - "rr2---sn-8qj-nbo6y.googlevideo.com.", "rr2---sn-8xgp1vo-2iae7.googlevideo.com.", "rr2---sn-8xgp1vo-a5ml.googlevideo.com.", "rr2---sn-8xgp1vo-ab56.googlevideo.com.", @@ -4326,49 +4218,42 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-8xgp1vo-ab5l.googlevideo.com.", "rr2---sn-8xgp1vo-ab5s.googlevideo.com.", "rr2---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr2---sn-8xgp1vo-p5ie.googlevideo.com.", - "rr2---sn-8xgp1vo-poql.googlevideo.com.", + "rr2---sn-8xgp1vo-p5qe7.googlevideo.com.", + "rr2---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr2---sn-8xgp1vo-p5ql.googlevideo.com.", + "rr2---sn-8xgp1vo-p5qy.googlevideo.com.", "rr2---sn-8xgp1vo-vgqe.googlevideo.com.", "rr2---sn-8xgp1vo-xfge.googlevideo.com.", "rr2---sn-8xgp1vo-xfgl.googlevideo.com.", "rr2---sn-8xgp1vo-xfgs.googlevideo.com.", "rr2---sn-9gv76n7e.googlevideo.com.", - "rr2---sn-9gv76n7l.googlevideo.com.", "rr2---sn-9gv76n7s.googlevideo.com.", "rr2---sn-9gv76n7z.googlevideo.com.", "rr2---sn-9gv7ene6.googlevideo.com.", "rr2---sn-9gv7ened.googlevideo.com.", "rr2---sn-9gv7zn76.googlevideo.com.", "rr2---sn-9gv7zn7e.googlevideo.com.", + "rr2---sn-9gv7zn7r.googlevideo.com.", "rr2---sn-9gv7zn7y.googlevideo.com.", "rr2---sn-a5m7lnl6.googlevideo.com.", - "rr2---sn-a5m7lnl6.gvt1.com.", "rr2---sn-a5m7lnld.googlevideo.com.", - "rr2---sn-a5m7lnld.gvt1.com.", "rr2---sn-a5mekn6d.googlevideo.com.", - "rr2---sn-a5mekn6d.gvt1.com.", "rr2---sn-a5mekn6k.googlevideo.com.", "rr2---sn-a5mekn6k.gvt1.com.", "rr2---sn-a5mekn6l.googlevideo.com.", "rr2---sn-a5mekn6r.googlevideo.com.", - "rr2---sn-a5mekn6r.gvt1.com.", "rr2---sn-a5mekn6s.googlevideo.com.", "rr2---sn-a5mekn6z.googlevideo.com.", - "rr2---sn-a5mekn6z.gvt1.com.", "rr2---sn-a5meknd6.googlevideo.com.", "rr2---sn-a5meknde.googlevideo.com.", "rr2---sn-a5mekndl.googlevideo.com.", - "rr2---sn-a5mekndl.gvt1.com.", "rr2---sn-a5meknds.googlevideo.com.", - "rr2---sn-a5meknds.gvt1.com.", "rr2---sn-a5mekndz.googlevideo.com.", "rr2---sn-a5meknsd.googlevideo.com.", "rr2---sn-a5meknsy.googlevideo.com.", "rr2---sn-a5meknzk.googlevideo.com.", - "rr2---sn-a5meknzk.gvt1.com.", "rr2---sn-a5meknzl.googlevideo.com.", "rr2---sn-a5meknzr.googlevideo.com.", - "rr2---sn-a5meknzr.gvt1.com.", "rr2---sn-a5meknzs.googlevideo.com.", "rr2---sn-a5mlrnek.googlevideo.com.", "rr2---sn-a5mlrnl6.googlevideo.com.", @@ -4403,25 +4288,24 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-ab5sznz6.googlevideo.com.", "rr2---sn-ab5sznzd.googlevideo.com.", "rr2---sn-ab5sznze.googlevideo.com.", - "rr2---sn-ab5sznze.gvt1.com.", "rr2---sn-ab5sznzk.googlevideo.com.", "rr2---sn-ab5sznzl.googlevideo.com.", "rr2---sn-ab5sznzr.googlevideo.com.", - "rr2---sn-ab5sznzr.gvt1.com.", "rr2---sn-ab5sznzs.googlevideo.com.", "rr2---sn-ab5sznzy.googlevideo.com.", "rr2---sn-ab5sznzz.googlevideo.com.", "rr2---sn-aigl6n6s.googlevideo.com.", "rr2---sn-aigl6ned.googlevideo.com.", "rr2---sn-aigl6nek.googlevideo.com.", + "rr2---sn-aigl6ner.googlevideo.com.", "rr2---sn-aigl6ney.googlevideo.com.", "rr2---sn-aigl6nl7.googlevideo.com.", "rr2---sn-aigl6ns6.googlevideo.com.", + "rr2---sn-aigl6ns6.gvt1.com.", "rr2---sn-aigl6nsd.googlevideo.com.", "rr2---sn-aigl6nsk.googlevideo.com.", "rr2---sn-aigl6nsr.googlevideo.com.", "rr2---sn-aigl6nz7.googlevideo.com.", - "rr2---sn-aigl6nz7.gvt1.com.", "rr2---sn-aigl6nze.googlevideo.com.", "rr2---sn-aigl6nzk.googlevideo.com.", "rr2---sn-aigl6nzl.googlevideo.com.", @@ -4434,9 +4318,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-aigzrn7l.googlevideo.com.", "rr2---sn-aigzrn7s.googlevideo.com.", "rr2---sn-aigzrn7z.googlevideo.com.", - "rr2---sn-aigzrnld.googlevideo.com.", "rr2---sn-aigzrnse.googlevideo.com.", - "rr2---sn-aigzrnsl.googlevideo.com.", "rr2---sn-aigzrnsr.googlevideo.com.", "rr2---sn-aigzrnss.googlevideo.com.", "rr2---sn-aigzrnsz.googlevideo.com.", @@ -4445,16 +4327,13 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-aj5ua5-5c.googlevideo.com.", "rr2---sn-ajab55-55.googlevideo.com.", "rr2---sn-avbpj-cq5e.googlevideo.com.", - "rr2---sn-bg5oqxjvh-50nz.googlevideo.com.", "rr2---sn-bg5oqxjvh-jg2s.googlevideo.com.", "rr2---sn-bg5oqxjvh-xa2s.googlevideo.com.", - "rr2---sn-c0q7lnly.googlevideo.com.", - "rr2---sn-c0q7lns7.googlevideo.com.", - "rr2---sn-c0q7lnsl.googlevideo.com.", - "rr2---sn-c0q7lnz7.googlevideo.com.", + "rr2---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr2---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", + "rr2---sn-bvvbaxivnuxqjvhj5nu-hp5s.googlevideo.com.", "rr2---sn-cvb7lne7.googlevideo.com.", "rr2---sn-cvb7lnee.googlevideo.com.", - "rr2---sn-cvb7lnls.googlevideo.com.", "rr2---sn-cvb7lnlz.googlevideo.com.", "rr2---sn-cvb7sn7k.googlevideo.com.", "rr2---sn-cvb7sn7r.googlevideo.com.", @@ -4468,72 +4347,51 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-hjoj-jaul.googlevideo.com.", "rr2---sn-hjoj-poul.googlevideo.com.", "rr2---sn-hoa7kn76.googlevideo.com.", - "rr2---sn-hoa7kn7z.googlevideo.com.", "rr2---sn-hoa7rn76.googlevideo.com.", "rr2---sn-hoa7rn7z.googlevideo.com.", "rr2---sn-hp57kn6r.googlevideo.com.", - "rr2---sn-hp57kn6r.gvt1.com.", "rr2---sn-hp57kn6y.googlevideo.com.", "rr2---sn-hp57knd6.googlevideo.com.", - "rr2---sn-hp57knd6.gvt1.com.", "rr2---sn-hp57kndd.googlevideo.com.", - "rr2---sn-hp57kndd.gvt1.com.", "rr2---sn-hp57kndk.googlevideo.com.", "rr2---sn-hp57kndr.googlevideo.com.", - "rr2---sn-hp57kndr.gvt1.com.", "rr2---sn-hp57knds.googlevideo.com.", - "rr2---sn-hp57knds.gvt1.com.", "rr2---sn-hp57kndy.googlevideo.com.", "rr2---sn-hp57kndz.googlevideo.com.", - "rr2---sn-hp57kndz.gvt1.com.", "rr2---sn-hp57yn7r.googlevideo.com.", "rr2---sn-hp57yn7y.googlevideo.com.", - "rr2---sn-hp57yn7y.gvt1.com.", "rr2---sn-hp57yne7.googlevideo.com.", "rr2---sn-hp57ynee.googlevideo.com.", "rr2---sn-hp57ynl6.googlevideo.com.", - "rr2---sn-hp57ynl6.gvt1.com.", "rr2---sn-hp57ynlr.googlevideo.com.", "rr2---sn-hp57ynly.googlevideo.com.", + "rr2---sn-hp57yns7.googlevideo.com.", "rr2---sn-hp57ynse.googlevideo.com.", "rr2---sn-hp57ynsl.googlevideo.com.", "rr2---sn-hp57ynss.googlevideo.com.", - "rr2---sn-hp57ynss.gvt1.com.", - "rr2---sn-huxaqvv-ubqe.googlevideo.com.", - "rr2---sn-huxaqvv-ubqe.gvt1.com.", - "rr2---sn-huxaqvv-ubql.googlevideo.com.", - "rr2---sn-huxaqvv-ubql.gvt1.com.", + "rr2---sn-hpqfxnu-oaxe.googlevideo.com.", + "rr2---sn-hpqfxnu-oaxl.googlevideo.com.", "rr2---sn-hxgpu-qufs.googlevideo.com.", "rr2---sn-i3b7kn6s.googlevideo.com.", "rr2---sn-i3b7knld.googlevideo.com.", "rr2---sn-i3b7knlk.googlevideo.com.", "rr2---sn-i3b7kns6.googlevideo.com.", - "rr2---sn-i3b7knsd.googlevideo.com.", "rr2---sn-i3b7knse.googlevideo.com.", "rr2---sn-i3b7knzl.googlevideo.com.", "rr2---sn-i3b7knzs.googlevideo.com.", "rr2---sn-i3belne6.googlevideo.com.", - "rr2---sn-i3belnl6.googlevideo.com.", + "rr2---sn-i3belney.googlevideo.com.", "rr2---sn-i3belnl7.googlevideo.com.", "rr2---sn-i3belnll.googlevideo.com.", "rr2---sn-i3belnls.googlevideo.com.", - "rr2---sn-i3belnlz.googlevideo.com.", "rr2---sn-i3bssn7e.googlevideo.com.", - "rr2---sn-i5f5ppuxa-ioas.googlevideo.com.", "rr2---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr2---sn-jn2pgx4pcxg-w5oz.googlevideo.com.", "rr2---sn-jvhj5nu-2iae.googlevideo.com.", - "rr2---sn-jvhj5nu-2ial.googlevideo.com.", - "rr2---sn-jvhj5nu-2ias.googlevideo.com.", - "rr2---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr2---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr2---sn-jvhj5nu-qufe.googlevideo.com.", - "rr2---sn-jvhj5nu-qufz.googlevideo.com.", "rr2---sn-jvooxqouf3-cqaz.googlevideo.com.", "rr2---sn-jxopj-n5oe.googlevideo.com.", "rr2---sn-jxopj-nh4e.googlevideo.com.", "rr2---sn-jxopj-nh4e.gvt1.com.", - "rr2---sn-muxa-2iae.googlevideo.com.", "rr2---sn-n2uxaxjvh-j5xl.googlevideo.com.", "rr2---sn-n2uxaxjvh-j5xl.gvt1.com.", "rr2---sn-n2uxaxjvh-j5xs.googlevideo.com.", @@ -4544,7 +4402,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-n4v7snll.googlevideo.com.", "rr2---sn-n4v7snlr.googlevideo.com.", "rr2---sn-n4v7snls.googlevideo.com.", - "rr2---sn-n4v7snly.googlevideo.com.", "rr2---sn-n4v7sns7.googlevideo.com.", "rr2---sn-n4v7snse.googlevideo.com.", "rr2---sn-nh5gujvh-h4xe.googlevideo.com.", @@ -4568,7 +4425,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-npoe7nsl.googlevideo.com.", "rr2---sn-npoe7nsr.googlevideo.com.", "rr2---sn-npoe7nss.googlevideo.com.", - "rr2---sn-npoe7nsy.googlevideo.com.", "rr2---sn-npoe7nz7.googlevideo.com.", "rr2---sn-npoeene6.googlevideo.com.", "rr2---sn-npoeened.googlevideo.com.", @@ -4591,10 +4447,10 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-npoldn7y.googlevideo.com.", "rr2---sn-npoldn7z.googlevideo.com.", "rr2---sn-npoldne7.googlevideo.com.", - "rr2---sn-nuagpm-nuae.googlevideo.com.", + "rr2---sn-ntq7yned.googlevideo.com.", + "rr2---sn-ntq7yney.googlevideo.com.", "rr2---sn-nv0uixgo-5ual.googlevideo.com.", "rr2---sn-nx57ynlk.googlevideo.com.", - "rr2---sn-nx57ynlk.gvt1.com.", "rr2---sn-nx57ynsd.googlevideo.com.", "rr2---sn-nx57ynse.googlevideo.com.", "rr2---sn-nx57ynse.gvt1.com.", @@ -4605,6 +4461,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-nx57ynsz.googlevideo.com.", "rr2---sn-nx5s7n76.googlevideo.com.", "rr2---sn-nx5s7n7d.googlevideo.com.", + "rr2---sn-nx5s7n7s.googlevideo.com.", "rr2---sn-nx5s7n7y.googlevideo.com.", "rr2---sn-nx5s7n7z.googlevideo.com.", "rr2---sn-nx5s7nee.googlevideo.com.", @@ -4616,6 +4473,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-o097znss.googlevideo.com.", "rr2---sn-o097znsz.googlevideo.com.", "rr2---sn-o097znz7.googlevideo.com.", + "rr2---sn-o097znzd.googlevideo.com.", "rr2---sn-o097znze.googlevideo.com.", "rr2---sn-o097znzk.googlevideo.com.", "rr2---sn-o097znzr.googlevideo.com.", @@ -4623,6 +4481,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-oxgpj-5ace.googlevideo.com.", "rr2---sn-p5qddn76.googlevideo.com.", "rr2---sn-p5qddn7d.googlevideo.com.", + "rr2---sn-p5qddn7k.googlevideo.com.", "rr2---sn-p5qddn7r.googlevideo.com.", "rr2---sn-p5qddn7z.googlevideo.com.", "rr2---sn-p5qlsn6l.googlevideo.com.", @@ -4645,104 +4504,93 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-p5qs7nzy.googlevideo.com.", "rr2---sn-paapovpnjxou0gt-nual.googlevideo.com.", "rr2---sn-paapovpnjxou0gt-nuas.googlevideo.com.", + "rr2---sn-paapovpnjxou0gt-nuaz.googlevideo.com.", "rr2---sn-pjnpu-5hfe.googlevideo.com.", "rr2---sn-pobpb-poql.googlevideo.com.", "rr2---sn-q4fl6n66.googlevideo.com.", - "rr2---sn-q4fl6n66.gvt1.com.", "rr2---sn-q4fl6n6d.googlevideo.com.", "rr2---sn-q4fl6n6d.gvt1.com.", "rr2---sn-q4fl6n6r.googlevideo.com.", - "rr2---sn-q4fl6n6r.gvt1.com.", "rr2---sn-q4fl6n6s.googlevideo.com.", "rr2---sn-q4fl6n6y.googlevideo.com.", "rr2---sn-q4fl6n6y.gvt1.com.", "rr2---sn-q4fl6n6z.googlevideo.com.", + "rr2---sn-q4fl6n6z.gvt1.com.", "rr2---sn-q4fl6nd7.googlevideo.com.", - "rr2---sn-q4fl6nd7.gvt1.com.", "rr2---sn-q4fl6nde.googlevideo.com.", "rr2---sn-q4fl6nde.gvt1.com.", "rr2---sn-q4fl6ndl.googlevideo.com.", - "rr2---sn-q4fl6ndl.gvt1.com.", "rr2---sn-q4fl6nds.googlevideo.com.", "rr2---sn-q4fl6ndz.googlevideo.com.", "rr2---sn-q4fl6ndz.gvt1.com.", "rr2---sn-q4fl6nlz.googlevideo.com.", "rr2---sn-q4fl6ns6.googlevideo.com.", - "rr2---sn-q4fl6ns6.gvt1.com.", "rr2---sn-q4fl6ns7.googlevideo.com.", "rr2---sn-q4fl6ns7.gvt1.com.", "rr2---sn-q4fl6nsd.googlevideo.com.", - "rr2---sn-q4fl6nsd.gvt1.com.", "rr2---sn-q4fl6nsk.googlevideo.com.", - "rr2---sn-q4fl6nsk.gvt1.com.", "rr2---sn-q4fl6nsl.googlevideo.com.", "rr2---sn-q4fl6nsr.googlevideo.com.", + "rr2---sn-q4fl6nsr.gvt1.com.", "rr2---sn-q4fl6nss.googlevideo.com.", "rr2---sn-q4fl6nsy.googlevideo.com.", + "rr2---sn-q4fl6nsy.gvt1.com.", "rr2---sn-q4fl6nz6.googlevideo.com.", "rr2---sn-q4fl6nz6.gvt1.com.", "rr2---sn-q4fl6nz7.googlevideo.com.", + "rr2---sn-q4fl6nz7.gvt1.com.", "rr2---sn-q4fl6nzy.googlevideo.com.", "rr2---sn-q4fl6nzy.gvt1.com.", "rr2---sn-q4flrn7k.googlevideo.com.", "rr2---sn-q4flrn7r.googlevideo.com.", "rr2---sn-q4flrn7y.googlevideo.com.", "rr2---sn-q4flrne6.googlevideo.com.", + "rr2---sn-q4flrne6.gvt1.com.", "rr2---sn-q4flrne7.googlevideo.com.", "rr2---sn-q4flrnee.googlevideo.com.", - "rr2---sn-q4flrnee.gvt1.com.", "rr2---sn-q4flrnek.googlevideo.com.", "rr2---sn-q4flrnel.googlevideo.com.", - "rr2---sn-q4flrnel.gvt1.com.", "rr2---sn-q4flrner.googlevideo.com.", "rr2---sn-q4flrnes.googlevideo.com.", "rr2---sn-q4flrney.googlevideo.com.", "rr2---sn-q4flrnez.googlevideo.com.", - "rr2---sn-q4flrnez.gvt1.com.", "rr2---sn-q4flrnl6.googlevideo.com.", + "rr2---sn-q4flrnl6.gvt1.com.", "rr2---sn-q4flrnl7.googlevideo.com.", "rr2---sn-q4flrnld.googlevideo.com.", "rr2---sn-q4flrnle.googlevideo.com.", "rr2---sn-q4flrnlz.googlevideo.com.", "rr2---sn-q4flrnsd.googlevideo.com.", "rr2---sn-q4flrnsk.googlevideo.com.", - "rr2---sn-q4flrnsk.gvt1.com.", "rr2---sn-q4flrnsl.googlevideo.com.", "rr2---sn-q4flrnsl.gvt1.com.", "rr2---sn-q4flrnss.googlevideo.com.", "rr2---sn-q4flrnss.gvt1.com.", "rr2---sn-q4fzen7e.googlevideo.com.", - "rr2---sn-q4fzen7e.gvt1.com.", - "rr2---sn-q4fzen7r.googlevideo.com.", - "rr2---sn-q4fzen7r.gvt1.com.", + "rr2---sn-q4fzen7l.googlevideo.com.", + "rr2---sn-q4fzen7l.gvt1.com.", "rr2---sn-q4fzen7s.googlevideo.com.", "rr2---sn-q4fzen7y.googlevideo.com.", - "rr2---sn-q4fzen7y.gvt1.com.", "rr2---sn-q4fzene7.googlevideo.com.", "rr2---sn-q4fzene7.gvt1.com.", "rr2---sn-q4fzenee.googlevideo.com.", "rr2---sn-qjp5q5-55.googlevideo.com.", "rr2---sn-qxo7rn7k.googlevideo.com.", - "rr2---sn-qxo7rn7r.googlevideo.com.", "rr2---sn-qxo7rn7y.googlevideo.com.", "rr2---sn-qxoedn7k.googlevideo.com.", "rr2---sn-qxoedne7.googlevideo.com.", "rr2---sn-qxoednee.googlevideo.com.", - "rr2---sn-u1hp55-5c.googlevideo.com.", - "rr2---sn-u1hp55-5c.gvt1.com.", - "rr2---sn-uxmqx2uv4po4v-50nl.googlevideo.com.", "rr2---sn-v53a5oqnji-4oul.googlevideo.com.", "rr2---sn-v5goxu-jhi6.googlevideo.com.", - "rr2---sn-v5goxu-jhi6.gvt1.com.", "rr2---sn-v5goxu-jhil.googlevideo.com.", "rr2---sn-v5goxu-jhiz.googlevideo.com.", + "rr2---sn-v5goxu-jhiz.gvt1.com.", "rr2---sn-vgqskn66.googlevideo.com.", + "rr2---sn-vgqskn66.gvt1.com.", "rr2---sn-vgqskn67.googlevideo.com.", "rr2---sn-vgqskn6d.googlevideo.com.", "rr2---sn-vgqskn6s.googlevideo.com.", - "rr2---sn-vgqskn6s.gvt1.com.", "rr2---sn-vgqskn6z.googlevideo.com.", - "rr2---sn-vgqskn6z.gvt1.com.", "rr2---sn-vgqskne6.googlevideo.com.", "rr2---sn-vgqskned.googlevideo.com.", "rr2---sn-vgqsknek.googlevideo.com.", @@ -4757,48 +4605,35 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-vgqsknlz.googlevideo.com.", "rr2---sn-vgqskns7.googlevideo.com.", "rr2---sn-vgqsknse.googlevideo.com.", - "rr2---sn-vgqsknz6.googlevideo.com.", - "rr2---sn-vgqsknz6.gvt1.com.", + "rr2---sn-vgqsknsk.googlevideo.com.", "rr2---sn-vgqsknz7.googlevideo.com.", "rr2---sn-vgqsknzd.googlevideo.com.", "rr2---sn-vgqsknze.googlevideo.com.", - "rr2---sn-vgqsknze.gvt1.com.", "rr2---sn-vgqsknzk.googlevideo.com.", "rr2---sn-vgqsknzl.googlevideo.com.", "rr2---sn-vgqsknzr.googlevideo.com.", "rr2---sn-vgqsknzs.googlevideo.com.", "rr2---sn-vgqsknzy.googlevideo.com.", - "rr2---sn-vgqsknzy.gvt1.com.", + "rr2---sn-vgqsknzz.googlevideo.com.", "rr2---sn-vgqsrn66.googlevideo.com.", - "rr2---sn-vgqsrn66.gvt1.com.", "rr2---sn-vgqsrn67.googlevideo.com.", "rr2---sn-vgqsrn6e.googlevideo.com.", - "rr2---sn-vgqsrn6e.gvt1.com.", - "rr2---sn-vgqsrn6l.googlevideo.com.", - "rr2---sn-vgqsrn6l.gvt1.com.", "rr2---sn-vgqsrn6z.googlevideo.com.", "rr2---sn-vgqsrne6.googlevideo.com.", "rr2---sn-vgqsrned.googlevideo.com.", "rr2---sn-vgqsrnek.googlevideo.com.", "rr2---sn-vgqsrnes.googlevideo.com.", "rr2---sn-vgqsrnez.googlevideo.com.", + "rr2---sn-vgqsrnl6.googlevideo.com.", "rr2---sn-vgqsrnld.googlevideo.com.", - "rr2---sn-vgqsrnld.gvt1.com.", - "rr2---sn-vgqsrnlk.googlevideo.com.", "rr2---sn-vgqsrnll.googlevideo.com.", "rr2---sn-vgqsrnls.googlevideo.com.", - "rr2---sn-vgqsrnls.gvt1.com.", "rr2---sn-vgqsrnlz.googlevideo.com.", "rr2---sn-vgqsrns6.googlevideo.com.", - "rr2---sn-vgqsrns6.gvt1.com.", - "rr2---sn-vgqsrnsd.googlevideo.com.", "rr2---sn-vgqsrnsr.googlevideo.com.", - "rr2---sn-vgqsrnsy.googlevideo.com.", "rr2---sn-vgqsrnz6.googlevideo.com.", - "rr2---sn-vgqsrnz6.gvt1.com.", "rr2---sn-vgqsrnz7.googlevideo.com.", "rr2---sn-vgqsrnzd.googlevideo.com.", - "rr2---sn-vgqsrnzd.gvt1.com.", "rr2---sn-vgqsrnzk.googlevideo.com.", "rr2---sn-vgqsrnzr.googlevideo.com.", "rr2---sn-vgqsrnzs.googlevideo.com.", @@ -4809,21 +4644,25 @@ var FakeECSFQDNs = container.NewMapSet( "rr2---sn-voxoxu-v3jl.googlevideo.com.", "rr2---sn-voxoxu-v3js.googlevideo.com.", "rr2---sn-xo5-co5l.googlevideo.com.", - "rr2.sn-5hnednss.googlevideo.com.", + "rr2.sn-5hnekn7l.googlevideo.com.", + "rr2.sn-hgn7rn7r.googlevideo.com.", "rr2.sn-hp57knds.googlevideo.com.", - "rr2.sn-q4flrnek.googlevideo.com.", - "rr2.sn-q4flrnsk.googlevideo.com.", + "rr2.sn-p5qs7nsk.googlevideo.com.", + "rr2.sn-q4fl6n6y.googlevideo.com.", + "rr2.sn-q4fl6nd7.googlevideo.com.", + "rr2.sn-q4fl6ndl.googlevideo.com.", + "rr2.sn-q4fl6nds.googlevideo.com.", + "rr2.sn-q4flrnl7.googlevideo.com.", + "rr2.sn-q4fzen7l.googlevideo.com.", + "rr2.sn-vgqsrnzz.googlevideo.com.", "rr3---sn-0nnpbo5a-bggl.googlevideo.com.", + "rr3---sn-2aqu-hoas7.googlevideo.com.", "rr3---sn-2imern76.googlevideo.com.", - "rr3---sn-2imern76.gvt1.com.", "rr3---sn-2imern7d.googlevideo.com.", - "rr3---sn-2imern7d.gvt1.com.", "rr3---sn-2imeyn7k.googlevideo.com.", - "rr3---sn-2imeyn7k.gvt1.com.", - "rr3---sn-2napbiu-p5ie.googlevideo.com.", - "rr3---sn-2napbiu-p5ie.gvt1.com.", - "rr3---sn-2oaig5-55.googlevideo.com.", + "rr3---sn-2pmxapm0n-gpjs.googlevideo.com.", "rr3---sn-30a7rne6.googlevideo.com.", + "rr3---sn-30a7rned.googlevideo.com.", "rr3---sn-30a7rnek.googlevideo.com.", "rr3---sn-30a7rner.googlevideo.com.", "rr3---sn-30a7ynek.googlevideo.com.", @@ -4831,6 +4670,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-30a7yney.googlevideo.com.", "rr3---sn-30a7ynl7.googlevideo.com.", "rr3---sn-3jpm-hjpe.googlevideo.com.", + "rr3---sn-3jpm-hjpe.gvt1.com.", "rr3---sn-4g5e6ns6.googlevideo.com.", "rr3---sn-4g5e6ns7.googlevideo.com.", "rr3---sn-4g5e6nsd.googlevideo.com.", @@ -4882,6 +4722,11 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-4g5lznle.googlevideo.com.", "rr3---sn-4g5lznls.googlevideo.com.", "rr3---sn-4g5lznlz.googlevideo.com.", + "rr3---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr3---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr3---sn-5abxgpxuxaxjvh-9n4z.googlevideo.com.", + "rr3---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", + "rr3---sn-5goeenes.googlevideo.com.", "rr3---sn-5hne6n6e.googlevideo.com.", "rr3---sn-5hne6n6l.googlevideo.com.", "rr3---sn-5hne6ns6.googlevideo.com.", @@ -4912,6 +4757,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-5uaeznel.googlevideo.com.", "rr3---sn-5uaeznes.googlevideo.com.", "rr3---sn-5uaeznez.googlevideo.com.", + "rr3---sn-5uaeznl6.googlevideo.com.", "rr3---sn-5uaeznld.googlevideo.com.", "rr3---sn-5uaeznls.googlevideo.com.", "rr3---sn-5uaeznlz.googlevideo.com.", @@ -4927,7 +4773,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-5ualdnlr.googlevideo.com.", "rr3---sn-5ualdnls.googlevideo.com.", "rr3---sn-5ualdns6.googlevideo.com.", - "rr3---sn-5ualdns7.googlevideo.com.", "rr3---sn-5ualdnsd.googlevideo.com.", "rr3---sn-5ualdnse.googlevideo.com.", "rr3---sn-5ualdnsk.googlevideo.com.", @@ -4938,7 +4783,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-5ualdnsz.googlevideo.com.", "rr3---sn-5ualdnz7.googlevideo.com.", "rr3---sn-5ualdnze.googlevideo.com.", - "rr3---sn-8qj-nbo66.googlevideo.com.", "rr3---sn-8xgp1vo-2iae7.googlevideo.com.", "rr3---sn-8xgp1vo-ab56.googlevideo.com.", "rr3---sn-8xgp1vo-ab5d.googlevideo.com.", @@ -4946,13 +4790,14 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-8xgp1vo-ab5l.googlevideo.com.", "rr3---sn-8xgp1vo-ab5s.googlevideo.com.", "rr3---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr3---sn-8xgp1vo-p5ie.googlevideo.com.", + "rr3---sn-8xgp1vo-p5qe7.googlevideo.com.", + "rr3---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr3---sn-8xgp1vo-p5ql.googlevideo.com.", + "rr3---sn-8xgp1vo-p5qs.googlevideo.com.", "rr3---sn-8xgp1vo-vgqe.googlevideo.com.", - "rr3---sn-8xgp1vo-xfge.googlevideo.com.", "rr3---sn-8xgp1vo-xfgl.googlevideo.com.", "rr3---sn-8xgp1vo-xfgs.googlevideo.com.", "rr3---sn-9gv76n7e.googlevideo.com.", - "rr3---sn-9gv76n7l.googlevideo.com.", "rr3---sn-9gv76n7s.googlevideo.com.", "rr3---sn-9gv76n7z.googlevideo.com.", "rr3---sn-9gv7ene6.googlevideo.com.", @@ -4964,56 +4809,43 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-a5m7lnl6.googlevideo.com.", "rr3---sn-a5m7lnld.googlevideo.com.", "rr3---sn-a5mekn6d.googlevideo.com.", - "rr3---sn-a5mekn6d.gvt1.com.", + "rr3---sn-a5mekn6k.googlevideo.com.", "rr3---sn-a5mekn6l.googlevideo.com.", "rr3---sn-a5mekn6r.googlevideo.com.", - "rr3---sn-a5mekn6r.gvt1.com.", "rr3---sn-a5mekn6s.googlevideo.com.", - "rr3---sn-a5mekn6s.gvt1.com.", "rr3---sn-a5mekn6z.googlevideo.com.", "rr3---sn-a5meknd6.googlevideo.com.", - "rr3---sn-a5meknd6.gvt1.com.", "rr3---sn-a5meknde.googlevideo.com.", "rr3---sn-a5mekndl.googlevideo.com.", "rr3---sn-a5meknds.googlevideo.com.", "rr3---sn-a5mekndz.googlevideo.com.", - "rr3---sn-a5mekndz.gvt1.com.", "rr3---sn-a5meknsd.googlevideo.com.", "rr3---sn-a5meknsy.googlevideo.com.", "rr3---sn-a5meknzk.googlevideo.com.", + "rr3---sn-a5meknzk.gvt1.com.", "rr3---sn-a5meknzl.googlevideo.com.", "rr3---sn-a5meknzr.googlevideo.com.", - "rr3---sn-a5meknzr.gvt1.com.", "rr3---sn-a5meknzs.googlevideo.com.", "rr3---sn-a5mlrnek.googlevideo.com.", "rr3---sn-a5mlrnl6.googlevideo.com.", - "rr3---sn-a5mlrnl6.gvt1.com.", "rr3---sn-a5mlrnll.googlevideo.com.", - "rr3---sn-a5mlrnll.gvt1.com.", "rr3---sn-a5mlrnls.googlevideo.com.", - "rr3---sn-a5mlrnls.gvt1.com.", "rr3---sn-a5mlrnlz.googlevideo.com.", - "rr3---sn-a5mlrnlz.gvt1.com.", "rr3---sn-a5msen76.googlevideo.com.", "rr3---sn-a5msen7l.googlevideo.com.", "rr3---sn-a5msen7s.googlevideo.com.", "rr3---sn-a5msen7z.googlevideo.com.", - "rr3---sn-a5msen7z.gvt1.com.", "rr3---sn-a5msenek.googlevideo.com.", "rr3---sn-a5msener.googlevideo.com.", "rr3---sn-a5msenes.googlevideo.com.", - "rr3---sn-a5msenes.gvt1.com.", "rr3---sn-a5msenl7.googlevideo.com.", - "rr3---sn-a5msenl7.gvt1.com.", "rr3---sn-a5msenle.googlevideo.com.", + "rr3---sn-a5msenle.gvt1.com.", "rr3---sn-a5msenll.googlevideo.com.", - "rr3---sn-a5msenll.gvt1.com.", - "rr3---sn-ab5l6ndr.googlevideo.com.", "rr3---sn-ab5l6ndy.googlevideo.com.", "rr3---sn-ab5l6nk6.googlevideo.com.", "rr3---sn-ab5l6nkd.googlevideo.com.", "rr3---sn-ab5l6nr6.googlevideo.com.", - "rr3---sn-ab5l6nr6.gvt1.com.", "rr3---sn-ab5l6nrd.googlevideo.com.", "rr3---sn-ab5l6nrk.googlevideo.com.", "rr3---sn-ab5l6nrl.googlevideo.com.", @@ -5024,7 +4856,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-ab5sznly.googlevideo.com.", "rr3---sn-ab5sznz6.googlevideo.com.", "rr3---sn-ab5sznzd.googlevideo.com.", - "rr3---sn-ab5sznzd.gvt1.com.", "rr3---sn-ab5sznze.googlevideo.com.", "rr3---sn-ab5sznzk.googlevideo.com.", "rr3---sn-ab5sznzl.googlevideo.com.", @@ -5044,9 +4875,9 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-aigl6nsr.googlevideo.com.", "rr3---sn-aigl6nz7.googlevideo.com.", "rr3---sn-aigl6nze.googlevideo.com.", - "rr3---sn-aigl6nze.gvt1.com.", "rr3---sn-aigl6nzk.googlevideo.com.", "rr3---sn-aigl6nzl.googlevideo.com.", + "rr3---sn-aigl6nzr.googlevideo.com.", "rr3---sn-aigl6nzs.googlevideo.com.", "rr3---sn-aigzrn76.googlevideo.com.", "rr3---sn-aigzrn7d.googlevideo.com.", @@ -5063,12 +4894,11 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-aigzrnsz.googlevideo.com.", "rr3---sn-aigzrnz7.googlevideo.com.", "rr3---sn-aigzrnze.googlevideo.com.", + "rr3---sn-aj5ua5-5c.googlevideo.com.", "rr3---sn-ajab55-55.googlevideo.com.", - "rr3---sn-bg5oqxjvh-50nz.googlevideo.com.", - "rr3---sn-c0q7lnly.googlevideo.com.", - "rr3---sn-c0q7lns7.googlevideo.com.", - "rr3---sn-c0q7lnsl.googlevideo.com.", - "rr3---sn-c0q7lnz7.googlevideo.com.", + "rr3---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr3---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", + "rr3---sn-bvvbaxivnuxqjvhj5nu-hp5s.googlevideo.com.", "rr3---sn-cvb7lne7.googlevideo.com.", "rr3---sn-cvb7lnlz.googlevideo.com.", "rr3---sn-cvb7sn7r.googlevideo.com.", @@ -5081,39 +4911,24 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-hoa7rn76.googlevideo.com.", "rr3---sn-hoa7rn7z.googlevideo.com.", "rr3---sn-hp57kn6r.googlevideo.com.", - "rr3---sn-hp57kn6r.gvt1.com.", "rr3---sn-hp57kn6y.googlevideo.com.", - "rr3---sn-hp57kn6y.gvt1.com.", "rr3---sn-hp57knd6.googlevideo.com.", "rr3---sn-hp57kndd.googlevideo.com.", "rr3---sn-hp57kndk.googlevideo.com.", - "rr3---sn-hp57kndk.gvt1.com.", "rr3---sn-hp57kndr.googlevideo.com.", - "rr3---sn-hp57kndr.gvt1.com.", "rr3---sn-hp57knds.googlevideo.com.", - "rr3---sn-hp57knds.gvt1.com.", - "rr3---sn-hp57kndy.googlevideo.com.", - "rr3---sn-hp57kndy.gvt1.com.", "rr3---sn-hp57kndz.googlevideo.com.", - "rr3---sn-hp57kndz.gvt1.com.", "rr3---sn-hp57yn7r.googlevideo.com.", - "rr3---sn-hp57yn7r.gvt1.com.", "rr3---sn-hp57yn7y.googlevideo.com.", "rr3---sn-hp57yne7.googlevideo.com.", "rr3---sn-hp57ynee.googlevideo.com.", "rr3---sn-hp57ynl6.googlevideo.com.", - "rr3---sn-hp57ynl6.gvt1.com.", "rr3---sn-hp57ynlr.googlevideo.com.", "rr3---sn-hp57ynly.googlevideo.com.", "rr3---sn-hp57yns7.googlevideo.com.", - "rr3---sn-hp57yns7.gvt1.com.", "rr3---sn-hp57ynse.googlevideo.com.", - "rr3---sn-hp57ynse.gvt1.com.", "rr3---sn-hp57ynsl.googlevideo.com.", - "rr3---sn-hp57ynsl.gvt1.com.", "rr3---sn-hp57ynss.googlevideo.com.", - "rr3---sn-huxaqvv-ubqe.googlevideo.com.", - "rr3---sn-huxaqvv-ubqe.gvt1.com.", "rr3---sn-i3b7kn6s.googlevideo.com.", "rr3---sn-i3b7knld.googlevideo.com.", "rr3---sn-i3b7knlk.googlevideo.com.", @@ -5123,8 +4938,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-i3b7knsl.googlevideo.com.", "rr3---sn-i3b7knzl.googlevideo.com.", "rr3---sn-i3b7knzs.googlevideo.com.", - "rr3---sn-i3belne6.googlevideo.com.", - "rr3---sn-i3belney.googlevideo.com.", "rr3---sn-i3belnl6.googlevideo.com.", "rr3---sn-i3belnl7.googlevideo.com.", "rr3---sn-i3belnll.googlevideo.com.", @@ -5134,18 +4947,10 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr3---sn-jn2pgx4pcxg-w5oz.googlevideo.com.", "rr3---sn-jvhj5nu-2iae.googlevideo.com.", - "rr3---sn-jvhj5nu-2ial.googlevideo.com.", - "rr3---sn-jvhj5nu-2ias.googlevideo.com.", - "rr3---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr3---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr3---sn-jvhj5nu-qufe.googlevideo.com.", - "rr3---sn-jvhj5nu-qufl.googlevideo.com.", - "rr3---sn-jvhj5nu-qufs.googlevideo.com.", - "rr3---sn-jvhj5nu-qufz.googlevideo.com.", + "rr3---sn-jvhj5nu-quf6.googlevideo.com.", "rr3---sn-jxopj-n5oe.googlevideo.com.", "rr3---sn-jxopj-nh4e.googlevideo.com.", "rr3---sn-jxopj-nh4e.gvt1.com.", - "rr3---sn-n4v7snee.googlevideo.com.", "rr3---sn-n4v7sney.googlevideo.com.", "rr3---sn-n4v7snl7.googlevideo.com.", "rr3---sn-n4v7snll.googlevideo.com.", @@ -5168,7 +4973,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-npoe7nlz.googlevideo.com.", "rr3---sn-npoe7ns6.googlevideo.com.", "rr3---sn-npoe7ns7.googlevideo.com.", - "rr3---sn-npoe7ns7.gvt1.com.", "rr3---sn-npoe7nsd.googlevideo.com.", "rr3---sn-npoe7nsk.googlevideo.com.", "rr3---sn-npoe7nsl.googlevideo.com.", @@ -5197,20 +5001,23 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-npoldn7y.googlevideo.com.", "rr3---sn-npoldn7z.googlevideo.com.", "rr3---sn-npoldne7.googlevideo.com.", + "rr3---sn-ntq7yns7.googlevideo.com.", + "rr3---sn-ntqe6nes.googlevideo.com.", "rr3---sn-nx57ynlk.googlevideo.com.", "rr3---sn-nx57ynsd.googlevideo.com.", "rr3---sn-nx57ynse.googlevideo.com.", "rr3---sn-nx57ynsk.googlevideo.com.", + "rr3---sn-nx57ynsk.gvt1.com.", "rr3---sn-nx57ynsl.googlevideo.com.", "rr3---sn-nx57ynss.googlevideo.com.", "rr3---sn-nx57ynsz.googlevideo.com.", - "rr3---sn-nx57ynsz.gvt1.com.", "rr3---sn-nx5s7n76.googlevideo.com.", "rr3---sn-nx5s7n7d.googlevideo.com.", + "rr3---sn-nx5s7n7s.googlevideo.com.", "rr3---sn-nx5s7n7y.googlevideo.com.", + "rr3---sn-nx5s7n7z.googlevideo.com.", "rr3---sn-nx5s7nee.googlevideo.com.", - "rr3---sn-nx5s7nel.googlevideo.com.", - "rr3---sn-nx5s7nel.gvt1.com.", + "rr3---sn-o097znsd.googlevideo.com.", "rr3---sn-o097znse.googlevideo.com.", "rr3---sn-o097znsk.googlevideo.com.", "rr3---sn-o097znsl.googlevideo.com.", @@ -5231,7 +5038,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-p5qlsn6l.googlevideo.com.", "rr3---sn-p5qlsn76.googlevideo.com.", "rr3---sn-p5qlsn7d.googlevideo.com.", - "rr3---sn-p5qlsn7d.gvt1.com.", "rr3---sn-p5qlsn7l.googlevideo.com.", "rr3---sn-p5qlsn7s.googlevideo.com.", "rr3---sn-p5qlsnd6.googlevideo.com.", @@ -5243,6 +5049,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-p5qlsny6.googlevideo.com.", "rr3---sn-p5qs7n6d.googlevideo.com.", "rr3---sn-p5qs7nsk.googlevideo.com.", + "rr3---sn-p5qs7nsk.gvt1.com.", "rr3---sn-p5qs7nsr.googlevideo.com.", "rr3---sn-p5qs7nzk.googlevideo.com.", "rr3---sn-p5qs7nzr.googlevideo.com.", @@ -5252,25 +5059,19 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-q4fl6n66.gvt1.com.", "rr3---sn-q4fl6n6d.googlevideo.com.", "rr3---sn-q4fl6n6r.googlevideo.com.", - "rr3---sn-q4fl6n6r.gvt1.com.", "rr3---sn-q4fl6n6s.googlevideo.com.", "rr3---sn-q4fl6n6y.googlevideo.com.", "rr3---sn-q4fl6n6y.gvt1.com.", "rr3---sn-q4fl6n6z.googlevideo.com.", "rr3---sn-q4fl6n6z.gvt1.com.", "rr3---sn-q4fl6nd7.googlevideo.com.", - "rr3---sn-q4fl6nd7.gvt1.com.", + "rr3---sn-q4fl6nde.googlevideo.com.", "rr3---sn-q4fl6ndl.googlevideo.com.", "rr3---sn-q4fl6nds.googlevideo.com.", - "rr3---sn-q4fl6nds.gvt1.com.", "rr3---sn-q4fl6ndz.googlevideo.com.", - "rr3---sn-q4fl6ndz.gvt1.com.", "rr3---sn-q4fl6nlz.googlevideo.com.", - "rr3---sn-q4fl6nlz.gvt1.com.", "rr3---sn-q4fl6ns6.googlevideo.com.", - "rr3---sn-q4fl6ns6.gvt1.com.", "rr3---sn-q4fl6ns7.googlevideo.com.", - "rr3---sn-q4fl6ns7.gvt1.com.", "rr3---sn-q4fl6nsd.googlevideo.com.", "rr3---sn-q4fl6nsk.googlevideo.com.", "rr3---sn-q4fl6nsl.googlevideo.com.", @@ -5279,13 +5080,10 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-q4fl6nss.googlevideo.com.", "rr3---sn-q4fl6nsy.googlevideo.com.", "rr3---sn-q4fl6nz6.googlevideo.com.", - "rr3---sn-q4fl6nz6.gvt1.com.", "rr3---sn-q4fl6nz7.googlevideo.com.", - "rr3---sn-q4fl6nz7.gvt1.com.", "rr3---sn-q4fl6nzy.googlevideo.com.", "rr3---sn-q4flrn7k.googlevideo.com.", "rr3---sn-q4flrn7r.googlevideo.com.", - "rr3---sn-q4flrn7r.gvt1.com.", "rr3---sn-q4flrn7y.googlevideo.com.", "rr3---sn-q4flrne6.googlevideo.com.", "rr3---sn-q4flrne7.googlevideo.com.", @@ -5293,14 +5091,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-q4flrnek.googlevideo.com.", "rr3---sn-q4flrnel.googlevideo.com.", "rr3---sn-q4flrner.googlevideo.com.", + "rr3---sn-q4flrner.gvt1.com.", "rr3---sn-q4flrnes.googlevideo.com.", "rr3---sn-q4flrney.googlevideo.com.", "rr3---sn-q4flrnez.googlevideo.com.", - "rr3---sn-q4flrnez.gvt1.com.", "rr3---sn-q4flrnl6.googlevideo.com.", "rr3---sn-q4flrnl6.gvt1.com.", "rr3---sn-q4flrnl7.googlevideo.com.", "rr3---sn-q4flrnld.googlevideo.com.", + "rr3---sn-q4flrnld.gvt1.com.", "rr3---sn-q4flrnle.googlevideo.com.", "rr3---sn-q4flrnlz.googlevideo.com.", "rr3---sn-q4flrnlz.gvt1.com.", @@ -5308,19 +5107,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-q4flrnsk.googlevideo.com.", "rr3---sn-q4flrnsk.gvt1.com.", "rr3---sn-q4flrnsl.googlevideo.com.", - "rr3---sn-q4flrnsl.gvt1.com.", "rr3---sn-q4flrnss.googlevideo.com.", "rr3---sn-q4flrnss.gvt1.com.", "rr3---sn-q4fzen7e.googlevideo.com.", + "rr3---sn-q4fzen7e.gvt1.com.", "rr3---sn-q4fzen7l.googlevideo.com.", - "rr3---sn-q4fzen7r.googlevideo.com.", - "rr3---sn-q4fzen7r.gvt1.com.", + "rr3---sn-q4fzen7l.gvt1.com.", "rr3---sn-q4fzen7s.googlevideo.com.", - "rr3---sn-q4fzen7s.gvt1.com.", "rr3---sn-q4fzen7y.googlevideo.com.", - "rr3---sn-q4fzen7y.gvt1.com.", "rr3---sn-q4fzene7.googlevideo.com.", - "rr3---sn-q4fzene7.gvt1.com.", "rr3---sn-q4fzenee.googlevideo.com.", "rr3---sn-q4fzenee.gvt1.com.", "rr3---sn-qjp5q5-55.googlevideo.com.", @@ -5330,8 +5125,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-qxoedn7k.googlevideo.com.", "rr3---sn-qxoedne7.googlevideo.com.", "rr3---sn-qxoednee.googlevideo.com.", - "rr3---sn-u1hp55-5c.googlevideo.com.", - "rr3---sn-u1hp55-5c.gvt1.com.", "rr3---sn-v5goxu-jhil.googlevideo.com.", "rr3---sn-vgqskn66.googlevideo.com.", "rr3---sn-vgqskn67.googlevideo.com.", @@ -5347,76 +5140,75 @@ var FakeECSFQDNs = container.NewMapSet( "rr3---sn-vgqsknlk.googlevideo.com.", "rr3---sn-vgqsknll.googlevideo.com.", "rr3---sn-vgqsknlr.googlevideo.com.", - "rr3---sn-vgqsknlr.gvt1.com.", + "rr3---sn-vgqsknls.googlevideo.com.", + "rr3---sn-vgqsknly.googlevideo.com.", "rr3---sn-vgqsknlz.googlevideo.com.", "rr3---sn-vgqskns7.googlevideo.com.", - "rr3---sn-vgqskns7.gvt1.com.", "rr3---sn-vgqsknse.googlevideo.com.", "rr3---sn-vgqsknsk.googlevideo.com.", "rr3---sn-vgqsknz6.googlevideo.com.", "rr3---sn-vgqsknz7.googlevideo.com.", - "rr3---sn-vgqsknz7.gvt1.com.", "rr3---sn-vgqsknzd.googlevideo.com.", "rr3---sn-vgqsknze.googlevideo.com.", "rr3---sn-vgqsknzk.googlevideo.com.", "rr3---sn-vgqsknzl.googlevideo.com.", "rr3---sn-vgqsknzr.googlevideo.com.", - "rr3---sn-vgqsknzr.gvt1.com.", "rr3---sn-vgqsknzs.googlevideo.com.", "rr3---sn-vgqsknzy.googlevideo.com.", "rr3---sn-vgqsknzz.googlevideo.com.", "rr3---sn-vgqsrn66.googlevideo.com.", "rr3---sn-vgqsrn67.googlevideo.com.", - "rr3---sn-vgqsrn67.gvt1.com.", "rr3---sn-vgqsrn6e.googlevideo.com.", "rr3---sn-vgqsrn6l.googlevideo.com.", "rr3---sn-vgqsrn6z.googlevideo.com.", "rr3---sn-vgqsrne6.googlevideo.com.", "rr3---sn-vgqsrned.googlevideo.com.", - "rr3---sn-vgqsrned.gvt1.com.", "rr3---sn-vgqsrnek.googlevideo.com.", "rr3---sn-vgqsrnes.googlevideo.com.", "rr3---sn-vgqsrnez.googlevideo.com.", "rr3---sn-vgqsrnl6.googlevideo.com.", - "rr3---sn-vgqsrnl6.gvt1.com.", "rr3---sn-vgqsrnld.googlevideo.com.", "rr3---sn-vgqsrnlk.googlevideo.com.", - "rr3---sn-vgqsrnlk.gvt1.com.", "rr3---sn-vgqsrnll.googlevideo.com.", "rr3---sn-vgqsrnls.googlevideo.com.", "rr3---sn-vgqsrnlz.googlevideo.com.", "rr3---sn-vgqsrns6.googlevideo.com.", "rr3---sn-vgqsrnsd.googlevideo.com.", "rr3---sn-vgqsrnsr.googlevideo.com.", + "rr3---sn-vgqsrnsr.gvt1.com.", "rr3---sn-vgqsrnsy.googlevideo.com.", "rr3---sn-vgqsrnz6.googlevideo.com.", "rr3---sn-vgqsrnz7.googlevideo.com.", "rr3---sn-vgqsrnzd.googlevideo.com.", - "rr3---sn-vgqsrnzd.gvt1.com.", "rr3---sn-vgqsrnzk.googlevideo.com.", "rr3---sn-vgqsrnzr.googlevideo.com.", "rr3---sn-vgqsrnzs.googlevideo.com.", "rr3---sn-vgqsrnzy.googlevideo.com.", "rr3---sn-vgqsrnzz.googlevideo.com.", - "rr3.sn-5hne6nzd.googlevideo.com.", "rr3.sn-5hnednsz.googlevideo.com.", - "rr3.sn-5hnekn7z.googlevideo.com.", - "rr3.sn-5hneknek.googlevideo.com.", - "rr3.sn-q4fl6n6z.googlevideo.com.", - "rr3.sn-q4flrnes.googlevideo.com.", - "rr3.sn-q4fzen7y.googlevideo.com.", + "rr3.sn-hp57knds.googlevideo.com.", + "rr3.sn-ntqe6nes.googlevideo.com.", + "rr3.sn-p5qs7nsk.googlevideo.com.", + "rr3.sn-q4fl6n6r.googlevideo.com.", + "rr3.sn-q4fl6ndl.googlevideo.com.", + "rr3.sn-q4fl6nss.googlevideo.com.", + "rr3.sn-q4fzen7l.googlevideo.com.", + "rr3.sn-vgqsrnzz.googlevideo.com.", "rr4---sn-0nnpbo5a-bggl.googlevideo.com.", + "rr4---sn-2aqu-hoas7.googlevideo.com.", + "rr4---sn-2aqu-hoasz.googlevideo.com.", + "rr4---sn-2aqu-jxcr.googlevideo.com.", "rr4---sn-2imern76.googlevideo.com.", "rr4---sn-2imern76.gvt1.com.", "rr4---sn-2imern7d.googlevideo.com.", - "rr4---sn-2imern7d.gvt1.com.", "rr4---sn-2imeyn7k.googlevideo.com.", - "rr4---sn-2imeyn7k.gvt1.com.", "rr4---sn-2oaig5-55.googlevideo.com.", + "rr4---sn-30a7rne6.googlevideo.com.", "rr4---sn-30a7rned.googlevideo.com.", "rr4---sn-30a7rnek.googlevideo.com.", "rr4---sn-30a7rner.googlevideo.com.", "rr4---sn-30a7ynek.googlevideo.com.", + "rr4---sn-30a7yner.googlevideo.com.", "rr4---sn-30a7ynl7.googlevideo.com.", "rr4---sn-4g5e6ns6.googlevideo.com.", "rr4---sn-4g5e6ns7.googlevideo.com.", @@ -5452,7 +5244,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-4g5ednse.googlevideo.com.", "rr4---sn-4g5ednsk.googlevideo.com.", "rr4---sn-4g5ednsl.googlevideo.com.", - "rr4---sn-4g5ednsr.googlevideo.com.", "rr4---sn-4g5ednss.googlevideo.com.", "rr4---sn-4g5ednsy.googlevideo.com.", "rr4---sn-4g5ednsz.googlevideo.com.", @@ -5469,6 +5260,12 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-4g5lznle.googlevideo.com.", "rr4---sn-4g5lznls.googlevideo.com.", "rr4---sn-4g5lznlz.googlevideo.com.", + "rr4---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr4---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr4---sn-5abxgpxuxaxjvh-9n4z.googlevideo.com.", + "rr4---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", + "rr4---sn-5goeenes.googlevideo.com.", + "rr4---sn-5goeenez.googlevideo.com.", "rr4---sn-5hne6n6e.googlevideo.com.", "rr4---sn-5hne6n6l.googlevideo.com.", "rr4---sn-5hne6ns6.googlevideo.com.", @@ -5479,7 +5276,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-5hne6nsz.googlevideo.com.", "rr4---sn-5hne6nz6.googlevideo.com.", "rr4---sn-5hne6nzd.googlevideo.com.", - "rr4---sn-5hne6nzd.gvt1.com.", "rr4---sn-5hne6nzk.googlevideo.com.", "rr4---sn-5hne6nzs.googlevideo.com.", "rr4---sn-5hne6nzy.googlevideo.com.", @@ -5492,6 +5288,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-5hnekn7z.googlevideo.com.", "rr4---sn-5hneknee.googlevideo.com.", "rr4---sn-5hneknek.googlevideo.com.", + "rr4---sn-5hneknes.googlevideo.com.", "rr4---sn-5pgnugx5h-hn2z.googlevideo.com.", "rr4---sn-5uaezndd.googlevideo.com.", "rr4---sn-5uaezne6.googlevideo.com.", @@ -5526,19 +5323,20 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-5ualdnsz.googlevideo.com.", "rr4---sn-5ualdnz7.googlevideo.com.", "rr4---sn-5ualdnze.googlevideo.com.", - "rr4---sn-8qj-i5o6k.googlevideo.com.", - "rr4---sn-8qj-nbo66.googlevideo.com.", "rr4---sn-8xgp1vo-2iae7.googlevideo.com.", "rr4---sn-8xgp1vo-ab56.googlevideo.com.", "rr4---sn-8xgp1vo-ab5d.googlevideo.com.", "rr4---sn-8xgp1vo-ab5e.googlevideo.com.", "rr4---sn-8xgp1vo-ab5s.googlevideo.com.", "rr4---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr4---sn-8xgp1vo-p5ie.googlevideo.com.", + "rr4---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr4---sn-8xgp1vo-p5ql.googlevideo.com.", + "rr4---sn-8xgp1vo-p5qs.googlevideo.com.", + "rr4---sn-8xgp1vo-p5qy.googlevideo.com.", + "rr4---sn-8xgp1vo-vgqe.googlevideo.com.", "rr4---sn-8xgp1vo-xfge.googlevideo.com.", "rr4---sn-8xgp1vo-xfgl.googlevideo.com.", "rr4---sn-9gv76n7e.googlevideo.com.", - "rr4---sn-9gv76n7l.googlevideo.com.", "rr4---sn-9gv76n7s.googlevideo.com.", "rr4---sn-9gv76n7z.googlevideo.com.", "rr4---sn-9gv7ene6.googlevideo.com.", @@ -5550,78 +5348,61 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-a5m7lnl6.googlevideo.com.", "rr4---sn-a5m7lnl6.gvt1.com.", "rr4---sn-a5m7lnld.googlevideo.com.", - "rr4---sn-a5m7lnld.gvt1.com.", "rr4---sn-a5mekn6d.googlevideo.com.", - "rr4---sn-a5mekn6d.gvt1.com.", "rr4---sn-a5mekn6k.googlevideo.com.", "rr4---sn-a5mekn6l.googlevideo.com.", - "rr4---sn-a5mekn6l.gvt1.com.", "rr4---sn-a5mekn6r.googlevideo.com.", - "rr4---sn-a5mekn6r.gvt1.com.", "rr4---sn-a5mekn6s.googlevideo.com.", - "rr4---sn-a5mekn6s.gvt1.com.", "rr4---sn-a5mekn6z.googlevideo.com.", "rr4---sn-a5mekn6z.gvt1.com.", "rr4---sn-a5meknd6.googlevideo.com.", "rr4---sn-a5meknde.googlevideo.com.", - "rr4---sn-a5meknde.gvt1.com.", "rr4---sn-a5mekndl.googlevideo.com.", "rr4---sn-a5meknds.googlevideo.com.", "rr4---sn-a5mekndz.googlevideo.com.", "rr4---sn-a5meknsd.googlevideo.com.", "rr4---sn-a5meknsy.googlevideo.com.", + "rr4---sn-a5meknzk.googlevideo.com.", "rr4---sn-a5meknzl.googlevideo.com.", "rr4---sn-a5meknzr.googlevideo.com.", - "rr4---sn-a5meknzr.gvt1.com.", "rr4---sn-a5meknzs.googlevideo.com.", - "rr4---sn-a5meknzs.gvt1.com.", "rr4---sn-a5mlrnek.googlevideo.com.", "rr4---sn-a5mlrnl6.googlevideo.com.", "rr4---sn-a5mlrnll.googlevideo.com.", - "rr4---sn-a5mlrnll.gvt1.com.", "rr4---sn-a5mlrnls.googlevideo.com.", - "rr4---sn-a5mlrnls.gvt1.com.", "rr4---sn-a5mlrnlz.googlevideo.com.", "rr4---sn-a5msen76.googlevideo.com.", - "rr4---sn-a5msen76.gvt1.com.", "rr4---sn-a5msen7l.googlevideo.com.", "rr4---sn-a5msen7s.googlevideo.com.", "rr4---sn-a5msen7z.googlevideo.com.", + "rr4---sn-a5msenek.googlevideo.com.", "rr4---sn-a5msener.googlevideo.com.", - "rr4---sn-a5msener.gvt1.com.", "rr4---sn-a5msenes.googlevideo.com.", - "rr4---sn-a5msenes.gvt1.com.", "rr4---sn-a5msenl7.googlevideo.com.", - "rr4---sn-a5msenl7.gvt1.com.", "rr4---sn-a5msenle.googlevideo.com.", "rr4---sn-a5msenll.googlevideo.com.", "rr4---sn-ab5l6ndr.googlevideo.com.", "rr4---sn-ab5l6ndy.googlevideo.com.", "rr4---sn-ab5l6nk6.googlevideo.com.", - "rr4---sn-ab5l6nk6.gvt1.com.", "rr4---sn-ab5l6nkd.googlevideo.com.", "rr4---sn-ab5l6nr6.googlevideo.com.", - "rr4---sn-ab5l6nr6.gvt1.com.", "rr4---sn-ab5l6nrd.googlevideo.com.", "rr4---sn-ab5l6nrk.googlevideo.com.", "rr4---sn-ab5l6nrl.googlevideo.com.", "rr4---sn-ab5l6nrr.googlevideo.com.", "rr4---sn-ab5l6nrz.googlevideo.com.", - "rr4---sn-ab5sznld.googlevideo.com.", "rr4---sn-ab5sznly.googlevideo.com.", "rr4---sn-ab5sznz6.googlevideo.com.", "rr4---sn-ab5sznzd.googlevideo.com.", "rr4---sn-ab5sznze.googlevideo.com.", - "rr4---sn-ab5sznze.gvt1.com.", "rr4---sn-ab5sznzk.googlevideo.com.", "rr4---sn-ab5sznzl.googlevideo.com.", - "rr4---sn-ab5sznzl.gvt1.com.", "rr4---sn-ab5sznzr.googlevideo.com.", "rr4---sn-ab5sznzs.googlevideo.com.", "rr4---sn-ab5sznzy.googlevideo.com.", "rr4---sn-ab5sznzz.googlevideo.com.", - "rr4---sn-ab5sznzz.gvt1.com.", "rr4---sn-aigl6n6s.googlevideo.com.", + "rr4---sn-aigl6ned.googlevideo.com.", "rr4---sn-aigl6nek.googlevideo.com.", "rr4---sn-aigl6ner.googlevideo.com.", "rr4---sn-aigl6ney.googlevideo.com.", @@ -5629,11 +5410,11 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-aigl6ns6.googlevideo.com.", "rr4---sn-aigl6nsd.googlevideo.com.", "rr4---sn-aigl6nsk.googlevideo.com.", - "rr4---sn-aigl6nsk.gvt1.com.", "rr4---sn-aigl6nsr.googlevideo.com.", "rr4---sn-aigl6nz7.googlevideo.com.", "rr4---sn-aigl6nze.googlevideo.com.", "rr4---sn-aigl6nzk.googlevideo.com.", + "rr4---sn-aigl6nzk.gvt1.com.", "rr4---sn-aigl6nzl.googlevideo.com.", "rr4---sn-aigl6nzr.googlevideo.com.", "rr4---sn-aigl6nzs.googlevideo.com.", @@ -5653,53 +5434,40 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-aigzrnz7.googlevideo.com.", "rr4---sn-aigzrnze.googlevideo.com.", "rr4---sn-ajab55-55.googlevideo.com.", - "rr4---sn-bg5oqxjvh-50nz.googlevideo.com.", - "rr4---sn-c0q7lnly.googlevideo.com.", - "rr4---sn-c0q7lns7.googlevideo.com.", - "rr4---sn-c0q7lnsl.googlevideo.com.", - "rr4---sn-c0q7lnz7.googlevideo.com.", + "rr4---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr4---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", + "rr4---sn-bvvbaxivnuxqjvhj5nu-hp5s.googlevideo.com.", "rr4---sn-cvb7lne7.googlevideo.com.", "rr4---sn-cvb7lnee.googlevideo.com.", "rr4---sn-cvb7lnlz.googlevideo.com.", "rr4---sn-cvb7sn7k.googlevideo.com.", "rr4---sn-cvb7sn7r.googlevideo.com.", + "rr4---sn-hgn7rn7r.googlevideo.com.", "rr4---sn-hjoj-gq0l.googlevideo.com.", "rr4---sn-hoa7kn76.googlevideo.com.", "rr4---sn-hoa7kn7z.googlevideo.com.", "rr4---sn-hoa7rn76.googlevideo.com.", "rr4---sn-hoa7rn7z.googlevideo.com.", "rr4---sn-hp57kn6r.googlevideo.com.", - "rr4---sn-hp57kn6r.gvt1.com.", "rr4---sn-hp57kn6y.googlevideo.com.", "rr4---sn-hp57knd6.googlevideo.com.", - "rr4---sn-hp57knd6.gvt1.com.", "rr4---sn-hp57kndd.googlevideo.com.", "rr4---sn-hp57kndr.googlevideo.com.", - "rr4---sn-hp57kndr.gvt1.com.", "rr4---sn-hp57knds.googlevideo.com.", - "rr4---sn-hp57knds.gvt1.com.", "rr4---sn-hp57kndy.googlevideo.com.", - "rr4---sn-hp57kndy.gvt1.com.", "rr4---sn-hp57kndz.googlevideo.com.", - "rr4---sn-hp57kndz.gvt1.com.", "rr4---sn-hp57yn7r.googlevideo.com.", "rr4---sn-hp57yn7y.googlevideo.com.", "rr4---sn-hp57yne7.googlevideo.com.", "rr4---sn-hp57ynee.googlevideo.com.", "rr4---sn-hp57ynl6.googlevideo.com.", "rr4---sn-hp57ynlr.googlevideo.com.", - "rr4---sn-hp57ynlr.gvt1.com.", "rr4---sn-hp57ynly.googlevideo.com.", - "rr4---sn-hp57ynly.gvt1.com.", "rr4---sn-hp57yns7.googlevideo.com.", + "rr4---sn-hp57ynse.googlevideo.com.", "rr4---sn-hp57ynsl.googlevideo.com.", "rr4---sn-hp57ynss.googlevideo.com.", - "rr4---sn-hp57ynss.gvt1.com.", - "rr4---sn-huxaqvv-ubqe.googlevideo.com.", - "rr4---sn-huxaqvv-ubqe.gvt1.com.", - "rr4---sn-i3b7kn6s.googlevideo.com.", "rr4---sn-i3b7knld.googlevideo.com.", - "rr4---sn-i3b7knlk.googlevideo.com.", "rr4---sn-i3b7kns6.googlevideo.com.", "rr4---sn-i3b7knsd.googlevideo.com.", "rr4---sn-i3b7knse.googlevideo.com.", @@ -5707,25 +5475,15 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-i3b7knzl.googlevideo.com.", "rr4---sn-i3b7knzs.googlevideo.com.", "rr4---sn-i3belne6.googlevideo.com.", - "rr4---sn-i3belney.googlevideo.com.", - "rr4---sn-i3belnl6.googlevideo.com.", "rr4---sn-i3belnl7.googlevideo.com.", - "rr4---sn-i3belnll.googlevideo.com.", - "rr4---sn-i3belnlz.googlevideo.com.", + "rr4---sn-i3belnls.googlevideo.com.", "rr4---sn-i3bssn7e.googlevideo.com.", "rr4---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr4---sn-jvhj5nu-2iae.googlevideo.com.", - "rr4---sn-jvhj5nu-2ial.googlevideo.com.", - "rr4---sn-jvhj5nu-2ias.googlevideo.com.", - "rr4---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr4---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr4---sn-jvhj5nu-qufe.googlevideo.com.", - "rr4---sn-jvhj5nu-qufl.googlevideo.com.", - "rr4---sn-jvhj5nu-qufs.googlevideo.com.", - "rr4---sn-jvhj5nu-qufz.googlevideo.com.", "rr4---sn-jxopj-n5oe.googlevideo.com.", "rr4---sn-jxopj-nh4e.googlevideo.com.", "rr4---sn-jxopj-nh4e.gvt1.com.", + "rr4---sn-muxa-2iae.googlevideo.com.", "rr4---sn-n4v7snee.googlevideo.com.", "rr4---sn-n4v7sney.googlevideo.com.", "rr4---sn-n4v7snl7.googlevideo.com.", @@ -5735,9 +5493,9 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-n4v7snly.googlevideo.com.", "rr4---sn-n4v7sns7.googlevideo.com.", "rr4---sn-n4v7snse.googlevideo.com.", - "rr4---sn-n4v7snse.gvt1.com.", "rr4---sn-npoe7ndl.googlevideo.com.", "rr4---sn-npoe7nds.googlevideo.com.", + "rr4---sn-npoe7ne6.googlevideo.com.", "rr4---sn-npoe7ne7.googlevideo.com.", "rr4---sn-npoe7ned.googlevideo.com.", "rr4---sn-npoe7nek.googlevideo.com.", @@ -5748,8 +5506,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-npoe7nl6.googlevideo.com.", "rr4---sn-npoe7nlz.googlevideo.com.", "rr4---sn-npoe7ns6.googlevideo.com.", - "rr4---sn-npoe7ns7.googlevideo.com.", - "rr4---sn-npoe7ns7.gvt1.com.", "rr4---sn-npoe7nsd.googlevideo.com.", "rr4---sn-npoe7nsk.googlevideo.com.", "rr4---sn-npoe7nsl.googlevideo.com.", @@ -5765,7 +5521,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-npoeeney.googlevideo.com.", "rr4---sn-npoeenez.googlevideo.com.", "rr4---sn-npoeenl7.googlevideo.com.", - "rr4---sn-npoeenle.googlevideo.com.", "rr4---sn-npoeenlk.googlevideo.com.", "rr4---sn-npoeenll.googlevideo.com.", "rr4---sn-npoeenly.googlevideo.com.", @@ -5778,24 +5533,21 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-npoldn7y.googlevideo.com.", "rr4---sn-npoldn7z.googlevideo.com.", "rr4---sn-npoldne7.googlevideo.com.", - "rr4---sn-ntq7yney.googlevideo.com.", + "rr4---sn-ntq7yner.googlevideo.com.", "rr4---sn-nx57ynlk.googlevideo.com.", - "rr4---sn-nx57ynlk.gvt1.com.", "rr4---sn-nx57ynsd.googlevideo.com.", "rr4---sn-nx57ynse.googlevideo.com.", "rr4---sn-nx57ynsk.googlevideo.com.", + "rr4---sn-nx57ynsk.gvt1.com.", "rr4---sn-nx57ynsl.googlevideo.com.", - "rr4---sn-nx57ynss.googlevideo.com.", - "rr4---sn-nx57ynss.gvt1.com.", "rr4---sn-nx57ynsz.googlevideo.com.", - "rr4---sn-nx57ynsz.gvt1.com.", "rr4---sn-nx5s7n76.googlevideo.com.", "rr4---sn-nx5s7n7d.googlevideo.com.", "rr4---sn-nx5s7n7y.googlevideo.com.", "rr4---sn-nx5s7n7z.googlevideo.com.", "rr4---sn-nx5s7nee.googlevideo.com.", - "rr4---sn-nx5s7nee.gvt1.com.", "rr4---sn-nx5s7nel.googlevideo.com.", + "rr4---sn-o097znsd.googlevideo.com.", "rr4---sn-o097znse.googlevideo.com.", "rr4---sn-o097znsk.googlevideo.com.", "rr4---sn-o097znsl.googlevideo.com.", @@ -5816,7 +5568,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-p5qlsn6l.googlevideo.com.", "rr4---sn-p5qlsn76.googlevideo.com.", "rr4---sn-p5qlsn7d.googlevideo.com.", - "rr4---sn-p5qlsn7d.gvt1.com.", "rr4---sn-p5qlsn7l.googlevideo.com.", "rr4---sn-p5qlsn7s.googlevideo.com.", "rr4---sn-p5qlsnd6.googlevideo.com.", @@ -5825,30 +5576,23 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-p5qlsndz.googlevideo.com.", "rr4---sn-p5qlsnrl.googlevideo.com.", "rr4---sn-p5qlsnrr.googlevideo.com.", - "rr4---sn-p5qlsnrr.gvt1.com.", "rr4---sn-p5qlsny6.googlevideo.com.", "rr4---sn-p5qs7n6d.googlevideo.com.", "rr4---sn-p5qs7nsk.googlevideo.com.", + "rr4---sn-p5qs7nsk.gvt1.com.", "rr4---sn-p5qs7nsr.googlevideo.com.", "rr4---sn-p5qs7nzk.googlevideo.com.", "rr4---sn-p5qs7nzr.googlevideo.com.", "rr4---sn-p5qs7nzy.googlevideo.com.", "rr4---sn-q4fl6n66.googlevideo.com.", "rr4---sn-q4fl6n6d.googlevideo.com.", - "rr4---sn-q4fl6n6d.gvt1.com.", "rr4---sn-q4fl6n6r.googlevideo.com.", - "rr4---sn-q4fl6n6r.gvt1.com.", "rr4---sn-q4fl6n6s.googlevideo.com.", - "rr4---sn-q4fl6n6s.gvt1.com.", "rr4---sn-q4fl6n6y.googlevideo.com.", - "rr4---sn-q4fl6n6y.gvt1.com.", "rr4---sn-q4fl6n6z.googlevideo.com.", "rr4---sn-q4fl6nd7.googlevideo.com.", - "rr4---sn-q4fl6nd7.gvt1.com.", "rr4---sn-q4fl6nde.googlevideo.com.", - "rr4---sn-q4fl6nde.gvt1.com.", "rr4---sn-q4fl6ndl.googlevideo.com.", - "rr4---sn-q4fl6ndl.gvt1.com.", "rr4---sn-q4fl6nds.googlevideo.com.", "rr4---sn-q4fl6nds.gvt1.com.", "rr4---sn-q4fl6ndz.googlevideo.com.", @@ -5858,54 +5602,47 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-q4fl6ns6.gvt1.com.", "rr4---sn-q4fl6ns7.googlevideo.com.", "rr4---sn-q4fl6nsd.googlevideo.com.", - "rr4---sn-q4fl6nsd.gvt1.com.", "rr4---sn-q4fl6nsk.googlevideo.com.", "rr4---sn-q4fl6nsl.googlevideo.com.", "rr4---sn-q4fl6nsr.googlevideo.com.", "rr4---sn-q4fl6nss.googlevideo.com.", - "rr4---sn-q4fl6nss.gvt1.com.", "rr4---sn-q4fl6nsy.googlevideo.com.", - "rr4---sn-q4fl6nsy.gvt1.com.", "rr4---sn-q4fl6nz6.googlevideo.com.", - "rr4---sn-q4fl6nz6.gvt1.com.", "rr4---sn-q4fl6nz7.googlevideo.com.", - "rr4---sn-q4fl6nz7.gvt1.com.", "rr4---sn-q4fl6nzy.googlevideo.com.", "rr4---sn-q4flrn7k.googlevideo.com.", "rr4---sn-q4flrn7k.gvt1.com.", "rr4---sn-q4flrn7r.googlevideo.com.", "rr4---sn-q4flrn7y.googlevideo.com.", - "rr4---sn-q4flrn7y.gvt1.com.", "rr4---sn-q4flrne6.googlevideo.com.", "rr4---sn-q4flrne6.gvt1.com.", "rr4---sn-q4flrne7.googlevideo.com.", "rr4---sn-q4flrnee.googlevideo.com.", "rr4---sn-q4flrnek.googlevideo.com.", - "rr4---sn-q4flrnel.googlevideo.com.", "rr4---sn-q4flrner.googlevideo.com.", + "rr4---sn-q4flrnes.googlevideo.com.", "rr4---sn-q4flrney.googlevideo.com.", "rr4---sn-q4flrney.gvt1.com.", "rr4---sn-q4flrnez.googlevideo.com.", - "rr4---sn-q4flrnez.gvt1.com.", "rr4---sn-q4flrnl6.googlevideo.com.", "rr4---sn-q4flrnl7.googlevideo.com.", + "rr4---sn-q4flrnl7.gvt1.com.", "rr4---sn-q4flrnld.googlevideo.com.", "rr4---sn-q4flrnld.gvt1.com.", "rr4---sn-q4flrnle.googlevideo.com.", + "rr4---sn-q4flrnle.gvt1.com.", + "rr4---sn-q4flrnlz.googlevideo.com.", "rr4---sn-q4flrnsd.googlevideo.com.", "rr4---sn-q4flrnsk.googlevideo.com.", "rr4---sn-q4flrnsl.googlevideo.com.", "rr4---sn-q4flrnss.googlevideo.com.", "rr4---sn-q4flrnss.gvt1.com.", "rr4---sn-q4fzen7e.googlevideo.com.", - "rr4---sn-q4fzen7e.gvt1.com.", "rr4---sn-q4fzen7l.googlevideo.com.", "rr4---sn-q4fzen7l.gvt1.com.", - "rr4---sn-q4fzen7r.googlevideo.com.", - "rr4---sn-q4fzen7r.gvt1.com.", "rr4---sn-q4fzen7s.googlevideo.com.", + "rr4---sn-q4fzen7s.gvt1.com.", "rr4---sn-q4fzen7y.googlevideo.com.", - "rr4---sn-q4fzen7y.gvt1.com.", "rr4---sn-q4fzene7.googlevideo.com.", "rr4---sn-q4fzenee.googlevideo.com.", "rr4---sn-q4fzenee.gvt1.com.", @@ -5916,14 +5653,11 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-qxoedn7k.googlevideo.com.", "rr4---sn-qxoedne7.googlevideo.com.", "rr4---sn-qxoednee.googlevideo.com.", - "rr4---sn-u1hp55-5c.googlevideo.com.", - "rr4---sn-u1hp55-5c.gvt1.com.", "rr4---sn-v5goxu-jhil.googlevideo.com.", + "rr4---sn-v5goxu-jhil.gvt1.com.", "rr4---sn-vgqskn66.googlevideo.com.", - "rr4---sn-vgqskn66.gvt1.com.", "rr4---sn-vgqskn67.googlevideo.com.", "rr4---sn-vgqskn6d.googlevideo.com.", - "rr4---sn-vgqskn6d.gvt1.com.", "rr4---sn-vgqskn6s.googlevideo.com.", "rr4---sn-vgqskn6z.googlevideo.com.", "rr4---sn-vgqskne6.googlevideo.com.", @@ -5935,74 +5669,71 @@ var FakeECSFQDNs = container.NewMapSet( "rr4---sn-vgqsknlk.googlevideo.com.", "rr4---sn-vgqsknll.googlevideo.com.", "rr4---sn-vgqsknlr.googlevideo.com.", - "rr4---sn-vgqsknlr.gvt1.com.", "rr4---sn-vgqsknls.googlevideo.com.", "rr4---sn-vgqsknly.googlevideo.com.", "rr4---sn-vgqsknlz.googlevideo.com.", "rr4---sn-vgqskns7.googlevideo.com.", "rr4---sn-vgqsknse.googlevideo.com.", + "rr4---sn-vgqsknse.gvt1.com.", "rr4---sn-vgqsknsk.googlevideo.com.", "rr4---sn-vgqsknz6.googlevideo.com.", "rr4---sn-vgqsknz7.googlevideo.com.", "rr4---sn-vgqsknzd.googlevideo.com.", - "rr4---sn-vgqsknzd.gvt1.com.", "rr4---sn-vgqsknze.googlevideo.com.", "rr4---sn-vgqsknzk.googlevideo.com.", - "rr4---sn-vgqsknzk.gvt1.com.", "rr4---sn-vgqsknzl.googlevideo.com.", "rr4---sn-vgqsknzr.googlevideo.com.", + "rr4---sn-vgqsknzr.gvt1.com.", "rr4---sn-vgqsknzs.googlevideo.com.", "rr4---sn-vgqsknzy.googlevideo.com.", - "rr4---sn-vgqsknzy.gvt1.com.", "rr4---sn-vgqsknzz.googlevideo.com.", - "rr4---sn-vgqsknzz.gvt1.com.", "rr4---sn-vgqsrn66.googlevideo.com.", - "rr4---sn-vgqsrn66.gvt1.com.", "rr4---sn-vgqsrn67.googlevideo.com.", "rr4---sn-vgqsrn6e.googlevideo.com.", "rr4---sn-vgqsrn6l.googlevideo.com.", "rr4---sn-vgqsrn6z.googlevideo.com.", "rr4---sn-vgqsrne6.googlevideo.com.", - "rr4---sn-vgqsrne6.gvt1.com.", "rr4---sn-vgqsrned.googlevideo.com.", "rr4---sn-vgqsrnek.googlevideo.com.", "rr4---sn-vgqsrnes.googlevideo.com.", "rr4---sn-vgqsrnez.googlevideo.com.", + "rr4---sn-vgqsrnl6.googlevideo.com.", "rr4---sn-vgqsrnld.googlevideo.com.", "rr4---sn-vgqsrnlk.googlevideo.com.", - "rr4---sn-vgqsrnlk.gvt1.com.", "rr4---sn-vgqsrnll.googlevideo.com.", "rr4---sn-vgqsrnls.googlevideo.com.", "rr4---sn-vgqsrnlz.googlevideo.com.", "rr4---sn-vgqsrns6.googlevideo.com.", "rr4---sn-vgqsrnsd.googlevideo.com.", - "rr4---sn-vgqsrnsd.gvt1.com.", "rr4---sn-vgqsrnsr.googlevideo.com.", + "rr4---sn-vgqsrnsy.googlevideo.com.", "rr4---sn-vgqsrnz6.googlevideo.com.", + "rr4---sn-vgqsrnz7.googlevideo.com.", "rr4---sn-vgqsrnzd.googlevideo.com.", "rr4---sn-vgqsrnzk.googlevideo.com.", "rr4---sn-vgqsrnzr.googlevideo.com.", - "rr4---sn-vgqsrnzr.gvt1.com.", "rr4---sn-vgqsrnzs.googlevideo.com.", "rr4---sn-vgqsrnzy.googlevideo.com.", "rr4---sn-vgqsrnzz.googlevideo.com.", - "rr4---sn-vgqsrnzz.gvt1.com.", - "rr4.sn-4g5lznes.googlevideo.com.", - "rr4.sn-5hneknee.googlevideo.com.", - "rr4.sn-q4fl6nlz.googlevideo.com.", - "rr4.sn-q4fl6nzy.googlevideo.com.", - "rr4.sn-q4flrnsd.googlevideo.com.", - "rr4.sn-q4fzen7r.googlevideo.com.", + "rr4.sn-hp57knds.googlevideo.com.", + "rr4.sn-p5qs7nsk.googlevideo.com.", + "rr4.sn-q4fl6nsd.googlevideo.com.", + "rr4.sn-q4flrn7r.googlevideo.com.", + "rr4.sn-q4fzen7l.googlevideo.com.", + "rr4.sn-vgqsrnzz.googlevideo.com.", "rr5---sn-0nnpbo5a-bggl.googlevideo.com.", + "rr5---sn-2aqu-hoas7.googlevideo.com.", "rr5---sn-2imern76.googlevideo.com.", - "rr5---sn-2imern76.gvt1.com.", "rr5---sn-2imern7d.googlevideo.com.", "rr5---sn-2imern7d.gvt1.com.", "rr5---sn-2imeyn7k.googlevideo.com.", "rr5---sn-2oaig5-55.googlevideo.com.", "rr5---sn-30a7rne6.googlevideo.com.", + "rr5---sn-30a7rned.googlevideo.com.", "rr5---sn-30a7rnek.googlevideo.com.", "rr5---sn-30a7rner.googlevideo.com.", + "rr5---sn-30a7ynek.googlevideo.com.", + "rr5---sn-30a7yner.googlevideo.com.", "rr5---sn-30a7yney.googlevideo.com.", "rr5---sn-30a7ynl7.googlevideo.com.", "rr5---sn-4g5e6ns6.googlevideo.com.", @@ -6056,19 +5787,22 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-4g5lznle.googlevideo.com.", "rr5---sn-4g5lznls.googlevideo.com.", "rr5---sn-4g5lznlz.googlevideo.com.", + "rr5---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr5---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr5---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", + "rr5---sn-5goeenez.googlevideo.com.", "rr5---sn-5hne6n6e.googlevideo.com.", "rr5---sn-5hne6n6l.googlevideo.com.", "rr5---sn-5hne6ns6.googlevideo.com.", + "rr5---sn-5hne6nsd.googlevideo.com.", "rr5---sn-5hne6nsk.googlevideo.com.", "rr5---sn-5hne6nsr.googlevideo.com.", "rr5---sn-5hne6nsy.googlevideo.com.", "rr5---sn-5hne6nsz.googlevideo.com.", "rr5---sn-5hne6nz6.googlevideo.com.", - "rr5---sn-5hne6nz6.gvt1.com.", "rr5---sn-5hne6nzd.googlevideo.com.", "rr5---sn-5hne6nzk.googlevideo.com.", "rr5---sn-5hne6nzs.googlevideo.com.", - "rr5---sn-5hne6nzs.gvt1.com.", "rr5---sn-5hne6nzy.googlevideo.com.", "rr5---sn-5hnednss.googlevideo.com.", "rr5---sn-5hnednsz.googlevideo.com.", @@ -6105,28 +5839,27 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-5ualdns6.googlevideo.com.", "rr5---sn-5ualdns7.googlevideo.com.", "rr5---sn-5ualdnsd.googlevideo.com.", - "rr5---sn-5ualdnse.googlevideo.com.", "rr5---sn-5ualdnsk.googlevideo.com.", "rr5---sn-5ualdnsl.googlevideo.com.", "rr5---sn-5ualdnsr.googlevideo.com.", "rr5---sn-5ualdnss.googlevideo.com.", "rr5---sn-5ualdnsy.googlevideo.com.", - "rr5---sn-5ualdnsz.googlevideo.com.", "rr5---sn-5ualdnz7.googlevideo.com.", "rr5---sn-5ualdnze.googlevideo.com.", - "rr5---sn-8qj-nbo66.googlevideo.com.", "rr5---sn-8xgp1vo-2iae7.googlevideo.com.", - "rr5---sn-8xgp1vo-a5me.googlevideo.com.", "rr5---sn-8xgp1vo-ab56.googlevideo.com.", "rr5---sn-8xgp1vo-ab5d.googlevideo.com.", "rr5---sn-8xgp1vo-ab5e.googlevideo.com.", "rr5---sn-8xgp1vo-ab5s.googlevideo.com.", "rr5---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr5---sn-8xgp1vo-p5ie.googlevideo.com.", + "rr5---sn-8xgp1vo-p5qe.googlevideo.com.", + "rr5---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr5---sn-8xgp1vo-p5ql.googlevideo.com.", + "rr5---sn-8xgp1vo-p5qs.googlevideo.com.", + "rr5---sn-8xgp1vo-p5qy.googlevideo.com.", "rr5---sn-8xgp1vo-vgqe.googlevideo.com.", - "rr5---sn-8xgp1vo-xfge.googlevideo.com.", "rr5---sn-8xgp1vo-xfgl.googlevideo.com.", - "rr5---sn-9gv76n7l.googlevideo.com.", + "rr5---sn-9gv76n7e.googlevideo.com.", "rr5---sn-9gv76n7s.googlevideo.com.", "rr5---sn-9gv76n7z.googlevideo.com.", "rr5---sn-9gv7ene6.googlevideo.com.", @@ -6138,46 +5871,37 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-a5m7lnl6.googlevideo.com.", "rr5---sn-a5m7lnld.googlevideo.com.", "rr5---sn-a5mekn6d.googlevideo.com.", - "rr5---sn-a5mekn6d.gvt1.com.", "rr5---sn-a5mekn6k.googlevideo.com.", "rr5---sn-a5mekn6l.googlevideo.com.", - "rr5---sn-a5mekn6l.gvt1.com.", "rr5---sn-a5mekn6r.googlevideo.com.", "rr5---sn-a5mekn6s.googlevideo.com.", "rr5---sn-a5mekn6z.googlevideo.com.", "rr5---sn-a5meknd6.googlevideo.com.", "rr5---sn-a5meknde.googlevideo.com.", - "rr5---sn-a5meknde.gvt1.com.", "rr5---sn-a5mekndl.googlevideo.com.", "rr5---sn-a5meknds.googlevideo.com.", - "rr5---sn-a5meknds.gvt1.com.", "rr5---sn-a5mekndz.googlevideo.com.", "rr5---sn-a5meknsd.googlevideo.com.", "rr5---sn-a5meknsy.googlevideo.com.", "rr5---sn-a5meknzk.googlevideo.com.", - "rr5---sn-a5meknzk.gvt1.com.", + "rr5---sn-a5meknzl.googlevideo.com.", "rr5---sn-a5meknzr.googlevideo.com.", "rr5---sn-a5meknzs.googlevideo.com.", - "rr5---sn-a5meknzs.gvt1.com.", "rr5---sn-a5mlrnek.googlevideo.com.", "rr5---sn-a5mlrnl6.googlevideo.com.", - "rr5---sn-a5mlrnl6.gvt1.com.", "rr5---sn-a5mlrnll.googlevideo.com.", "rr5---sn-a5mlrnls.googlevideo.com.", - "rr5---sn-a5mlrnls.gvt1.com.", "rr5---sn-a5mlrnlz.googlevideo.com.", - "rr5---sn-a5mlrnlz.gvt1.com.", + "rr5---sn-a5msen76.googlevideo.com.", "rr5---sn-a5msen7l.googlevideo.com.", "rr5---sn-a5msen7s.googlevideo.com.", "rr5---sn-a5msen7z.googlevideo.com.", "rr5---sn-a5msenek.googlevideo.com.", "rr5---sn-a5msener.googlevideo.com.", - "rr5---sn-a5msener.gvt1.com.", "rr5---sn-a5msenes.googlevideo.com.", "rr5---sn-a5msenl7.googlevideo.com.", "rr5---sn-a5msenle.googlevideo.com.", "rr5---sn-a5msenll.googlevideo.com.", - "rr5---sn-a5msenll.gvt1.com.", "rr5---sn-ab5l6ndr.googlevideo.com.", "rr5---sn-ab5l6ndy.googlevideo.com.", "rr5---sn-ab5l6nk6.googlevideo.com.", @@ -6187,16 +5911,13 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-ab5l6nrk.googlevideo.com.", "rr5---sn-ab5l6nrl.googlevideo.com.", "rr5---sn-ab5l6nrr.googlevideo.com.", - "rr5---sn-ab5l6nrr.gvt1.com.", "rr5---sn-ab5l6nrs.googlevideo.com.", "rr5---sn-ab5l6nrz.googlevideo.com.", "rr5---sn-ab5sznld.googlevideo.com.", "rr5---sn-ab5sznly.googlevideo.com.", "rr5---sn-ab5sznz6.googlevideo.com.", "rr5---sn-ab5sznzd.googlevideo.com.", - "rr5---sn-ab5sznzd.gvt1.com.", "rr5---sn-ab5sznze.googlevideo.com.", - "rr5---sn-ab5sznze.gvt1.com.", "rr5---sn-ab5sznzk.googlevideo.com.", "rr5---sn-ab5sznzl.googlevideo.com.", "rr5---sn-ab5sznzr.googlevideo.com.", @@ -6206,7 +5927,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-aigl6n6s.googlevideo.com.", "rr5---sn-aigl6ned.googlevideo.com.", "rr5---sn-aigl6nek.googlevideo.com.", - "rr5---sn-aigl6nek.gvt1.com.", "rr5---sn-aigl6ner.googlevideo.com.", "rr5---sn-aigl6ney.googlevideo.com.", "rr5---sn-aigl6nl7.googlevideo.com.", @@ -6215,12 +5935,12 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-aigl6nsk.googlevideo.com.", "rr5---sn-aigl6nsr.googlevideo.com.", "rr5---sn-aigl6nz7.googlevideo.com.", + "rr5---sn-aigl6nz7.gvt1.com.", "rr5---sn-aigl6nze.googlevideo.com.", "rr5---sn-aigl6nzk.googlevideo.com.", "rr5---sn-aigl6nzl.googlevideo.com.", "rr5---sn-aigl6nzr.googlevideo.com.", "rr5---sn-aigl6nzs.googlevideo.com.", - "rr5---sn-aigl6nzs.gvt1.com.", "rr5---sn-aigzrn76.googlevideo.com.", "rr5---sn-aigzrn7d.googlevideo.com.", "rr5---sn-aigzrn7e.googlevideo.com.", @@ -6238,46 +5958,34 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-aigzrnze.googlevideo.com.", "rr5---sn-aj5ua5-5c.googlevideo.com.", "rr5---sn-ajab55-55.googlevideo.com.", - "rr5---sn-bg5oqxjvh-50nz.googlevideo.com.", - "rr5---sn-c0q7lns7.googlevideo.com.", - "rr5---sn-c0q7lnz7.googlevideo.com.", + "rr5---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr5---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", "rr5---sn-cvb7lne7.googlevideo.com.", "rr5---sn-cvb7lnee.googlevideo.com.", - "rr5---sn-cvb7lnls.googlevideo.com.", - "rr5---sn-cvb7lnlz.googlevideo.com.", "rr5---sn-cvb7sn7k.googlevideo.com.", "rr5---sn-cvb7sn7r.googlevideo.com.", - "rr5---sn-hgn7rn7r.googlevideo.com.", "rr5---sn-hoa7kn76.googlevideo.com.", "rr5---sn-hoa7kn7z.googlevideo.com.", "rr5---sn-hoa7rn76.googlevideo.com.", "rr5---sn-hoa7rn7z.googlevideo.com.", "rr5---sn-hp57kn6r.googlevideo.com.", - "rr5---sn-hp57kn6r.gvt1.com.", "rr5---sn-hp57kn6y.googlevideo.com.", - "rr5---sn-hp57kn6y.gvt1.com.", "rr5---sn-hp57knd6.googlevideo.com.", - "rr5---sn-hp57knd6.gvt1.com.", "rr5---sn-hp57kndd.googlevideo.com.", - "rr5---sn-hp57kndd.gvt1.com.", "rr5---sn-hp57kndk.googlevideo.com.", - "rr5---sn-hp57kndk.gvt1.com.", "rr5---sn-hp57kndr.googlevideo.com.", "rr5---sn-hp57knds.googlevideo.com.", "rr5---sn-hp57knds.gvt1.com.", "rr5---sn-hp57kndy.googlevideo.com.", - "rr5---sn-hp57kndy.gvt1.com.", "rr5---sn-hp57kndz.googlevideo.com.", "rr5---sn-hp57yn7r.googlevideo.com.", - "rr5---sn-hp57yn7r.gvt1.com.", "rr5---sn-hp57yn7y.googlevideo.com.", - "rr5---sn-hp57yn7y.gvt1.com.", "rr5---sn-hp57yne7.googlevideo.com.", "rr5---sn-hp57ynee.googlevideo.com.", "rr5---sn-hp57ynl6.googlevideo.com.", - "rr5---sn-hp57ynl6.gvt1.com.", "rr5---sn-hp57ynlr.googlevideo.com.", "rr5---sn-hp57ynly.googlevideo.com.", + "rr5---sn-hp57yns7.googlevideo.com.", "rr5---sn-hp57ynse.googlevideo.com.", "rr5---sn-hp57ynsl.googlevideo.com.", "rr5---sn-hp57ynss.googlevideo.com.", @@ -6285,27 +5993,18 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-i3b7knld.googlevideo.com.", "rr5---sn-i3b7knlk.googlevideo.com.", "rr5---sn-i3b7kns6.googlevideo.com.", + "rr5---sn-i3b7knsd.googlevideo.com.", "rr5---sn-i3b7knse.googlevideo.com.", "rr5---sn-i3b7knsl.googlevideo.com.", "rr5---sn-i3b7knzl.googlevideo.com.", "rr5---sn-i3b7knzs.googlevideo.com.", "rr5---sn-i3belne6.googlevideo.com.", - "rr5---sn-i3belney.googlevideo.com.", "rr5---sn-i3belnl6.googlevideo.com.", "rr5---sn-i3belnl7.googlevideo.com.", - "rr5---sn-i3belnll.googlevideo.com.", "rr5---sn-i3belnls.googlevideo.com.", - "rr5---sn-i3belnlz.googlevideo.com.", "rr5---sn-i3bssn7e.googlevideo.com.", "rr5---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr5---sn-jvhj5nu-2iae.googlevideo.com.", - "rr5---sn-jvhj5nu-2ias.googlevideo.com.", - "rr5---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr5---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr5---sn-jvhj5nu-qufe.googlevideo.com.", - "rr5---sn-jvhj5nu-qufl.googlevideo.com.", - "rr5---sn-jvhj5nu-qufs.googlevideo.com.", - "rr5---sn-jvhj5nu-qufz.googlevideo.com.", "rr5---sn-jxopj-n5oe.googlevideo.com.", "rr5---sn-jxopj-nh4e.googlevideo.com.", "rr5---sn-jxopj-nh4e.gvt1.com.", @@ -6315,6 +6014,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-n4v7snl7.googlevideo.com.", "rr5---sn-n4v7snll.googlevideo.com.", "rr5---sn-n4v7snlr.googlevideo.com.", + "rr5---sn-n4v7snls.googlevideo.com.", "rr5---sn-n4v7snly.googlevideo.com.", "rr5---sn-n4v7sns7.googlevideo.com.", "rr5---sn-n4v7snse.googlevideo.com.", @@ -6347,35 +6047,32 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-npoeeney.googlevideo.com.", "rr5---sn-npoeenez.googlevideo.com.", "rr5---sn-npoeenl7.googlevideo.com.", + "rr5---sn-npoeenle.googlevideo.com.", "rr5---sn-npoeenlk.googlevideo.com.", "rr5---sn-npoeenll.googlevideo.com.", - "rr5---sn-npoeenly.googlevideo.com.", "rr5---sn-npoeens7.googlevideo.com.", "rr5---sn-npoldn76.googlevideo.com.", "rr5---sn-npoldn7d.googlevideo.com.", - "rr5---sn-npoldn7d.gvt1.com.", "rr5---sn-npoldn7e.googlevideo.com.", "rr5---sn-npoldn7l.googlevideo.com.", "rr5---sn-npoldn7s.googlevideo.com.", "rr5---sn-npoldn7y.googlevideo.com.", + "rr5---sn-npoldn7z.googlevideo.com.", "rr5---sn-npoldne7.googlevideo.com.", "rr5---sn-nx57ynlk.googlevideo.com.", "rr5---sn-nx57ynsd.googlevideo.com.", - "rr5---sn-nx57ynsd.gvt1.com.", + "rr5---sn-nx57ynse.googlevideo.com.", "rr5---sn-nx57ynsk.googlevideo.com.", "rr5---sn-nx57ynsk.gvt1.com.", "rr5---sn-nx57ynsl.googlevideo.com.", - "rr5---sn-nx57ynsl.gvt1.com.", "rr5---sn-nx57ynss.googlevideo.com.", - "rr5---sn-nx57ynsz.googlevideo.com.", - "rr5---sn-nx57ynsz.gvt1.com.", "rr5---sn-nx5s7n76.googlevideo.com.", + "rr5---sn-nx5s7n7d.googlevideo.com.", + "rr5---sn-nx5s7n7s.googlevideo.com.", "rr5---sn-nx5s7n7y.googlevideo.com.", "rr5---sn-nx5s7n7z.googlevideo.com.", "rr5---sn-nx5s7nee.googlevideo.com.", - "rr5---sn-nx5s7nee.gvt1.com.", "rr5---sn-nx5s7nel.googlevideo.com.", - "rr5---sn-nx5s7nel.gvt1.com.", "rr5---sn-o097znsd.googlevideo.com.", "rr5---sn-o097znse.googlevideo.com.", "rr5---sn-o097znsk.googlevideo.com.", @@ -6387,9 +6084,7 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-o097znzd.googlevideo.com.", "rr5---sn-o097znze.googlevideo.com.", "rr5---sn-o097znzk.googlevideo.com.", - "rr5---sn-o097znzk.gvt1.com.", "rr5---sn-o097znzr.googlevideo.com.", - "rr5---sn-o097znzr.gvt1.com.", "rr5---sn-oj5hn5-55.googlevideo.com.", "rr5---sn-p5qddn76.googlevideo.com.", "rr5---sn-p5qddn7d.googlevideo.com.", @@ -6400,7 +6095,6 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-p5qlsn76.googlevideo.com.", "rr5---sn-p5qlsn7d.googlevideo.com.", "rr5---sn-p5qlsn7l.googlevideo.com.", - "rr5---sn-p5qlsn7l.gvt1.com.", "rr5---sn-p5qlsn7s.googlevideo.com.", "rr5---sn-p5qlsnd6.googlevideo.com.", "rr5---sn-p5qlsndk.googlevideo.com.", @@ -6408,19 +6102,18 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-p5qlsndz.googlevideo.com.", "rr5---sn-p5qlsnrl.googlevideo.com.", "rr5---sn-p5qlsnrr.googlevideo.com.", - "rr5---sn-p5qlsnrr.gvt1.com.", "rr5---sn-p5qlsny6.googlevideo.com.", "rr5---sn-p5qs7n6d.googlevideo.com.", "rr5---sn-p5qs7nsk.googlevideo.com.", + "rr5---sn-p5qs7nsk.gvt1.com.", "rr5---sn-p5qs7nsr.googlevideo.com.", - "rr5---sn-p5qs7nsr.gvt1.com.", "rr5---sn-p5qs7nzk.googlevideo.com.", "rr5---sn-p5qs7nzr.googlevideo.com.", "rr5---sn-p5qs7nzy.googlevideo.com.", "rr5---sn-q4fl6n66.googlevideo.com.", - "rr5---sn-q4fl6n66.gvt1.com.", "rr5---sn-q4fl6n6d.googlevideo.com.", "rr5---sn-q4fl6n6r.googlevideo.com.", + "rr5---sn-q4fl6n6r.gvt1.com.", "rr5---sn-q4fl6n6s.googlevideo.com.", "rr5---sn-q4fl6n6y.googlevideo.com.", "rr5---sn-q4fl6n6y.gvt1.com.", @@ -6431,90 +6124,79 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-q4fl6nde.gvt1.com.", "rr5---sn-q4fl6ndl.googlevideo.com.", "rr5---sn-q4fl6nds.googlevideo.com.", - "rr5---sn-q4fl6nds.gvt1.com.", "rr5---sn-q4fl6ndz.googlevideo.com.", "rr5---sn-q4fl6ndz.gvt1.com.", "rr5---sn-q4fl6nlz.googlevideo.com.", - "rr5---sn-q4fl6nlz.gvt1.com.", "rr5---sn-q4fl6ns6.googlevideo.com.", "rr5---sn-q4fl6ns7.googlevideo.com.", + "rr5---sn-q4fl6ns7.gvt1.com.", "rr5---sn-q4fl6nsd.googlevideo.com.", "rr5---sn-q4fl6nsk.googlevideo.com.", "rr5---sn-q4fl6nsl.googlevideo.com.", "rr5---sn-q4fl6nsl.gvt1.com.", + "rr5---sn-q4fl6nsr.googlevideo.com.", "rr5---sn-q4fl6nss.googlevideo.com.", "rr5---sn-q4fl6nsy.googlevideo.com.", "rr5---sn-q4fl6nz6.googlevideo.com.", - "rr5---sn-q4fl6nz6.gvt1.com.", "rr5---sn-q4fl6nz7.googlevideo.com.", "rr5---sn-q4fl6nzy.googlevideo.com.", - "rr5---sn-q4fl6nzy.gvt1.com.", + "rr5---sn-q4flrn7k.googlevideo.com.", "rr5---sn-q4flrn7r.googlevideo.com.", - "rr5---sn-q4flrn7r.gvt1.com.", "rr5---sn-q4flrn7y.googlevideo.com.", + "rr5---sn-q4flrn7y.gvt1.com.", + "rr5---sn-q4flrne6.googlevideo.com.", + "rr5---sn-q4flrne6.gvt1.com.", "rr5---sn-q4flrne7.googlevideo.com.", - "rr5---sn-q4flrne7.gvt1.com.", + "rr5---sn-q4flrnee.googlevideo.com.", "rr5---sn-q4flrnek.googlevideo.com.", - "rr5---sn-q4flrnek.gvt1.com.", "rr5---sn-q4flrnel.googlevideo.com.", "rr5---sn-q4flrner.googlevideo.com.", - "rr5---sn-q4flrner.gvt1.com.", "rr5---sn-q4flrnes.googlevideo.com.", "rr5---sn-q4flrney.googlevideo.com.", - "rr5---sn-q4flrney.gvt1.com.", "rr5---sn-q4flrnez.googlevideo.com.", "rr5---sn-q4flrnl6.googlevideo.com.", "rr5---sn-q4flrnl7.googlevideo.com.", "rr5---sn-q4flrnld.googlevideo.com.", + "rr5---sn-q4flrnld.gvt1.com.", "rr5---sn-q4flrnle.googlevideo.com.", "rr5---sn-q4flrnlz.googlevideo.com.", - "rr5---sn-q4flrnlz.gvt1.com.", "rr5---sn-q4flrnsd.googlevideo.com.", "rr5---sn-q4flrnsk.googlevideo.com.", - "rr5---sn-q4flrnsk.gvt1.com.", "rr5---sn-q4flrnsl.googlevideo.com.", "rr5---sn-q4flrnsl.gvt1.com.", "rr5---sn-q4flrnss.googlevideo.com.", - "rr5---sn-q4flrnss.gvt1.com.", "rr5---sn-q4fzen7e.googlevideo.com.", + "rr5---sn-q4fzen7e.gvt1.com.", "rr5---sn-q4fzen7l.googlevideo.com.", - "rr5---sn-q4fzen7r.googlevideo.com.", + "rr5---sn-q4fzen7l.gvt1.com.", "rr5---sn-q4fzen7s.googlevideo.com.", - "rr5---sn-q4fzen7s.gvt1.com.", "rr5---sn-q4fzen7y.googlevideo.com.", "rr5---sn-q4fzen7y.gvt1.com.", "rr5---sn-q4fzene7.googlevideo.com.", - "rr5---sn-q4fzene7.gvt1.com.", "rr5---sn-q4fzenee.googlevideo.com.", "rr5---sn-qjp5q5-55.googlevideo.com.", "rr5---sn-qxo7rn7k.googlevideo.com.", "rr5---sn-qxo7rn7r.googlevideo.com.", "rr5---sn-qxo7rn7y.googlevideo.com.", "rr5---sn-qxoedn7k.googlevideo.com.", + "rr5---sn-qxoedne7.googlevideo.com.", "rr5---sn-qxoednee.googlevideo.com.", - "rr5---sn-u1hp55-5c.googlevideo.com.", - "rr5---sn-u1hp55-5c.gvt1.com.", "rr5---sn-vgqskn66.googlevideo.com.", "rr5---sn-vgqskn67.googlevideo.com.", "rr5---sn-vgqskn6d.googlevideo.com.", "rr5---sn-vgqskn6s.googlevideo.com.", "rr5---sn-vgqskn6z.googlevideo.com.", - "rr5---sn-vgqskn6z.gvt1.com.", "rr5---sn-vgqskne6.googlevideo.com.", - "rr5---sn-vgqskne6.gvt1.com.", "rr5---sn-vgqskned.googlevideo.com.", "rr5---sn-vgqsknek.googlevideo.com.", "rr5---sn-vgqsknes.googlevideo.com.", "rr5---sn-vgqsknez.googlevideo.com.", "rr5---sn-vgqsknld.googlevideo.com.", - "rr5---sn-vgqsknld.gvt1.com.", "rr5---sn-vgqsknlk.googlevideo.com.", "rr5---sn-vgqsknll.googlevideo.com.", "rr5---sn-vgqsknlr.googlevideo.com.", - "rr5---sn-vgqsknlr.gvt1.com.", "rr5---sn-vgqsknls.googlevideo.com.", "rr5---sn-vgqsknly.googlevideo.com.", - "rr5---sn-vgqsknly.gvt1.com.", "rr5---sn-vgqsknlz.googlevideo.com.", "rr5---sn-vgqskns7.googlevideo.com.", "rr5---sn-vgqsknse.googlevideo.com.", @@ -6523,16 +6205,13 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-vgqsknz6.googlevideo.com.", "rr5---sn-vgqsknz7.googlevideo.com.", "rr5---sn-vgqsknzd.googlevideo.com.", - "rr5---sn-vgqsknzd.gvt1.com.", + "rr5---sn-vgqsknze.googlevideo.com.", "rr5---sn-vgqsknzk.googlevideo.com.", "rr5---sn-vgqsknzl.googlevideo.com.", "rr5---sn-vgqsknzr.googlevideo.com.", - "rr5---sn-vgqsknzr.gvt1.com.", "rr5---sn-vgqsknzs.googlevideo.com.", "rr5---sn-vgqsknzy.googlevideo.com.", - "rr5---sn-vgqsknzy.gvt1.com.", "rr5---sn-vgqsknzz.googlevideo.com.", - "rr5---sn-vgqsknzz.gvt1.com.", "rr5---sn-vgqsrn66.googlevideo.com.", "rr5---sn-vgqsrn67.googlevideo.com.", "rr5---sn-vgqsrn6e.googlevideo.com.", @@ -6544,126 +6223,127 @@ var FakeECSFQDNs = container.NewMapSet( "rr5---sn-vgqsrnes.googlevideo.com.", "rr5---sn-vgqsrnez.googlevideo.com.", "rr5---sn-vgqsrnl6.googlevideo.com.", - "rr5---sn-vgqsrnl6.gvt1.com.", "rr5---sn-vgqsrnld.googlevideo.com.", - "rr5---sn-vgqsrnld.gvt1.com.", "rr5---sn-vgqsrnlk.googlevideo.com.", "rr5---sn-vgqsrnll.googlevideo.com.", "rr5---sn-vgqsrnls.googlevideo.com.", "rr5---sn-vgqsrnlz.googlevideo.com.", "rr5---sn-vgqsrns6.googlevideo.com.", - "rr5---sn-vgqsrnsd.googlevideo.com.", "rr5---sn-vgqsrnsr.googlevideo.com.", "rr5---sn-vgqsrnsy.googlevideo.com.", "rr5---sn-vgqsrnz6.googlevideo.com.", "rr5---sn-vgqsrnz7.googlevideo.com.", "rr5---sn-vgqsrnzd.googlevideo.com.", - "rr5---sn-vgqsrnzd.gvt1.com.", "rr5---sn-vgqsrnzk.googlevideo.com.", "rr5---sn-vgqsrnzr.googlevideo.com.", "rr5---sn-vgqsrnzs.googlevideo.com.", "rr5---sn-vgqsrnzy.googlevideo.com.", "rr5---sn-vgqsrnzz.googlevideo.com.", - "rr5.sn-4g5ednsy.googlevideo.com.", - "rr5.sn-hgn7rn7r.googlevideo.com.", + "rr5.sn-hp57knds.googlevideo.com.", + "rr5.sn-p5qs7nsk.googlevideo.com.", "rr5.sn-q4fl6n66.googlevideo.com.", - "rr5.sn-q4fl6nde.googlevideo.com.", + "rr5.sn-q4fl6n6d.googlevideo.com.", "rr5.sn-q4fl6ndl.googlevideo.com.", - "rr5.sn-q4fl6nsk.googlevideo.com.", - "rr5.sn-q4flrnss.googlevideo.com.", - "rr5.sn-q4fzen7s.googlevideo.com.", + "rr5.sn-q4flrne6.googlevideo.com.", + "rr5.sn-q4flrnl7.googlevideo.com.", + "rr5.sn-q4flrnlz.googlevideo.com.", + "rr5.sn-q4flrnsd.googlevideo.com.", + "rr5.sn-q4fzen7l.googlevideo.com.", + "rr5.sn-q4fzenee.googlevideo.com.", + "rr5.sn-vgqsrnzz.googlevideo.com.", + "rr6---sn-2aqu-hoas7.googlevideo.com.", + "rr6---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr6---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", + "rr6---sn-5abxgpxuxaxjvh-j1az.googlevideo.com.", "rr6---sn-5pgnugx5h-hn2z.googlevideo.com.", - "rr6---sn-8qj-nbo66.googlevideo.com.", "rr6---sn-8xgp1vo-ab56.googlevideo.com.", "rr6---sn-8xgp1vo-ab5d.googlevideo.com.", "rr6---sn-8xgp1vo-ab5e.googlevideo.com.", "rr6---sn-8xgp1vo-ab5l.googlevideo.com.", "rr6---sn-8xgp1vo-ab5s.googlevideo.com.", "rr6---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr6---sn-8xgp1vo-p5ie.googlevideo.com.", + "rr6---sn-8xgp1vo-p5qe.googlevideo.com.", + "rr6---sn-8xgp1vo-p5qe7.googlevideo.com.", + "rr6---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr6---sn-8xgp1vo-p5ql.googlevideo.com.", "rr6---sn-8xgp1vo-vgqe.googlevideo.com.", "rr6---sn-8xgp1vo-xfge.googlevideo.com.", "rr6---sn-8xgp1vo-xfgl.googlevideo.com.", "rr6---sn-8xgp1vo-xfgs.googlevideo.com.", - "rr6---sn-bg5oqxjvh-50nz.googlevideo.com.", + "rr6---sn-bvvbaxivnuxqjvhj5nu-hp5e.googlevideo.com.", + "rr6---sn-bvvbaxivnuxqjvhj5nu-hp5l.googlevideo.com.", + "rr6---sn-bvvbaxivnuxqjvhj5nu-hp5s.googlevideo.com.", "rr6---sn-jn2pgx4pcxg-w5os.googlevideo.com.", "rr6---sn-jvhj5nu-2iae.googlevideo.com.", - "rr6---sn-jvhj5nu-2ial.googlevideo.com.", - "rr6---sn-jvhj5nu-2ias.googlevideo.com.", - "rr6---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr6---sn-jvhj5nu-nh4s.googlevideo.com.", "rr6---sn-jvhj5nu-qufe.googlevideo.com.", - "rr6---sn-jvhj5nu-qufl.googlevideo.com.", - "rr6---sn-jvhj5nu-qufs.googlevideo.com.", - "rr6---sn-jvhj5nu-qufz.googlevideo.com.", "rr6---sn-jxopj-n5oe.googlevideo.com.", "rr6---sn-jxopj-nh4e.googlevideo.com.", "rr6---sn-jxopj-nh4e.gvt1.com.", - "rr7---sn-8qj-nbo66.googlevideo.com.", + "rr7---sn-2aqu-hoas7.googlevideo.com.", + "rr7---sn-2aqu-hoasz.googlevideo.com.", + "rr7---sn-5abxgpxuxaxjvh-9n4e.googlevideo.com.", + "rr7---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", "rr7---sn-8xgp1vo-ab56.googlevideo.com.", "rr7---sn-8xgp1vo-ab5d.googlevideo.com.", "rr7---sn-8xgp1vo-ab5e.googlevideo.com.", "rr7---sn-8xgp1vo-ab5l.googlevideo.com.", "rr7---sn-8xgp1vo-ab5s.googlevideo.com.", "rr7---sn-8xgp1vo-ab5z.googlevideo.com.", + "rr7---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr7---sn-8xgp1vo-p5qs.googlevideo.com.", "rr7---sn-8xgp1vo-vgqe.googlevideo.com.", "rr7---sn-8xgp1vo-xfge.googlevideo.com.", - "rr7---sn-jvhj5nu-nh4e.googlevideo.com.", - "rr7---sn-jvhj5nu-nh4s.googlevideo.com.", - "rr7---sn-jvhj5nu-qufe.googlevideo.com.", - "rr7---sn-jvhj5nu-qufl.googlevideo.com.", - "rr7---sn-jvhj5nu-qufs.googlevideo.com.", - "rr7---sn-jvhj5nu-qufz.googlevideo.com.", "rr7---sn-jxopj-n5oe.googlevideo.com.", + "rr8---sn-2aqu-hoas7.googlevideo.com.", + "rr8---sn-5abxgpxuxaxjvh-9n4l.googlevideo.com.", "rr8---sn-8xgp1vo-ab56.googlevideo.com.", "rr8---sn-8xgp1vo-ab5d.googlevideo.com.", "rr8---sn-8xgp1vo-ab5e.googlevideo.com.", "rr8---sn-8xgp1vo-ab5l.googlevideo.com.", "rr8---sn-8xgp1vo-ab5z.googlevideo.com.", - "rr8---sn-8xgp1vo-p5ie.googlevideo.com.", + "rr8---sn-8xgp1vo-p5qe.googlevideo.com.", + "rr8---sn-8xgp1vo-p5qee.googlevideo.com.", + "rr8---sn-8xgp1vo-p5ql.googlevideo.com.", + "rr8---sn-8xgp1vo-p5qs.googlevideo.com.", + "rr8---sn-8xgp1vo-p5qy.googlevideo.com.", "rr8---sn-8xgp1vo-vgqe.googlevideo.com.", "rr8---sn-8xgp1vo-xfge.googlevideo.com.", "rr8---sn-8xgp1vo-xfgl.googlevideo.com.", + "rr9---sn-8xgp1vo-ab56.googlevideo.com.", + "rr9---sn-8xgp1vo-ab5d.googlevideo.com.", + "rr9---sn-8xgp1vo-p5qee.googlevideo.com.", "rs-stripe.alm.com.", - "rs1.qq.com.", - "rs2.qq.com.", "rsmjournals.com.", "rsx.afterpay.com.", - "rt.applvn.com.", "rt.teramind.co.", - "rtb-apac.appocean.media.", - "rtb-apac.rtbserve.io.", - "rtb-eu.rtbserve.io.", - "rtb-useast-v4.qortex.ai.", + "rtb-eu-v4.prertbdir.com.", "rtb-useast.creativedot.net.", + "rtb-useast.openrtb.in.", "rtb-useast.rtbserve.io.", - "rtb-uswest-v4.qortex.ai.", - "rtb2-apac.xaprio.net.", - "rtb2-eu.xaprio.net.", + "rtb-uswest-v4.prertbdir.com.", "rtb2-useast.xaprio.net.", "rtbsuperhub.com.", "rtbwave.com.", "rttf.citrix.com.", + "ru-comonrt-stsdk.vivoglobal.com.", "ru.global.market.xiaomi.com.", + "rubiconmd.zoom.us.", "rubyfish.cn.", - "rudder.immivision.net.", "ruijienetworks.com.", "rule34video.com.", - "rum.perfops.cdb.cdn.orange.com.", - "rumt-sg.com.", "rus-mqtt.transsion-os.com.", "rutgersconnect-my.sharepoint.com.", "rutubelist.ru.", - "rxsafeway-my.sharepoint.com.", - "s-a.innovid.com.", "s-cdn.anthropic.com.", "s-cs.send.microad.jp.", - "s-pinimg-com-cdn-cloudflare-net.pinimg.com.", "s.deepl.com.", "s.seedtag.com.", + "s1.listrakbi.com.cdn.cloudflare.net.", + "s1001-f104.mp.lura.live.", + "s1002-f107.mp.lura.live.", "s2-a.time.mci1.us.rozint.net.", "s2-b.time.mci1.us.rozint.net.", - "s9.gifyu.com.", + "s3-design-language-system.cdn4dd.com.", "sa1.chat.si.riotgames.com.", "sa2.chat.si.riotgames.com.", "sa3.chat.si.riotgames.com.", @@ -6671,10 +6351,13 @@ var FakeECSFQDNs = container.NewMapSet( "sach.gopsahome.info.", "saintasaph.remotepc.com.", "sales.ai.dynamics.com.", - "salidzini.lv.", + "salesbridge.sharepoint.com.", + "salesfire.co.uk.", "saltlakecity.remotepc.com.", + "samip.genetec.com.", "samsclub.quantummetric.com.", "sanantonio.remotepc.com.", + "sandbox.wdesk.com.", "sandboxclient.retinavue.net.", "sandboxregister.retinavue.net.", "sandiego.remotepc.com.", @@ -6687,12 +6370,10 @@ var FakeECSFQDNs = container.NewMapSet( "saopaulo1.remotepc.com.", "sat02pap001.storage.live.com.", "sat02pap002.storage.live.com.", - "sat02pap003.storage.live.com.", - "sat02pap004.storage.live.com.", + "sav.cynet.com.", "sc.zoom.us.", "schneidercorp.com.", "sciener.cn.", - "scm.haplat.net.", "scontent-ams2-1.cdninstagram.com.", "scontent-ams2-1.xx.fbcdn.net.", "scontent-ams4-1.cdninstagram.com.", @@ -6706,6 +6387,7 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-atl3-3.cdninstagram.com.", "scontent-atl3-3.xx.fbcdn.net.", "scontent-ber1-1.cdninstagram.com.", + "scontent-ber1-1.xx.fbcdn.net.", "scontent-bkk1-1.xx.fbcdn.net.", "scontent-bkk1-2.xx.fbcdn.net.", "scontent-bog2-1.cdninstagram.com.", @@ -6730,6 +6412,8 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-cgk2-1.xx.fbcdn.net.", "scontent-den2-1.cdninstagram.com.", "scontent-den2-1.xx.fbcdn.net.", + "scontent-det1-1.cdninstagram.com.", + "scontent-det1-1.xx.fbcdn.net.", "scontent-dfw5-1.cdninstagram.com.", "scontent-dfw5-1.xx.fbcdn.net.", "scontent-dfw5-2.cdninstagram.com.", @@ -6745,8 +6429,8 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-fra5-1.xx.fbcdn.net.", "scontent-fra5-2.cdninstagram.com.", "scontent-fra5-2.xx.fbcdn.net.", + "scontent-gmp1-1.cdninstagram.com.", "scontent-gru1-1.cdninstagram.com.", - "scontent-gru1-1.xx.fbcdn.net.", "scontent-gru1-2.cdninstagram.com.", "scontent-gru1-2.xx.fbcdn.net.", "scontent-gru2-1.cdninstagram.com.", @@ -6789,15 +6473,21 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-lhr8-1.xx.fbcdn.net.", "scontent-lhr8-2.cdninstagram.com.", "scontent-lhr8-2.xx.fbcdn.net.", - "scontent-los2-1.xx.fbcdn.net.", "scontent-man2-1.cdninstagram.com.", "scontent-man2-1.xx.fbcdn.net.", "scontent-mia3-1.cdninstagram.com.", "scontent-mia3-1.xx.fbcdn.net.", "scontent-mia3-2.cdninstagram.com.", "scontent-mia3-2.xx.fbcdn.net.", + "scontent-mia3-3.cdninstagram.com.", + "scontent-mia3-3.xx.fbcdn.net.", "scontent-mnl1-1.xx.fbcdn.net.", "scontent-mnl1-2.xx.fbcdn.net.", + "scontent-mnl3-1.xx.fbcdn.net.", + "scontent-mnl3-2.xx.fbcdn.net.", + "scontent-mrs2-1.xx.fbcdn.net.", + "scontent-mrs2-2.xx.fbcdn.net.", + "scontent-mrs2-3.xx.fbcdn.net.", "scontent-msp1-1.cdninstagram.com.", "scontent-msp1-1.xx.fbcdn.net.", "scontent-mty2-1.cdninstagram.com.", @@ -6835,7 +6525,6 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-sjc3-1.cdninstagram.com.", "scontent-sjc3-1.xx.fbcdn.net.", "scontent-ssn1-1.cdninstagram.com.", - "scontent-tpe1-1.xx.fbcdn.net.", "scontent-vie1-1.cdninstagram.com.", "scontent-vie1-1.xx.fbcdn.net.", "scontent-waw2-1.cdninstagram.com.", @@ -6844,34 +6533,30 @@ var FakeECSFQDNs = container.NewMapSet( "scontent-waw2-2.xx.fbcdn.net.", "scontent-yyz1-1.cdninstagram.com.", "scontent-yyz1-1.xx.fbcdn.net.", - "scouccl1.haplat.net.", - "scouccl2.haplat.net.", - "scout-cdn.salesloft.com.", "scraper2.onlineradiobox.com.", - "scripps.okta.com.", - "script.crazyegg.com.cdn.cloudflare.net.", - "sdataprod.ncaa.com.", "sdk-api-us.maticooads.com.", - "sdk-cdn.onlineaccess1.com.", "sdk.beizi.biz.", "sdk.qcloud.com.", "sdkgate.pushv3.easebar.com.", - "sdktmp.hubcloud.com.cn.", - "seabroadnet.com.", "seagate.com.", "seagullscientific.com.", + "seal-blue.bbb.org.", + "seal-chicago.bbb.org.", + "seal-dallas.bbb.org.", "sealsubscriptions.com.", "search.dnssearch.org.", "search.namequery.com.", + "search.sunbiz.org.", "search.us.namequery.com.", "searchserverapi.com.", "seattle.remotepc.com.", "secaucus.remotepc.com.", "secure.accurint.com.", - "secure.internetdownloadmanager.com.", + "secure.paymentech.com.", "secure.syndetics.com.", + "secure5.arcot.com.", "securelink.med.usc.edu.", - "secureprivacy.ai.", + "securemail.empower.com.", "securetheorem.com.", "securityapi.d3-pr-tm.com.", "seedtag.com.", @@ -6883,81 +6568,67 @@ var FakeECSFQDNs = container.NewMapSet( "sentry.appodeal.com.", "sentry.archive.org.", "sentry.radyushin.com.", + "sentry.voicemod.net.", "sentry.wmt.dev.", "sentry.wrike.com.", - "seo.apps.avada.io.", - "serraview.com.", + "sephora-live.inside-graph.com.", "served-by.pixfuture.com.", - "servedby.flashtalking.com-v1.edgekey.net.", - "service.maxymiser.net.", - "service.wemass.com.", + "server-time.sybo-aps.workers.dev.", "service2.ultipro.com.", - "servicebus102.myconnectsecure.com.", - "servicebus1021.myconnectsecure.com.", - "servicebus1022.myconnectsecure.com.", - "servicebus1023.myconnectsecure.com.", - "servicebus1024.myconnectsecure.com.", - "servicebus1041.myconnectsecure.com.", - "servicebus1042.myconnectsecure.com.", - "servicebus1043.myconnectsecure.com.", - "servicebus1044.myconnectsecure.com.", - "servicebus1051.myconnectsecure.com.", - "servicebus1052.myconnectsecure.com.", - "servicebus1053.myconnectsecure.com.", - "servicebus1054.myconnectsecure.com.", - "services.docusign.net.", + "services-us8.wfs.cloud.", "services.lego.com.", "servicetitan.com.", "servx.opamarketplace.com.", "servx.playstream.media.", "settings.luckyorange.com.", "sevenrooms.com.", + "sewjn80htn-3.algolianet.com.", + "sex4tokens.com.", "sg-o-s3.smartcloudcon.com.", "sg.api.translator.voice.gcloudsdk.com.", - "sg.business.smartcamera.api.io.mi.com.", "sg1a-excel-collab.officeapps.live.com.", "sg1a-powerpoint-collab.officeapps.live.com.", "sgfp.tongdun.net.", "sgmbocast.com.", "sgpcas.ezvizlife.com.", + "share.connect.aig.", + "share.fcgame.net.", "sharpschool.com.", - "shc6.y.qq.com.", "shopcircle.co.", "shopify-gtm-suite.getelevar.com.", - "shopifynetwork.com.", "shortpixel.ai.", - "shp.ee.", - "shredder-eu.osi.office.net.", "shrinetheme.com.", "shuzilm.cn.", + "sigma-qdata-h72.proxima.nie.netease.com.", + "signin.stampsendicia.com.", "signin.ultipro.com.", - "sina.com.", "sinaimg.cn.", "sip-backup.phonepower.com.", "sip-primary.phonepower.com.", "sip.ringcentral.com.", - "sip112-1121.ringcentral.com.", - "sip112-1131.ringcentral.com.", - "sip112-1141.ringcentral.com.", - "sip113-1121.ringcentral.com.", - "sip113-1131.ringcentral.com.", - "sip113-1141.ringcentral.com.", "sip121-1111.ringcentral.com.", "sip121-1121.ringcentral.com.", "sip121-1131.ringcentral.com.", "sip121-1141.ringcentral.com.", - "sip121-1221.ringcentral.com.", + "sip121-1231.ringcentral.com.", + "sip121-1241.ringcentral.com.", + "sip123-1111.ringcentral.com.", "sip123-1121.ringcentral.com.", "sip123-1131.ringcentral.com.", "sip123-1141.ringcentral.com.", "sip123-1211.ringcentral.com.", "sip123-1221.ringcentral.com.", + "sip131-1111.ringcentral.com.", "sip131-1121.ringcentral.com.", "sip131-1131.ringcentral.com.", "sip131-1141.ringcentral.com.", - "sip131-1241.ringcentral.com.", + "sip131-1211.ringcentral.com.", + "sip131-1221.ringcentral.com.", "sip132-1121.ringcentral.com.", + "sip132-1131.ringcentral.com.", "sip132-1141.ringcentral.com.", + "sip132-1221.ringcentral.com.", + "sip132-1231.ringcentral.com.", "sip421-121.ringcentral.biz.", "site-config.com.", "sjc.zoom.us.", @@ -6974,9 +6645,20 @@ var FakeECSFQDNs = container.NewMapSet( "skyward-ocprod.iscorp.com.", "skyward.iscorp.com.", "skyward10.iscorp.com.", + "slb-p2p.vcloud.ks-live.com.", + "slickdealscdn.com.", + "slot365ku.info.", "sm1.selectmedia.asia.", "smartcloudcon.com.", "smarthome.ctdevice.ott4china.com.", + "smoot-api-safari-ause1a.v.aaplimg.com.", + "smoot-api-safari-ause1b.v.aaplimg.com.", + "smoot-api-safari-ause1c.v.aaplimg.com.", + "smoot-api-safari-ause2a.v.aaplimg.com.", + "smoot-api-safari-ause2b.v.aaplimg.com.", + "smoot-api-safari-ause2c.v.aaplimg.com.", + "smoot-api-safari-ausw2b.v.aaplimg.com.", + "smoot-api-safari-ausw2c.v.aaplimg.com.", "smoot-searchv2-aapse1c.v.aaplimg.com.", "smoot-searchv2-aeun1a.v.aaplimg.com.", "smoot-searchv2-aeun1b.v.aaplimg.com.", @@ -6994,20 +6676,15 @@ var FakeECSFQDNs = container.NewMapSet( "sms.ads.heytapmobi.com.", "snap-storage-cdn.l.google.com.", "snippet.affilimatejs.com.", - "snippet.tldw.me.", - "snokido.com.", - "sns-avatar-qc.xhscdn.com.cdn.cloudflare.net.", "snz04pap001.storage.live.com.", "so1506.ci.managedwhitelisting.com.", "sobot.com.", "socialchain.app.", "sockets.stackexchange.com.", "sofia.remotepc.com.", - "sogoucdn.com.", "sohu.com.", - "solarmanpv.com.", "solid.preyproject.com.", - "somplo.com.", + "someaitech.com.", "sonar-akl1-1.xx.fbcdn.net.", "sonar-ams2-1.xx.fbcdn.net.", "sonar-ams4-1.xx.fbcdn.net.", @@ -7019,13 +6696,16 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-ber1-1.xx.fbcdn.net.", "sonar-bkk1-1.xx.fbcdn.net.", "sonar-bkk1-2.xx.fbcdn.net.", + "sonar-blr1-1.xx.fbcdn.net.", + "sonar-blr1-2.xx.fbcdn.net.", "sonar-bog2-1.xx.fbcdn.net.", "sonar-bog2-2.xx.fbcdn.net.", "sonar-bos5-1.xx.fbcdn.net.", "sonar-bru2-1.xx.fbcdn.net.", "sonar-bsb1-1.xx.fbcdn.net.", - "sonar-ccu1-1.xx.fbcdn.net.", "sonar-ccu1-2.xx.fbcdn.net.", + "sonar-ccu2-1.xx.fbcdn.net.", + "sonar-ccu2-2.xx.fbcdn.net.", "sonar-cdg4-1.xx.fbcdn.net.", "sonar-cdg4-2.xx.fbcdn.net.", "sonar-cdg4-3.xx.fbcdn.net.", @@ -7035,6 +6715,7 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-cph2-1.xx.fbcdn.net.", "sonar-cpt1-1.xx.fbcdn.net.", "sonar-den2-1.xx.fbcdn.net.", + "sonar-det1-1.xx.fbcdn.net.", "sonar-dfw5-1.xx.fbcdn.net.", "sonar-dfw5-2.xx.fbcdn.net.", "sonar-doh1-1.xx.fbcdn.net.", @@ -7100,8 +6781,11 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-mct1-2.xx.fbcdn.net.", "sonar-mia3-1.xx.fbcdn.net.", "sonar-mia3-2.xx.fbcdn.net.", + "sonar-mia3-3.xx.fbcdn.net.", "sonar-mnl1-1.xx.fbcdn.net.", "sonar-mnl1-2.xx.fbcdn.net.", + "sonar-mnl3-1.xx.fbcdn.net.", + "sonar-mnl3-2.xx.fbcdn.net.", "sonar-mrs2-1.xx.fbcdn.net.", "sonar-mrs2-2.xx.fbcdn.net.", "sonar-mrs2-3.xx.fbcdn.net.", @@ -7120,6 +6804,7 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-pmo1-1.xx.fbcdn.net.", "sonar-pnq1-1.xx.fbcdn.net.", "sonar-pnq1-2.xx.fbcdn.net.", + "sonar-poa1-1.xx.fbcdn.net.", "sonar-prg1-1.xx.fbcdn.net.", "sonar-qro1-1.xx.fbcdn.net.", "sonar-qro1-2.xx.fbcdn.net.", @@ -7138,10 +6823,8 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-sof1-2.xx.fbcdn.net.", "sonar-ssn1-1.xx.fbcdn.net.", "sonar-syd2-1.xx.fbcdn.net.", - "sonar-tir3-1.xx.fbcdn.net.", "sonar-tir3-2.xx.fbcdn.net.", "sonar-tir3-3.xx.fbcdn.net.", - "sonar-tir3-4.xx.fbcdn.net.", "sonar-tpe1-1.xx.fbcdn.net.", "sonar-vie1-1.xx.fbcdn.net.", "sonar-waw2-1.xx.fbcdn.net.", @@ -7149,36 +6832,57 @@ var FakeECSFQDNs = container.NewMapSet( "sonar-yyz1-1.xx.fbcdn.net.", "sonar-zrh1-1.xx.fbcdn.net.", "sonar.viously.com.", + "sonichealthcareusa.com.", "southafricanorth.api.cognitive.microsoft.com.", "southcarolina.remotepc.com.", "southeastasia.api.cognitive.microsoft.com.", "southindia.api.cognitive.microsoft.com.", "spadsync.com.", "sparteo.com.", + "special-network.com.", "speedtest.us-sc.kamatera.com.", + "spifc.ssl.fun.", "spiny.ai.", "spion.savvy.security.", - "sq.bls.mdt.qq.com.", "src.ebay-us.com.", + "srv-sg10.traffmonetizer.com.", + "srv-sg11.traffmonetizer.com.", + "srv-sg12.traffmonetizer.com.", + "srv-sg13.traffmonetizer.com.", + "srv-sg14.traffmonetizer.com.", + "srv-sg7.traffmonetizer.com.", + "srv-sg8.traffmonetizer.com.", + "srv-sg9.traffmonetizer.com.", + "srv.stalker.to.", "srv2.maxhost.io.", + "srv2.traffmonetizer.com.", + "srv3.traffmonetizer.com.", + "srv4.traffmonetizer.com.", + "srv5.traffmonetizer.com.", + "srv6.traffmonetizer.com.", + "srv7.traffmonetizer.com.", + "srv8.traffmonetizer.com.", + "srv9.traffmonetizer.com.", "ssafp.samsclub.com.", "ssctech.com.", + "sse.devcycle.com.", "ssl.geoplugin.net.", + "sso-forms-prod.cdn-tinkoff.ru.", "sso-stb.jdadelivers.com.", "sso.services.box.net.", "ssp.hbrd.io.", "ssp.hybrid.ai.", - "sst.puma.com.", "sstatic.net.", - "ssxd.mediav.com.", "st-sysupgrade.vivo.com.cn.", "stable.dl2.discordapp.net.", "staffbase.com.", + "staples-us.attn.tv.", + "stappupgrade.vivo.com.cn.", "starbucks-wfmr.jdadelivers.com.", + "starbucks-wfmrclock.jdadelivers.com.", "stardustgod.com.", "starrydyn.com.", "startssl.com.", - "stash.webstaurantstore.com.", "stat.pdfforge.org.", "statad.ru.", "static-atl3-1.xx.fbcdn.net.", @@ -7186,6 +6890,7 @@ var FakeECSFQDNs = container.NewMapSet( "static-atl3-3.xx.fbcdn.net.", "static-bos5-1.xx.fbcdn.net.", "static-den2-1.xx.fbcdn.net.", + "static-det1-1.xx.fbcdn.net.", "static-dfw5-1.xx.fbcdn.net.", "static-dfw5-2.xx.fbcdn.net.", "static-hou1-1.xx.fbcdn.net.", @@ -7195,49 +6900,43 @@ var FakeECSFQDNs = container.NewMapSet( "static-lax3-2.xx.fbcdn.net.", "static-lga3-1.xx.fbcdn.net.", "static-lga3-2.xx.fbcdn.net.", - "static-lga3-3.xx.fbcdn.net.", - "static-lhr6-1.xx.fbcdn.net.", - "static-lhr6-2.xx.fbcdn.net.", "static-lhr8-1.xx.fbcdn.net.", "static-lhr8-2.xx.fbcdn.net.", "static-mia3-1.xx.fbcdn.net.", "static-mia3-2.xx.fbcdn.net.", + "static-mia3-3.xx.fbcdn.net.", "static-msp1-1.xx.fbcdn.net.", - "static-na3p1.sabacloud.com.", "static-ord5-1.xx.fbcdn.net.", "static-ord5-2.xx.fbcdn.net.", "static-ord5-3.xx.fbcdn.net.", - "static-phx1-1.xx.fbcdn.net.", "static-sea1-1.xx.fbcdn.net.", "static-sjc3-1.xx.fbcdn.net.", "static.avito.ru.", + "static.ctctcdn.com.", + "static.getclicky.com.", "static.getliner.com.", - "static.kwcdn.com.", - "static2.mixi.media.", - "static6.mixi.media.", - "static8.mixi.media.", + "static.olark.com.", + "static.rutubelist.ru.", + "static4.mixi.media.", "stats.aeries.com.", - "stats.rip.", + "stats.senty.com.au.", "stats.transitapp.com.", "statsig.anthropic.com.", - "stevemadden.com.", + "stemchristie.rome2rio.com.", "stg-data-in.ads.heytapmobile.com.", "stg-data.ads.heytapmobi.com.", "stockholm.remotepc.com.", "stocks-analytics-events.apple.com.", - "storage.procore.com.", - "store.qq.com.", "store.vsco.co.", - "storeedgefd.dsx.mp.microsoft.com.", - "storymagic.co.", - "streamhub.tech.", + "stp-cdn.inside-graph.com.", + "str-vcode-tracker-fenghuang-prd-bj.vivo.com.cn.", "streetviewpixels-pa.googleapis.com.", - "stripchat.com.", + "stripchats.io.", "stse02.ultipro.com.", "stsew02.ultipro.com.", "stsn02.ultipro.com.", "student.atitesting.com.", - "studentaid.gov.", + "student.xello.world.", "studyquicks.com.", "stun.acrobits.cz.", "stun.cdnbye.com.", @@ -7249,52 +6948,44 @@ var FakeECSFQDNs = container.NewMapSet( "stun2.ringcentral.com.", "stun3.l.google.com.", "stun4.l.google.com.", - "stvinc-my.sharepoint.com.", "su6786.ci.managedwhitelisting.com.", + "subsceness.xyz.", "sumari-prod-1.srv.jbisumari.org.", "sumologic.com.", "sunmi.com.", "supl.qxwz.com.", "support.powerschool.com.", - "sv-ookla.geolinks.com.", "sv8.cyberhaven.io.", + "svideo.ltwebstatic.com.", "svlive.serraview.com.", "swag.maxhost.io.", "swedencentral.api.cognitive.microsoft.com.", "switch.babybus.com.", "switzerlandnorth.api.cognitive.microsoft.com.", - "swoop.com.", "sydney.remotepc.com.", - "synacor-match.dotomi.com.", "sync-1-us-west1-g.sync.services.mozilla.com.", "sync.bidence.net.", + "sync.driftpixel.live.", "sync.inmobi.com.", "sync.oraki.io.", "sync.videowalldirect.com.", - "syncfusion.com.", "syndetics.com.", + "syndication.diveinthebluesky.biz.", "systemreportservices.genetec.com.", + "t-odx.geo2.op-mobile.opera.com.", "t-odx.op-mobile.opera.com.", "t.adcell.com.", - "t.dailymail.co.uk.", "t.marketingcloudfx.com.", "t.mookie1.com.", - "t.poki.io.", - "t.wayfair.com.cdn.cloudflare.net.", - "t1.tiles.virtualearth.net.", - "t13925.iqzonertb.live.", "t3.xiaohongshu.com.", - "t98200.iqzonertb.live.", "tag.winister.app.", - "tagcommander.com.", "tags.natwest.com.", "tampa.remotepc.com.", + "tanjingpaas.com.", "tapecontent.net.", + "taplytics-umami.grubhub.com.", "tapsell.ir.", - "tara.ns.cloudflare.com.", "target.digitalaudience.io.", - "tasks.office.com.", - "tasteofhome.com.", "tatracker-us.rivergame.net.", "tccprod01.honeywell.com.", "tccprod01.resideo.com.", @@ -7303,20 +6994,18 @@ var FakeECSFQDNs = container.NewMapSet( "tccprod03.honeywell.com.", "tccprod03.resideo.com.", "tcdnlive.com.", - "tch.poe.com.", "tclclouds.com.", "tdcservices.tandemdiabetes.com.", "tdm.qq.com.", - "teadv.checkpoint.com.", + "teamsnap.com.", "teamviewer.com.", - "teamviewer.com.cdn.cloudflare.net.", "teddymobile.cn.", - "telegraph-sync.quantummetric.com.", "telemetry-sdk-inmobi-comtm.trafficmanager.net.", "telemetry.savvy.security.", "telemetry.sdk.inmobi.com.", "telephony.goog.", - "teleport.media.", + "tenant-content.apm.appfolio-analytics.com.", + "tenant-data.apm.appfolio-analytics.com.", "tencent-cloud.com.", "tencent-cloud.net.", "tencent.com.", @@ -7325,19 +7014,14 @@ var FakeECSFQDNs = container.NewMapSet( "tenpay.com.", "terabox.app.", "terabox.com.", - "terabox1024.com.", "terms3.hicloud.com.", - "test3.dantri.com.vn.", "tgp.qq.com.", "tgpa.qq.com.", - "thanhnien.vn.", "theoks.net.", "thetracker.org.", "thinkific.com.", "thm.visa.com.", "thm12.visa.com.", - "thumb-v1.xhpingcdn.com.", - "thumb-v4.xhpingcdn.com.", "time-a-b.nist.gov.", "time-a-g.nist.gov.", "time-a.nist.gov.", @@ -7352,9 +7036,8 @@ var FakeECSFQDNs = container.NewMapSet( "time-nw.nist.gov.", "time.circlevps.net.", "time.ecansol.net.", - "time.lmtlabs.com.", "time.nest.com.", - "time.tritan.host.", + "time.va.lmtlabs.com.", "time.walb.tech.", "time1.aliyun.com.", "time1.google.com.", @@ -7370,7 +7053,6 @@ var FakeECSFQDNs = container.NewMapSet( "tk.mosspf.com.", "tk.mossru.com.", "tkx.mp.lura.live.", - "tl.upwork.com.", "tlivecdn.com.", "tlivesource.com.", "tls12.eu01.nr-data.net.cdn.cloudflare.net.", @@ -7379,12 +7061,10 @@ var FakeECSFQDNs = container.NewMapSet( "tm.bdc-cdn.com.", "tm.cybersource.com.", "tm.regions.com.", - "tmfp.klarna.com.", "tmfsdktcp.m.qq.com.", "tmfsdktcpv4.m.qq.com.", "tmga.qq.com.", "tmge.alicdn.com.", - "tmobile-sync.quantummetric.com.", "tmx.bestbuy.com.", "tmx.tdbank.com.", "tmx.uptodate.com.", @@ -7399,47 +7079,15 @@ var FakeECSFQDNs = container.NewMapSet( "tpns.sh.tencent.com.", "tpns.tencent.com.", "tpsservice-files-inner.cn-hangzhou.oss-cdn.aliyun-inc.com.", - "tra-ac-ae.apktorrents.com.", - "tra-ac-ae.best82.com.", - "tra-ac-ae2.apktorrents.com.", - "tra-ac-ae2.best82.com.", - "tra-ac-id.apktorrents.com.", - "tra-ac-id.best82.com.", - "tra-ac-id2.apktorrents.com.", - "tra-ac-id2.best82.com.", - "tra-ac-ind.apktorrents.com.", - "tra-ac-ind.best82.com.", - "tra-ac-mas.apktorrents.com.", - "tra-ac-mas.best82.com.", - "tra-ard-id.apktorrents.com.", - "tra-ard-id.best82.com.", - "tra-hd-id.apktorrents.com.", - "tra-hd-id.best82.com.", - "tra-ht-id.apktorrents.com.", - "tra-ht-id.best82.com.", "tra-hz-de.hyper-torrent.com.", - "tra-hz-fl.hyper-torrent.com.", "tra-lwb-sg.best61.com.", - "tra-s4-us.best61.com.", - "tra-tc-ind.apktorrents.com.", - "tra-tc-ind.best82.com.", - "tra-the-br.apktorrents.com.", - "tra-the-br.best82.com.", - "tra-the-tr.apktorrents.com.", - "tra-the-tr.best82.com.", - "tra-ved-br.apktorrents.com.", - "tra-ved-br.best82.com.", - "tra-ved-in.apktorrents.com.", - "tra-ved-in.best82.com.", - "tra-ved-ru.apktorrents.com.", - "tra-ved-ru.best82.com.", "trace.qq.com.", "track-eu1.hubspot.com.", + "track-fra04-origin.spectrum.hubspot.com.", + "track.rtb-sm.com.", "track.sendlane.com.", - "track.yourrtb.com.", "trackedlink.net.", "tracker-udp.gbitt.info.", - "tracker.auctor.tv.", "tracker.best61.com.", "tracker.files.fm.", "tracker.grepler.com.", @@ -7447,25 +7095,20 @@ var FakeECSFQDNs = container.NewMapSet( "tracker.hyper-torrent.com.", "tracker.newtvcdn.com.", "tracker.nitropay.com.", - "tracker.nwps.ws.", - "tracker.tamersunion.org.", "tracker.theoks.net.", "tracker1.bt.moack.co.kr.", - "tracker1.myporn.club.", - "tracker2.dler.org.", + "tracking.carbonatixaudio.com.", "tracking.eu.flamtyr.com.", - "tracking.ksztone.com.", + "tradovateapi.com.", "tradplusad.com.", - "traductor1.spanishdict.com.", "transaccional.saludtotal.com.co.", "traversal.syncromsp.com.", "treas.gov.", "treasury.gov.", - "trendyol.com.", "tribalfusion.com.", - "trovit.com.", - "ts1.qq.com.", - "ts2.qq.com.", + "trk-tristique.com.", + "truemed.com.", + "trustedstack.com.", "tse1.explicit.bing.net.", "tsms-dre.security.dbankcloud.com.", "tt.browser.360.cn.", @@ -7473,33 +7116,34 @@ var FakeECSFQDNs = container.NewMapSet( "ttuhscep.cyberhaven.io.", "tubecup.net.", "tunnel.googlezip.net.", - "tuoitre.vn.", "turn.cloudflare.com.", "tw.ntp.org.cn.", - "twcloudgz.ucbj.net.", - "twcloudgz1.ucbj.net.", - "twvideogz31.ucbj.net.", + "twvideogz32.ucbj.net.", "twvideohf41.ucbj.net.", "twvideohf42.ucbj.net.", "twvideohf43.ucbj.net.", + "tx-cfg-u1.ubixioe.com.", "tydevice.com.", - "typenetwork.com.", + "u-ams.4dex.io.", + "u-las.4dex.io.", "u.4dex.io.", "uaenorth.api.cognitive.microsoft.com.", - "uam1.dexcom.com.", - "uapi.mp.360.cn.", "uber.zoom.us.", + "ubg235.com.", "uc.cn.", "ucweb.com.", "udemycdn.com.", - "ue.lenovomm.cn.", - "uhabo.com.", - "uhf.microsoft.com.", + "udms.zoom.us.", + "udsp.io.", + "uix-pusa01.app.blackbaud.net.", "uk-api.asm.skype.com.", "uk-prod.asyncgw.teams.microsoft.com.", "uk3-excel-collab.officeapps.live.com.", "uk3-powerpoint-collab.officeapps.live.com.", + "uk3-word-collab.officeapps.live.com.", "uk5-excel-collab.officeapps.live.com.", + "uk5-powerpoint-collab.officeapps.live.com.", + "uk5-word-collab.officeapps.live.com.", "ukc-collabrtc-geo.rtc.trafficmanager.net.", "ukc-collabrtc.officeapps.live.com.", "ukc-excel-collab.officeapps.live.com.", @@ -7510,27 +7154,25 @@ var FakeECSFQDNs = container.NewMapSet( "ultipro.com.", "ultiprotime.com.", "ultiproworkplace.com.", - "umami.is.", + "ultrahuman.com.", "umeng.com.", - "unicom.shuzilm.cn.", "unified-inbox-1-gw.ultipro.com.", "unified-inbox-2-gw.ultipro.com.", "unipay.qq.com.", "unisoc.com.", "united.quantummetric.com.", "unity.cn.", - "uodoo.com.", + "universityofwieauclaire-my.sharepoint.com.", + "unls.mep.go.cr.", "up.railway.app.", "update.ee-share.com.", "update.huorong.cn.", "update.kingsoftstore.com.", - "update.pdfforge.org.", "update.vivaldi.com.", - "update.yealink.com.", "updatechannel.sharegate.com.", "updaterservices.genetec.com.", + "updates.dorkbox.com.", "updatesnl.macrium.com.", - "upload1.systemmonitor.co.uk.", "upravel.com.", "upremium.asia.", "urekamedia.com.", @@ -7541,8 +7183,6 @@ var FakeECSFQDNs = container.NewMapSet( "us-05.ws-api.ringcentral.com.", "us-06.ws-api.ringcentral.com.", "us-api.asm.skype.com.", - "us-atl-anx-r003.router.teamviewer.com.", - "us-atl-anx-r010.router.teamviewer.com.", "us-central1-adaptive-growth.cloudfunctions.net.", "us-central1-adchat-ai.cloudfunctions.net.", "us-central1-addshoppers-data-production.cloudfunctions.net.", @@ -7557,52 +7197,54 @@ var FakeECSFQDNs = container.NewMapSet( "us-central1-fsgenergy-shared.cloudfunctions.net.", "us-central1-gaggle-staging.cloudfunctions.net.", "us-central1-justbuild-cdb86.cloudfunctions.net.", - "us-central1-kube-ownlocal.cloudfunctions.net.", "us-central1-live-prod-1-1.cloudfunctions.net.", "us-central1-locket-4252a.cloudfunctions.net.", - "us-central1-mikmak-microservices.cloudfunctions.net.", + "us-central1-marketplace-production-east4.cloudfunctions.net.", "us-central1-muslim-pro-app.cloudfunctions.net.", - "us-central1-noteit-4dca3.cloudfunctions.net.", "us-central1-royal-match-prod-cce6d.cloudfunctions.net.", "us-central1-shopify-instrumentat-ff788286.cloudfunctions.net.", + "us-central1-speechifydev.cloudfunctions.net.", "us-central1-speechifymobile.cloudfunctions.net.", + "us-central1-sq-sgtm-prod.cloudfunctions.net.", "us-central1-teach-monster.cloudfunctions.net.", "us-central1-tranquil-petal-272922.cloudfunctions.net.", "us-central1-webgltest-17af1.cloudfunctions.net.", "us-central1-wise-arch-107501.cloudfunctions.net.", - "us-chi-anx-r003.router.teamviewer.com.", - "us-den-anx-r002.router.teamviewer.com.", + "us-cmh-gcp-r002.router.teamviewer.com.", + "us-dal-anx-r004.router.teamviewer.com.", + "us-dal-anx-r008.router.teamviewer.com.", + "us-dal-anx-r010.router.teamviewer.com.", "us-den-anx-r008.router.teamviewer.com.", "us-device-scheduler.ymcs.yealink.com.", "us-device.ymcs.yealink.com.", "us-east4-chkp-gcp-rnd-threat-hunt-box.cloudfunctions.net.", - "us-hnl-anx-r001.router.teamviewer.com.", "us-hnl-anx-r002.router.teamviewer.com.", - "us-lax-gcp-r005.router.teamviewer.com.", - "us-mia-anx-r004.router.teamviewer.com.", - "us-mia-anx-r008.router.teamviewer.com.", - "us-mia-anx-r010.router.teamviewer.com.", - "us-mia-anx-r011.router.teamviewer.com.", - "us-mks-gcp-r002.router.teamviewer.com.", - "us-mks-gcp-r004.router.teamviewer.com.", - "us-oma-gcp-r002.router.teamviewer.com.", - "us-pdx-gcp-r001.router.teamviewer.com.", + "us-las-gcp-r005.router.teamviewer.com.", + "us-lax-anx-r003.router.teamviewer.com.", + "us-lax-anx-r008.router.teamviewer.com.", + "us-lax-anx-r011.router.teamviewer.com.", + "us-lax-anx-r013.router.teamviewer.com.", + "us-lax-anx-r014.router.teamviewer.com.", + "us-lax-gcp-r002.router.teamviewer.com.", + "us-lax-gcp-r003.router.teamviewer.com.", + "us-mia-anx-r009.router.teamviewer.com.", + "us-mks-gcp-r003.router.teamviewer.com.", + "us-njc-anx-r005.router.teamviewer.com.", + "us-njc-anx-r015.router.teamviewer.com.", "us-prod.asyncgw.teams.microsoft.com.", - "us-sea-anx-r008.router.teamviewer.com.", - "us-service.cartsee-from.cartx.cloud.", "us-spectrum.rcs.telephony.goog.", - "us-was-anx-r012.router.teamviewer.com.", + "us-was-anx-r001.router.teamviewer.com.", + "us-was-anx-r003.router.teamviewer.com.", + "us-was-anx-r010.router.teamviewer.com.", "us.att.rcs.telephony.goog.", "us.evidation.com.", - "us.hlth.io.mi.com.", - "us.micardapi.micloud.xiaomi.net.", - "us.tmobile.rcs.telephony.goog.", "us.tracfone.rcs.telephony.goog.", "us.ubianet.com.", "us.uscc.rcs.telephony.goog.", "us.xfinity.rcs.telephony.goog.", "us01docs.zoom.us.", "us02log.zoom.us.", + "us02nws-platform.zoom.us.", "us02nws.zoom.us.", "us02polling.zoom.us.", "us02web.zoom.us.", @@ -7610,7 +7252,6 @@ var FakeECSFQDNs = container.NewMapSet( "us04asyncim.zoom.us.", "us04nws-platform.zoom.us.", "us04nws.zoom.us.", - "us04st2.zoom.us.", "us04web.zoom.us.", "us04www3.zoom.us.", "us05nws-platform.zoom.us.", @@ -7623,22 +7264,29 @@ var FakeECSFQDNs = container.NewMapSet( "us06polling.zoom.us.", "us06web.zoom.us.", "us06www3.zoom.us.", + "us2.backdrop.cloud.", + "us3a-excel-collab.ocs.trafficmanager.net.", "us3a-excel-collab.officeapps.live.com.", + "us3a-powerpoint-collab.ocs.trafficmanager.net.", + "us3a-powerpoint-collab.officeapps.live.com.", + "us3a-word-collab.ocs.trafficmanager.net.", + "us3a-word-collab.officeapps.live.com.", "us4b-excel-collab.ocs.trafficmanager.net.", "us4b-excel-collab.officeapps.live.com.", "us4b-powerpoint-collab.ocs.trafficmanager.net.", "us4b-powerpoint-collab.officeapps.live.com.", "us4b-word-collab.ocs.trafficmanager.net.", "us4b-word-collab.officeapps.live.com.", + "us4n-excel-collab.ocs.trafficmanager.net.", "us4n-excel-collab.officeapps.live.com.", "us4n-powerpoint-collab.officeapps.live.com.", "us4n-word-collab.officeapps.live.com.", + "us4s-excel-collab.ocs.trafficmanager.net.", "us4s-excel-collab.officeapps.live.com.", "us4s-powerpoint-collab.officeapps.live.com.", "us4s-word-collab.officeapps.live.com.", "us7-excel-collab.ocs.trafficmanager.net.", "us7-excel-collab.officeapps.live.com.", - "us7-powerpoint-collab.ocs.trafficmanager.net.", "us7-powerpoint-collab.officeapps.live.com.", "us7-word-collab.ocs.trafficmanager.net.", "us7-word-collab.officeapps.live.com.", @@ -7646,67 +7294,63 @@ var FakeECSFQDNs = container.NewMapSet( "us8-excel-collab.officeapps.live.com.", "us8-powerpoint-collab.ocs.trafficmanager.net.", "us8-powerpoint-collab.officeapps.live.com.", + "us8-word-collab.ocs.trafficmanager.net.", "us8-word-collab.officeapps.live.com.", "usbank.quantummetric.com.", - "usc-collabrtc-geo.rtc.trafficmanager.net.", - "usc-collabrtc.officeapps.live.com.", "usc-excel-collab-geo.ocs.trafficmanager.net.", "usc-excel-collab.officeapps.live.com.", "usc-powerpoint-collab.officeapps.live.com.", + "usc-visio.officeapps.live.com.", "usc-word-collab.officeapps.live.com.", "usc.edu.", - "usc.pods.officeapps.live.com.", "uscis.gov.", - "useast.quantumdex.io.", + "usdoj.gov.", + "use4.s.seedtag.com.", "userlike.com.", "usgs.gov.", "ussav.cynet.com.", "usserver.serverapi.org.", "usslb.cynet.com.", + "usw-aiwit-file-push.oss-us-west-1.aliyuncs.com.", "usw.stape.io.", "uswest-beacon.deepintent.com.", "ut.hzshudian.com.", - "uu.qq.com.", + "ute-tech.com.cn.", "uuidksinc.net.", "uxfeedback.ru.", "v.vivintsky.com.", - "v2assets.zopim.io.", - "v31.tiktokcdn.com.", "v39-as.tiktokcdn.com.", "v39-ca.tiktokcdn.com.", "v39-id-telin.tiktokcdn.com.", - "v39-mx.tiktokcdn.com.", "v39-row.gts.byteoversea.net.", "v39-row.tiktokcdn.com.", "v39-us.gts.byteoversea.net.", "v39-us.tiktokcdn.com.", - "v39e-us.tiktokcdn.com.", "v45-br.tiktokcdn.com.", "v45-ph-globe.tiktokcdn.com.", "v6-gdvod.kwaicdn.com.", - "v8.analytics.pinsightmedia.com.", - "v8engine.pinsightmedia.com.", "vador.com.", "vbw.vivoglobal.com.", - "veh-dms.na.ultifi.gm.com.", + "vc-brain-lf.ndcpp.com.", "venafi.com.", "verification.fda.gov.ph.", + "verification.repocket.com.", + "verizon.quantummetric.com.", "verticals.wix.com.", - "vfa.hpplay.cn.", + "verval-snpmb.bppp.kemdikbud.go.id.", "vhx.com.", "vibe.co.", "vibeaconstr.onezapp.com.", "vicoo.tech.", - "video-ams2-1.xx.fbcdn.net.", "video-ams4-1.xx.fbcdn.net.", "video-atl3-1.xx.fbcdn.net.", "video-atl3-2.xx.fbcdn.net.", "video-atl3-3.xx.fbcdn.net.", "video-bos5-1.xx.fbcdn.net.", "video-den2-1.xx.fbcdn.net.", + "video-det1-1.xx.fbcdn.net.", "video-dfw5-1.xx.fbcdn.net.", "video-dfw5-2.xx.fbcdn.net.", - "video-hkg4-1.xx.fbcdn.net.", "video-hou1-1.xx.fbcdn.net.", "video-iad3-1.xx.fbcdn.net.", "video-iad3-2.xx.fbcdn.net.", @@ -7721,6 +7365,7 @@ var FakeECSFQDNs = container.NewMapSet( "video-lhr8-2.xx.fbcdn.net.", "video-mia3-1.xx.fbcdn.net.", "video-mia3-2.xx.fbcdn.net.", + "video-mia3-3.xx.fbcdn.net.", "video-msp1-1.xx.fbcdn.net.", "video-ord5-1.xx.fbcdn.net.", "video-ord5-2.xx.fbcdn.net.", @@ -7728,16 +7373,13 @@ var FakeECSFQDNs = container.NewMapSet( "video-phx1-1.xx.fbcdn.net.", "video-sea1-1.xx.fbcdn.net.", "video-sjc3-1.xx.fbcdn.net.", - "video.unrulymedia.com.", "videocontent-dra.himovie.dbankcloud.com.", "vidmate.net.", "vieon.vn.", - "view.admeking.com.", "vik-ca.moonactive.net.", "viki.com.", "vinted.fr.", "viously.com.", - "vipads.live.", "virgul.com.", "visaforchina.cn.", "visitors.live.", @@ -7745,140 +7387,121 @@ var FakeECSFQDNs = container.NewMapSet( "vitality.io.", "vividseats.com.", "vivoglobal.com.", - "vntsnotificationservice.visa.com.cdn.cloudflare.net.", - "vod.ngb.haplat.net.", - "vod2.ngb.haplat.net.", - "vod3.ngb.haplat.net.", - "vod4.ngb.haplat.net.", - "vod5.ngb.haplat.net.", - "vod6.ngb.haplat.net.", + "vlscppe.microsoft.com.", "voe.sx.", "voice.gcloudcs.com.", "voice.telephony.goog.", - "voxox.com.", "voya.com.", "vpn1.ocso.com.", "vsco.co.", - "vx323.com.", "vzuu.com.", "w.deepl.com.", + "w25.cf.2ksports.com.", "w3.mp.lura.live.", "wahapanih.xyz.", "warsaw.remotepc.com.", + "waterfiltersfactory.com.", "wayfinding-hub-gateway-atl.ultipro.com.", "wayfinding-hub-gateway-plas1.ultipro.com.", - "we.footballbros.io.", + "wbte.drcedirect.com.", "weather-analytics-events.apple.com.", "weather-server-sg.allawnos.com.", "weather-server.allawntech.com.", "weather-widget-events.apple.com.", - "weather.swishapps.ai.", "weathercn.com.", + "web-assets.stylitics.com.cdn.cloudflare.net.", + "web-push-sw.useinsider.com.", "web-static.mindbox.ru.", "web.voice.telephony.goog.", "web1.remotepc.com.", "webapi.teamviewer.com.", "webmd.com.", "websocket.app.pdq.com.", - "webstaurantstore.com.", "wechatos.net.", - "weerrhoop.cc.", - "wegame.com.cn.", "weibocdn.com.", "welcome.ultipro.com.", "wemuslim.com.", - "wesingapp.com.", "westcentralus.api.cognitive.microsoft.com.", "westeurope.api.cognitive.microsoft.com.", "westpalm.remotepc.com.", + "westrockco.sharepoint.com.", "westus.api.cognitive.microsoft.com.", "westus2.api.cognitive.microsoft.com.", "westus3.api.cognitive.microsoft.com.", - "whatismyipaddress.com.", "whirlpool.com.", - "whitefoxboutique.com.", - "whitingturner-my.sharepoint.com.", - "whitingturner.sharepoint.com.", + "whitehouse.gov.", + "whizzco.com.", + "whop.com.", + "wida-insight.drcedirect.com.", "widget-api.uxfeedback.ru.", + "widgets.risevision.com.", "widgets.sociablekit.com.", - "wifispot.io.", "windows.policies.live.net.", "winscp.net.", + "wireless-social.com.", "wkhpe.com.", + "wl4.loobygameshub.com.", "wmt.dev.", "word-collab.officeapps.live.com.", "wordreference.com.", "workdaycdn.com.cn.", - "worldguessr.com.", "worldnic.com.", "worldtimeserver.com.", "wosign.com.", + "wpp.lol.", "wr.moyoung.com.", "wr.pvp.net.", "write-free.www.deepl.com.", "ws.gleap.io.", - "ws.mybib.com.", "ws.thales.monumetric.com.", "ws.tildacdn.com.", "wsms.haplat.net.", "wsoversea.com.", "wss-web.freshchat.com.", - "wtecdn.net.", - "wtzw.com.", + "wsv3cdn.audioeye.com.", + "wsv3cdn.audioeye.com.cdn.cloudflare.net.", "www.aigconnect.aig.", - "www.att.com.edgekey.net.", + "www.animefox.sbs.", + "www.aniwatch.click.", "www.automizely-analytics.com.", + "www.belkin.com.", "www.breitbart.com.", - "www.cabelas.com.", - "www.chrome.com.", + "www.britannica.com.", "www.claudeusercontent.com.", - "www.cmpassport.com.", - "www.columbia.com.", - "www.crazygames.com.", "www.ctmail.com.", - "www.geappliances.com.", + "www.digitalcardservice.com.", + "www.etoro.com.", "www.geoplugin.net.", "www.google.org.", - "www.handmadewithjoann.com.", - "www.hopkinsmedicine.org.", - "www.hottopic.com.", - "www.hpsmart.com.", "www.in.gov.", - "www.intel.com.", + "www.jhnet.com.", "www.jimmyjohns.com.", - "www.jotform.com.", - "www.justice.gov.", - "www.kqzyfj.com.", - "www.linkedin.com.cdn.cloudflare.net.", "www.maintenanceconnection.com.", - "www.masterclass.com.", - "www.nativecos.com.", + "www.newegg.com.", "www.overleaf.com.", "www.pingler.com.", - "www.printfriendly.com.", - "www.rbcroyalbank.com.", + "www.qchannel01.cn.", + "www.redstream.in.", "www.regions.com.", - "www.rentcafe.com.", - "www.riotgames.com.", + "www.routeone.net.", "www.sevenrooms.com.", - "www.snokido.com.", - "www.startssl.com.", - "www.teacherspayteachers.com.", + "www.sobot.com.", + "www.syndetics.com.", "www.terabox.com.", "www.users.storage.live.com.", - "www.vipads.live.", + "www.ute-tech.com.cn.", + "www.viously.com.", "www.visaforchina.cn.", - "www.vividseats.com.", + "www.waterfiltersfactory.com.", "www.whitehouse.gov.", - "www.wifispot.io.", "www.wix.com.", "www.worldtimeserver.com.", "www.zoom.com.", "www.zoom.us.", - "www.zoominfo.com.", - "www.zscaler.com.", "www1.remotepc.com.", + "www19.pointclickcare.com.", "www2.deepl.com.", + "www27.pointclickcare.com.", "www28.pointclickcare.com.", "www3.zoom.us.", "www30.pointclickcare.com.", @@ -7887,25 +7510,25 @@ var FakeECSFQDNs = container.NewMapSet( "wxqcloud.qq.com.cn.", "wyze.com.", "x-flow.app.", - "xbox-guide-public.rec.mp.microsoft.com.", - "xbox.ipv6.microsoft.com.", + "x1.xplorebrav.space.", + "x20na.appdump.nie.easebar.com.", "xhpingcdn.com.", - "xhwear.life.", + "xhprograms.site.", "xiaoyi.com.", "xinqiucc.com.", "xintaicz.cn.", "xmcsrv.net.", - "xml-eu-v4.ezmob.com.", - "xml-v4.ezmob.com.", + "xml-v4.clickmi.net.", "xml.acertb.com.", - "xml.adservtday.com.", + "xml.adokutcontextual.com.", + "xml.ezmob.com.", + "xml.junplatdirect.com.", "xml.kunvertads.com.", - "xml.responseservez.com.", + "xml.nexrtb.com.", + "xml.optumads.com.", "xml.revrtb.net.", - "xml.servsserverz.com.", + "xml.ripamatic.com.", "xml.xmlking.com.", - "xml.zeusadx.com.", - "xmppapi.zoom.us.", "xmt.paze.com.", "xp001.itsupport247.net.", "xp002.itsupport247.net.", @@ -7924,23 +7547,23 @@ var FakeECSFQDNs = container.NewMapSet( "yalla.live.", "yealink.com.", "ymmobi.com.", - "yomedia.vn.", "yomeno.xyz.", "yosmart.com.", "youlesp.com.", + "yp.cdnstream1.com.", "yunxindns.com.", "yunxinfw.com.", "z.cdn.adpool.bet.", "z.cdn.adtarget.market.", "z.cdn.ftd.agency.", + "z.cdn.trafficbass.com.", "zemanta.com.", - "zepp.com.", - "zero.txryan.com.", "zjcdn.com.yangyi19.com.", "zog.link.", "zoom.us.", "zui.com.", "zuiqiangyingyu.net.", "zurich.remotepc.com.", + "zybooks.com.", "zztfly.com.", ) diff --git a/internal/ecscache/ecscache.go b/internal/ecscache/ecscache.go index d78f2fc..a706f1b 100644 --- a/internal/ecscache/ecscache.go +++ b/internal/ecscache/ecscache.go @@ -13,7 +13,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/optslog" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/netutil" @@ -21,42 +20,12 @@ import ( "github.com/miekg/dns" ) -// Constants that define cache identifiers for the cache manager. -const ( - cachePrefix = "dns/" - cacheIDWithECS = cachePrefix + "ecscache_with_ecs" - cacheIDNoECS = cachePrefix + "ecscache_no_ecs" -) - -// Middleware is a dnsserver.Middleware with ECS-aware caching. -type Middleware struct { - // cloner is the memory-efficient cloner of DNS messages. - cloner *dnsmsg.Cloner - - // cacheReqPool is a pool of cache requests. - cacheReqPool *syncutil.Pool[cacheRequest] - - // logger is used to log the operation of the middleware. - logger *slog.Logger - - // cache is the LRU cache for results indicating no support for ECS. - cache agdcache.Interface[uint64, *cacheItem] - - // ecsCache is the LRU cache for results indicating ECS support. - ecsCache agdcache.Interface[uint64, *cacheItem] - - // geoIP is used to get subnets for countries. - geoIP geoip.Interface - - // cacheMinTTL is the minimum supported TTL for cache items. - 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 { + // Metrics is used for the collection of the ECS cache middleware + // statistics. It must not be nil. + Metrics Metrics + // Cloner is used to clone messages taken from cache. It must not be nil. Cloner *dnsmsg.Cloner @@ -86,6 +55,43 @@ type MiddlewareConfig struct { OverrideTTL bool } +// Middleware is a dnsserver.Middleware with ECS-aware caching. +type Middleware struct { + // metrics is used for the collection of the ECS cache statistics. + metrics Metrics + + // cloner is the memory-efficient cloner of DNS messages. + cloner *dnsmsg.Cloner + + // cacheReqPool is a pool of cache requests. + cacheReqPool *syncutil.Pool[cacheRequest] + + // logger is used to log the operation of the middleware. + logger *slog.Logger + + // cache is the LRU cache for results indicating no support for ECS. + cache agdcache.Interface[uint64, *cacheItem] + + // ecsCache is the LRU cache for results indicating ECS support. + ecsCache agdcache.Interface[uint64, *cacheItem] + + // geoIP is used to get subnets for countries. + geoIP geoip.Interface + + // cacheMinTTL is the minimum supported TTL for cache items. + cacheMinTTL time.Duration + + // overrideTTL shows if the TTL overrides logic should be used. + overrideTTL bool +} + +// Constants that define cache identifiers for the cache manager. +const ( + cachePrefix = "dns/" + cacheIDWithECS = cachePrefix + "ecscache_with_ecs" + cacheIDNoECS = cachePrefix + "ecscache_no_ecs" +) + // NewMiddleware initializes a new ECS-aware LRU caching middleware. It also // adds the caches with IDs [CacheIDNoECS] and [CacheIDWithECS] to the cache // manager. c must not be nil. @@ -101,8 +107,9 @@ func NewMiddleware(c *MiddlewareConfig) (m *Middleware) { c.CacheManager.Add(cacheIDWithECS, ecsCache) return &Middleware{ - cloner: c.Cloner, - logger: c.Logger, + metrics: c.Metrics, + cloner: c.Cloner, + logger: c.Logger, cacheReqPool: syncutil.NewPool(func() (req *cacheRequest) { return &cacheRequest{} }), @@ -134,19 +141,7 @@ func writeCachedResponse( resp *dns.Msg, ecs *dnsmsg.ECS, ecsFam netutil.AddrFamily, - respIsECSDependent bool, ) (err error) { - // Increment the hits metrics here, since we already know if the domain name - // supports ECS or not from the cache data. Increment the misses metrics in - // writeResponse, where this information is retrieved from the upstream - metrics.ECSCacheLookupTotalHits.Inc() - - metrics.IncrementCond( - respIsECSDependent, - metrics.ECSCacheLookupHasSupportHits, - metrics.ECSCacheLookupNoSupportHits, - ) - // If the client query did include the ECS option, the server MUST include // one in its response. // @@ -237,19 +232,19 @@ func (mw *Middleware) writeUpstreamResponse( reqDO := cr.reqDO rmHopToHopData(resp, ri.QType, reqDO) - metrics.ECSCacheLookupTotalMisses.Inc() - respIsECS := respIsECSDependent(scope, req.Question[0].Name) - if respIsECS { - metrics.ECSCacheLookupHasSupportMisses.Inc() - metrics.ECSHasSupportCacheSize.Set(float64(mw.ecsCache.Len())) - } else { - metrics.ECSCacheLookupNoSupportMisses.Inc() - metrics.ECSNoSupportCacheSize.Set(float64(mw.cache.Len())) + var cache agdcache.Interface[uint64, *cacheItem] + if respIsECS { + cache = mw.ecsCache + } else { + cache = mw.cache cr.subnet = netutil.ZeroPrefix(ecsFam) } + mw.metrics.SetElementsCount(ctx, respIsECS, cache.Len()) + mw.metrics.IncrementLookups(ctx, respIsECS, false) + mw.set(resp, cr, respIsECS) // Set the AD bit and ECS information here, where it is safe to do so, since @@ -343,8 +338,14 @@ func (mh *mwHandler) ServeDNS( if resp != nil { optslog.Debug1(ctx, mw.logger, "using cached response", "ecs_aware", respIsECS) + // Increment the hits metrics here, since we already know if the domain + // name supports ECS or not from the cache data. Increment the misses + // metrics in writeUpstreamResponse, where this information is retrieved + // from the upstream. + mw.metrics.IncrementLookups(ctx, respIsECS, true) + // Don't wrap the error, because it's informative enough as is. - return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, respIsECS) + return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam) } mw.logger.DebugContext(ctx, "no cached response") diff --git a/internal/ecscache/ecscache_test.go b/internal/ecscache/ecscache_test.go index ef1e221..154bc34 100644 --- a/internal/ecscache/ecscache_test.go +++ b/internal/ecscache/ecscache_test.go @@ -680,6 +680,7 @@ func newWithCache( return dnsserver.WithMiddlewares( h, ecscache.NewMiddleware(&ecscache.MiddlewareConfig{ + Metrics: ecscache.EmptyMetrics{}, Cloner: agdtest.NewCloner(), Logger: slogutil.NewDiscardLogger(), CacheManager: agdcache.EmptyManager{}, diff --git a/internal/ecscache/metrics.go b/internal/ecscache/metrics.go new file mode 100644 index 0000000..b8ec588 --- /dev/null +++ b/internal/ecscache/metrics.go @@ -0,0 +1,28 @@ +package ecscache + +import "context" + +// Metrics is an interface that is used for the collection of the ECS cache +// statistics. +type Metrics interface { + // SetElementsCount sets the total number of items in the cache for domain + // names that support or do not support ECS. + SetElementsCount(ctx context.Context, supportsECS bool, count int) + + // IncrementLookups increments the number of ECS cache lookups for hosts + // that does or doesn't support ECS. + IncrementLookups(ctx context.Context, supportsECS, hit bool) +} + +// EmptyMetrics is the implementation of the [Metrics] interface that does +// nothing. +type EmptyMetrics struct{} + +// type check +var _ Metrics = EmptyMetrics{} + +// SetElementsCount implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) SetElementsCount(_ context.Context, _ bool, _ int) {} + +// IncrementLookups implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) IncrementLookups(_ context.Context, _, _ bool) {} diff --git a/internal/errcoll/errcoll.go b/internal/errcoll/errcoll.go index 0644f39..2284324 100644 --- a/internal/errcoll/errcoll.go +++ b/internal/errcoll/errcoll.go @@ -7,8 +7,8 @@ import ( "fmt" "log/slog" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/service" ) // Interface is the interface for error collectors that process information @@ -17,14 +17,6 @@ type Interface interface { Collect(ctx context.Context, err error) } -// Collectf is a helper method for reporting non-critical errors. It writes the -// resulting error into the log and also into errColl. -func Collectf(ctx context.Context, errColl Interface, format string, args ...any) { - err := fmt.Errorf(format, args...) - log.Error("%s", err) - errColl.Collect(ctx, err) -} - // Collect is a helper method for reporting non-critical errors. It writes the // resulting error into the log and also into errColl. // @@ -33,3 +25,28 @@ func Collect(ctx context.Context, errColl Interface, l *slog.Logger, msg string, l.ErrorContext(ctx, msg, slogutil.KeyError, err) errColl.Collect(ctx, fmt.Errorf("%s: %w", msg, err)) } + +// RefreshErrorHandler is a [service.ErrorHandler] that can be used whenever a +// [service.Refresher] cannot report its own errors for some reason. +type RefreshErrorHandler struct { + logger *slog.Logger + errColl Interface +} + +// NewRefreshErrorHandler returns a properly initialized *RefreshErrorHandler. +// All arguments must not be nil. +func NewRefreshErrorHandler(logger *slog.Logger, errColl Interface) (h *RefreshErrorHandler) { + return &RefreshErrorHandler{ + logger: logger, + errColl: errColl, + } +} + +// type check +var _ service.ErrorHandler = (*RefreshErrorHandler)(nil) + +// Handle implements the [service.ErrorHandler] interface for +// *RefreshErrorHandler. +func (h *RefreshErrorHandler) Handle(ctx context.Context, err error) { + Collect(ctx, h.errColl, h.logger, "refreshing", err) +} diff --git a/internal/errcoll/sentry.go b/internal/errcoll/sentry.go index ae75bc8..4210bca 100644 --- a/internal/errcoll/sentry.go +++ b/internal/errcoll/sentry.go @@ -4,6 +4,7 @@ import ( "cmp" "context" "io" + "log/slog" "net" "os" "strconv" @@ -15,7 +16,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/version" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/getsentry/sentry-go" "golang.org/x/sys/unix" ) @@ -23,13 +24,15 @@ import ( // SentryErrorCollector is an [Interface] implementation that sends errors to a // Sentry-like HTTP API. type SentryErrorCollector struct { + logger *slog.Logger sentry *sentry.Client } -// NewSentryErrorCollector returns a new SentryErrorCollector. cli must be -// non-nil. -func NewSentryErrorCollector(cli *sentry.Client) (c *SentryErrorCollector) { +// NewSentryErrorCollector returns a new SentryErrorCollector. All arguments +// must not be nil. +func NewSentryErrorCollector(cli *sentry.Client, l *slog.Logger) (c *SentryErrorCollector) { return &SentryErrorCollector{ + logger: l, sentry: cli, } } @@ -40,7 +43,7 @@ var _ Interface = (*SentryErrorCollector)(nil) // Collect implements the [Interface] interface for *SentryErrorCollector. func (c *SentryErrorCollector) Collect(ctx context.Context, err error) { if !isReportable(err) { - log.Debug("errcoll: sentry: non-reportable error: %s", err) + c.logger.DebugContext(ctx, "non-reportable error", slogutil.KeyError, err) return } diff --git a/internal/errcoll/sentry_test.go b/internal/errcoll/sentry_test.go index 8f95b77..02d8995 100644 --- a/internal/errcoll/sentry_test.go +++ b/internal/errcoll/sentry_test.go @@ -12,46 +12,26 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/version" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/testutil/sentrytest" "github.com/getsentry/sentry-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// testSentryTransport is a sentry.Transport for tests. -type testSentryTransport struct { - onConfigure func(opts sentry.ClientOptions) - onFlush func(timeout time.Duration) (ok bool) - onSend func(e *sentry.Event) -} - -// type check -var _ sentry.Transport = (*testSentryTransport)(nil) - -// Configure implements the sentry.Transport interface for *testSentryTransport. -func (t *testSentryTransport) Configure(ops sentry.ClientOptions) { - t.onConfigure(ops) -} - -// Flush implements the sentry.Transport interface for *testSentryTransport. -func (t *testSentryTransport) Flush(timeout time.Duration) (ok bool) { - return t.onFlush(timeout) -} - -// Send implements the sentry.Transport interface for *testSentryTransport. -func (t *testSentryTransport) SendEvent(e *sentry.Event) { - t.onSend(e) -} - func TestSentryErrorCollector(t *testing.T) { gotEventCh := make(chan *sentry.Event, 1) - tr := &testSentryTransport{ - onConfigure: func(_ sentry.ClientOptions) { + tr := &sentrytest.Transport{ + OnClose: func() { // Do nothing. }, - onFlush: func(_ time.Duration) (ok bool) { + OnConfigure: func(_ sentry.ClientOptions) { + // Do nothing. + }, + OnFlush: func(_ time.Duration) (ok bool) { return true }, - onSend: func(e *sentry.Event) { + OnSendEvent: func(e *sentry.Event) { gotEventCh <- e }, } @@ -63,7 +43,7 @@ func TestSentryErrorCollector(t *testing.T) { }) require.NoError(t, err) - c := errcoll.NewSentryErrorCollector(sentryClient) + c := errcoll.NewSentryErrorCollector(sentryClient, slogutil.NewDiscardLogger()) const devID = "dev1234" const fltGrpID = "fg1234" diff --git a/internal/experiment/experiment.go b/internal/experiment/experiment.go index 71b8753..8ab0990 100644 --- a/internal/experiment/experiment.go +++ b/internal/experiment/experiment.go @@ -11,19 +11,18 @@ // - Some errors may be logged or ignored. // - Tests may be lacking. // - The environment may be read here as opposed to package cmd. -// - init() is allowed. package experiment import ( + "log/slog" "os" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/stringutil" "github.com/prometheus/client_golang/prometheus" ) -func init() { +func Init(l *slog.Logger) { expStr := os.Getenv("EXPERIMENTS") if expStr == "" { return @@ -36,7 +35,7 @@ func init() { // case idMyExp: // enableMyExp() default: - log.Error("experiment: no experiment with id %q", id) + l.Error("no such experiment", "id", id) } } diff --git a/internal/filter/config.go b/internal/filter/config.go index b71aad6..f62b2fe 100644 --- a/internal/filter/config.go +++ b/internal/filter/config.go @@ -1,6 +1,12 @@ package filter -import "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" +import ( + "context" + "net/netip" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/urlfilter" +) // Config is the sum type of [Storage.ForConfig] configurations. // @@ -39,7 +45,32 @@ func (*ConfigClient) isConfig() {} // ConfigCustom is the configuration for identification or construction of a // custom filter for a client. -type ConfigCustom = internal.ConfigCustom +type ConfigCustom struct { + // Filter is the custom filter to use for this client, if any. If + // [ConfigCustom.Enabled] is true, Filter must not be nil. + Filter Custom + + // Enabled shows whether the custom filters are applied at all. + Enabled bool +} + +// Custom is a custom filter for a client. +type Custom interface { + // DNSResult returns the result of applying the urlfilter DNS filtering + // engine. If the request is not filtered, DNSResult returns nil. + DNSResult( + ctx context.Context, + clientIP netip.Addr, + clientName string, + host string, + rrType dnsmsg.RRType, + isAns bool, + ) (res *urlfilter.DNSResult) + + // Rules returns the rules used to create the filter. rules must not be + // modified. + Rules() (rules []RuleText) +} // ConfigParental is the configuration for parental-control filtering. type ConfigParental struct { diff --git a/internal/filter/custom/custom.go b/internal/filter/custom/custom.go new file mode 100644 index 0000000..bceaddd --- /dev/null +++ b/internal/filter/custom/custom.go @@ -0,0 +1,91 @@ +// Package custom contains filters made from custom filtering rules of clients. +package custom + +import ( + "context" + "log/slog" + "net/netip" + "strings" + "sync" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" + "github.com/AdguardTeam/golibs/stringutil" + "github.com/AdguardTeam/urlfilter" +) + +// Filter is a custom filter for a client. +type Filter struct { + logger *slog.Logger + initOnce *sync.Once + immutable *rulelist.Immutable + rules []filter.RuleText +} + +// Config is the configuration for a custom filter. +type Config struct { + // Logger is used for logging the compilation of the engine. It must not be + // nil. + Logger *slog.Logger + + // Rules are the rules for this custom filter. They must not be modified + // after calling New. + Rules []filter.RuleText +} + +// New creates a new custom filter. c must not be nil and must be valid. +func New(c *Config) (f *Filter) { + return &Filter{ + logger: c.Logger, + initOnce: &sync.Once{}, + rules: c.Rules, + } +} + +// init initializes f.immutable. +func (f *Filter) init(ctx context.Context) { + // TODO(a.garipov): Consider making a copy of [strings.Join] for + // [filter.RuleText]. + textLen := 0 + for _, r := range f.rules { + textLen += len(r) + len("\n") + } + + b := &strings.Builder{} + b.Grow(textLen) + + for _, r := range f.rules { + stringutil.WriteToBuilder(b, string(r), "\n") + } + + // Don't use cache for users' custom filters, because [rulelist.ResultCache] + // doesn't take $client rules into account. + // + // TODO(a.garipov): Consider adding client names to the result-cache keys. + cache := rulelist.EmptyResultCache{} + + f.immutable = rulelist.NewImmutable(b.String(), filter.IDCustom, "", cache) + + f.logger.DebugContext(ctx, "engine compiled", "num_rules", f.immutable.RulesCount()) +} + +// DNSResult returns the result of applying the custom filter to the query with +// the given parameters. +func (f *Filter) DNSResult( + ctx context.Context, + clientIP netip.Addr, + clientName string, + host string, + rrType dnsmsg.RRType, + isAns bool, +) (r *urlfilter.DNSResult) { + f.initOnce.Do(func() { + f.init(ctx) + }) + + return f.immutable.DNSResult(clientIP, clientName, host, rrType, isAns) +} + +// Rules implements the [filter.Custom] interface for *Filter. +func (f *Filter) Rules() (rules []filter.RuleText) { return f.rules } diff --git a/internal/filter/custom/custom_test.go b/internal/filter/custom/custom_test.go new file mode 100644 index 0000000..415e33c --- /dev/null +++ b/internal/filter/custom/custom_test.go @@ -0,0 +1,68 @@ +package custom_test + +import ( + "context" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/custom" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + t.Parallel() + + rules := []filter.RuleText{ + filtertest.RuleBlock, + filtertest.RuleBlockForClientIP, + filtertest.RuleBlockForClientName, + } + + f := custom.New(&custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: rules, + }) + require.NotNil(t, f) + require.Equal(t, rules, f.Rules()) + + ip := filtertest.IPv4Client + + testCases := []struct { + name string + cliName string + host string + wantRuleStr string + }{{ + name: "simple", + cliName: "", + host: filtertest.HostBlocked, + wantRuleStr: filtertest.RuleBlockStr, + }, { + name: "client_ip", + cliName: "", + host: filtertest.HostBlockedForClientIP, + wantRuleStr: filtertest.RuleBlockForClientIPStr, + }, { + name: "client_name", + cliName: filtertest.ClientName, + host: filtertest.HostBlockedForClientName, + wantRuleStr: filtertest.RuleBlockForClientNameStr, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + dr := f.DNSResult(context.Background(), ip, tc.cliName, tc.host, dns.TypeA, false) + + require.NotNil(t, dr) + require.NotNil(t, dr.NetworkRule) + + assert.Equal(t, tc.wantRuleStr, dr.NetworkRule.RuleText) + }) + } +} diff --git a/internal/filter/filter.go b/internal/filter/filter.go index 1e1848d..dc384ad 100644 --- a/internal/filter/filter.go +++ b/internal/filter/filter.go @@ -5,93 +5,77 @@ package filter import ( "context" + "net/netip" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/miekg/dns" ) // Interface is the DNS request and response filter interface. -type Interface = internal.Interface +type Interface interface { + // FilterRequest filters a DNS request based on the information provided + // about the request. req must be valid. + FilterRequest(ctx context.Context, req *Request) (r Result, err error) -// Empty is an [Interface] implementation that always returns nil. -type Empty = internal.Empty + // FilterResponse filters a DNS response based on the information provided + // about the response. resp must be valid. + FilterResponse(ctx context.Context, resp *Response) (r Result, err error) +} // Request contains information about a request being filtered. -type Request = internal.Request +type Request struct { + // DNS is the original DNS request used to create filtered responses. It + // must not be nil and must have exactly one question. + DNS *dns.Msg + + // Messages is used to create filtered responses for this request. It must + // not be nil. + Messages *dnsmsg.Constructor + + // RemoteIP is the remote IP address of the client. + RemoteIP netip.Addr + + // ClientName is the client name for rule-list filtering. + ClientName string + + // Host is the lowercased, non-FQDN version of the hostname from the + // question of the request. + Host string + + // QType is the type of question for this request. + QType dnsmsg.RRType + + // QClass is the class of question for this request. + QClass dnsmsg.Class +} // Response contains information about a response being filtered. -type Response = internal.Response +type Response struct { + // DNS is the original DNS response used to create filtered responses. It + // must not be nil and must have exactly one question. + DNS *dns.Msg -// Result is a sum type of all possible filtering actions. See the following -// types as implementations: -// -// - [*ResultAllowed] -// - [*ResultBlocked] -// - [*ResultModifiedResponse] -// - [*ResultModifiedRequest] -type Result = internal.Result + // RemoteIP is the remote IP address of the client. + RemoteIP netip.Addr -// ResultAllowed means that this request or response was allowed by an allowlist -// rule within the given filter list. -type ResultAllowed = internal.ResultAllowed + // ClientName is the client name for rule-list filtering. + ClientName string +} -// ResultBlocked means that this request or response was blocked by a blocklist -// rule within the given filter list. -type ResultBlocked = internal.ResultBlocked +// Empty is an [Interface] implementation that always returns nil. +type Empty struct{} -// ResultModifiedResponse means that this response was rewritten or modified by -// a rewrite rule within the given filter list. -type ResultModifiedResponse = internal.ResultModifiedResponse +// type check +var _ Interface = Empty{} -// ResultModifiedRequest means that this request was modified by a rewrite rule -// within the given filter list. -type ResultModifiedRequest = internal.ResultModifiedRequest +// FilterRequest implements the [Interface] interface for Empty. +func (Empty) FilterRequest(_ context.Context, _ *Request) (r Result, err error) { + return nil, nil +} -// ID is the ID of a filter list. It is an opaque string. -type ID = internal.ID - -// Special ID values shared across the AdGuard DNS system. -// -// NOTE: DO NOT change these as other parts of the system depend on these -// values. -// -// TODO(a.garipov): Consider removing those that aren't used outside of the -// filter subpackages. -const ( - IDNone = internal.IDNone - - IDAdGuardDNS = internal.IDAdGuardDNS - IDAdultBlocking = internal.IDAdultBlocking - IDBlockedService = internal.IDBlockedService - IDCustom = internal.IDCustom - IDGeneralSafeSearch = internal.IDGeneralSafeSearch - IDNewRegDomains = internal.IDNewRegDomains - IDSafeBrowsing = internal.IDSafeBrowsing - IDYoutubeSafeSearch = internal.IDYoutubeSafeSearch -) - -// NewID converts a simple string into an ID and makes sure that it's valid. -// This should be preferred to a simple type conversion. -func NewID(s string) (id ID, err error) { return internal.NewID(s) } - -// RuleText is the text of a single rule within a rule-list filter. -type RuleText = internal.RuleText - -// NewRuleText converts a simple string into an RuleText and makes sure that -// it's valid. This should be preferred to a simple type conversion. -func NewRuleText(s string) (id RuleText, err error) { return internal.NewRuleText(s) } - -// BlockedServiceID is the ID of a blocked service. While these are usually -// human-readable, clients should treat them as opaque strings. -// -// When a request is blocked by the service blocker, this ID is used as the -// text of the blocking rule. -type BlockedServiceID = internal.BlockedServiceID - -// NewBlockedServiceID converts a simple string into a BlockedServiceID and -// makes sure that it's valid. This should be preferred to a simple type -// conversion. -func NewBlockedServiceID(s string) (id BlockedServiceID, err error) { - return internal.NewBlockedServiceID(s) +// FilterResponse implements the [Interface] interface for Empty. +func (Empty) FilterResponse(_ context.Context, _ *Response) (r Result, err error) { + return nil, nil } // HashMatcher is the interface for a safe-browsing and adult-blocking hash @@ -105,10 +89,3 @@ const ( GeneralTXTSuffix = ".sb.dns.adguard.com" AdultBlockingTXTSuffix = ".pc.dns.adguard.com" ) - -// Metrics is the interface for metrics of filters. -type Metrics = internal.Metrics - -// EmptyMetrics is the implementation of the [Metrics] interface that does -// nothing. -type EmptyMetrics = internal.EmptyMetrics diff --git a/internal/filter/internal/internal_test.go b/internal/filter/filter_test.go similarity index 55% rename from internal/filter/internal/internal_test.go rename to internal/filter/filter_test.go index a79895c..a6963b5 100644 --- a/internal/filter/internal/internal_test.go +++ b/internal/filter/filter_test.go @@ -1,10 +1,8 @@ -package internal_test +package filter_test import "strings" // Common long strings for tests. -// -// TODO(a.garipov): Move to a new validation package. var ( testLongStr = strings.Repeat("a", 200) ) diff --git a/internal/filter/filterstorage/config.go b/internal/filter/filterstorage/config.go index 72173ba..cff4bc1 100644 --- a/internal/filter/filterstorage/config.go +++ b/internal/filter/filterstorage/config.go @@ -6,10 +6,10 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" + "github.com/AdguardTeam/golibs/timeutil" "github.com/c2h5oh/datasize" ) @@ -52,7 +52,7 @@ type Config struct { // Clock is used for time-related operations, such as schedule checking. // It must not be nil. - Clock agdtime.Clock + Clock timeutil.Clock // ErrColl is used to collect non-critical and rare errors as well as // refresh errors. It must not be nil. diff --git a/internal/filter/filterstorage/default.go b/internal/filter/filterstorage/default.go index e7a5acc..30b4cc8 100644 --- a/internal/filter/filterstorage/default.go +++ b/internal/filter/filterstorage/default.go @@ -10,32 +10,29 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/custom" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/serviceblock" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/timeutil" "github.com/c2h5oh/datasize" ) // Default is the default filter storage that assembles filters based on rule -// lists, custom filters of profiles, safe browsing, and safe search ones. It -// should be initially refreshed with [Default.RefreshInitial]. +// lists, safe browsing, and safe search ones. It should be initially refreshed +// with [Default.RefreshInitial]. type Default struct { baseLogger *slog.Logger logger *slog.Logger services *serviceblock.Filter - custom *custom.Filters - adult *hashprefix.Filter dangerous *hashprefix.Filter newlyRegistered *hashprefix.Filter @@ -50,7 +47,7 @@ type Default struct { ruleListIdxRefr *refreshable.Refreshable cacheManager agdcache.Manager - clock agdtime.Clock + clock timeutil.Clock errColl errcoll.Interface metrics filter.Metrics @@ -83,9 +80,6 @@ func New(c *Config) (s *Default, err error) { // Initialized in [Default.initBlockedServices]. services: nil, - // Initialized in [Default.initCustom]. - custom: nil, - adult: c.HashPrefix.Adult, dangerous: c.HashPrefix.Dangerous, newlyRegistered: c.HashPrefix.NewlyRegistered, @@ -132,8 +126,6 @@ func New(c *Config) (s *Default, err error) { // init finishes the initialization of a storage. c must not be nil. func (s *Default) init(c *Config) (err error) { - s.initCustom(c.Custom) - var errs []error err = s.initBlockedServices(c.BlockedServices) if err != nil { @@ -156,21 +148,6 @@ func (s *Default) init(c *Config) (err error) { return errors.Join(errs...) } -// initCustom initializes the custom-filter storage in s. c must not be nil. -func (s *Default) initCustom(c *ConfigCustom) { - s.custom = custom.New(&custom.Config{ - Logger: s.baseLogger.With( - slogutil.KeyPrefix, - path.Join("filters", string(filter.IDCustom)), - ), - ErrColl: s.errColl, - CacheConf: &agdcache.LRUConfig{ - Count: c.CacheCount, - }, - CacheManager: s.cacheManager, - }) -} - // initBlockedServices initializes the blocked-service filter in s. c must not // be nil. func (s *Default) initBlockedServices(c *ConfigBlockedServices) (err error) { @@ -298,13 +275,15 @@ func (s *Default) forClient(ctx context.Context, c *filter.ConfigClient) (f filt s.setRuleLists(compConf, c.RuleList) s.setSafeBrowsing(compConf, c.SafeBrowsing) - compConf.Custom = s.custom.Get(ctx, c.Custom) + if c.Custom.Enabled { + compConf.Custom = c.Custom.Filter + } return composite.New(compConf) } -// setParental sets the parental-control filters in compConf from c. c must not -// be nil. +// setParental checks if the parental-control filters are enabled and, if they +// are, sets them in compConf from c. c must not be nil. func (s *Default) setParental( ctx context.Context, compConf *composite.Config, @@ -319,15 +298,27 @@ func (s *Default) setParental( return } - if c.AdultBlockingEnabled { + s.setEnabledParental(ctx, compConf, c) +} + +// setEnabledParental sets the parental-control filters in compConf from c. c +// must not be nil. +func (s *Default) setEnabledParental( + ctx context.Context, + compConf *composite.Config, + c *filter.ConfigParental, +) { + // NOTE: Here and below always check the pointer for nil to avoid non-nil + // interface values containing nil pointers. + if c.AdultBlockingEnabled && s.adult != nil { compConf.AdultBlocking = s.adult } - if c.SafeSearchGeneralEnabled { + if c.SafeSearchGeneralEnabled && s.safeSearchGeneral != nil { compConf.GeneralSafeSearch = s.safeSearchGeneral } - if c.SafeSearchYouTubeEnabled { + if c.SafeSearchYouTubeEnabled && s.safeSearchYouTube != nil { compConf.YouTubeSafeSearch = s.safeSearchYouTube } @@ -361,11 +352,11 @@ func (s *Default) setSafeBrowsing(compConf *composite.Config, c *filter.ConfigSa return } - if c.DangerousDomainsEnabled { + if c.DangerousDomainsEnabled && s.dangerous != nil { compConf.SafeBrowsing = s.dangerous } - if c.NewlyRegisteredDomainsEnabled { + if c.NewlyRegisteredDomainsEnabled && s.newlyRegistered != nil { compConf.NewRegisteredDomains = s.newlyRegistered } } diff --git a/internal/filter/filterstorage/default_test.go b/internal/filter/filterstorage/default_test.go index 384e06b..3cb5216 100644 --- a/internal/filter/filterstorage/default_test.go +++ b/internal/filter/filterstorage/default_test.go @@ -7,9 +7,11 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/custom" "github.com/AdguardTeam/AdGuardDNS/internal/filter/filterstorage" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" @@ -105,12 +107,11 @@ func TestDefault_ForConfig_client(t *testing.T) { newFltConfigSafeBrowsing(false, false), ) - conf.Custom.ID = "1234" - conf.Custom.UpdateTime = time.Now() - conf.Custom.Rules = []filter.RuleText{ - filtertest.RuleBlock, - } conf.Custom.Enabled = true + conf.Custom.Filter = custom.New(&custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: []filter.RuleText{filtertest.RuleBlock}, + }) ctx := testutil.ContextWithTimeout(t, filtertest.Timeout) f := s.ForConfig(ctx, conf) diff --git a/internal/filter/filterstorage/filterstorage_test.go b/internal/filter/filterstorage/filterstorage_test.go index b204ded..1632aa5 100644 --- a/internal/filter/filterstorage/filterstorage_test.go +++ b/internal/filter/filterstorage/filterstorage_test.go @@ -7,13 +7,13 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/filterstorage" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" + "github.com/AdguardTeam/golibs/timeutil" "github.com/miekg/dns" "github.com/stretchr/testify/require" ) @@ -162,7 +162,7 @@ func newDisabledConfig( Enabled: false, }, CacheManager: agdcache.EmptyManager{}, - Clock: agdtime.SystemClock{}, + Clock: timeutil.SystemClock{}, ErrColl: agdtest.NewErrorCollector(), Metrics: filter.EmptyMetrics{}, CacheDir: tb.TempDir(), diff --git a/internal/filter/filterstorage/refresh.go b/internal/filter/filterstorage/refresh.go index 322dbd9..e0ec7fe 100644 --- a/internal/filter/filterstorage/refresh.go +++ b/internal/filter/filterstorage/refresh.go @@ -9,19 +9,19 @@ import ( "slices" "strings" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/service" ) // type check -var _ agdservice.Refresher = (*Default)(nil) +var _ service.Refresher = (*Default)(nil) -// Refresh implements the [agdservice.Refresher] interface for *Default. +// Refresh implements the [service.Refresher] interface for *Default. func (s *Default) Refresh(ctx context.Context) (err error) { s.logger.InfoContext(ctx, "refresh started") defer s.logger.InfoContext(ctx, "refresh finished") diff --git a/internal/filter/hashprefix/filter.go b/internal/filter/hashprefix/filter.go index dfc28b4..0d41696 100644 --- a/internal/filter/hashprefix/filter.go +++ b/internal/filter/hashprefix/filter.go @@ -11,16 +11,15 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/service" "github.com/c2h5oh/datasize" "github.com/miekg/dns" - "github.com/prometheus/client_golang/prometheus" "golang.org/x/net/publicsuffix" ) @@ -44,14 +43,14 @@ type FilterConfig struct { // ErrColl is used to collect non-critical and rare errors. ErrColl errcoll.Interface + // HashPrefixMtcs are the specific metrics for the hashprefix filter. + HashPrefixMtcs Metrics + // Metrics are the metrics for the hashprefix filter. - // - // TODO(a.garipov): Create a separate interface to also handle the - // hashprefix-specific metrics. - Metrics internal.Metrics + Metrics filter.Metrics // ID is the ID of this hash storage for logging and error reporting. - ID internal.ID + ID filter.ID // CachePath is the path to the file containing the cached filtered // hostnames, one per line. @@ -85,7 +84,7 @@ type FilterConfig struct { // cacheItem represents an item that we will store in the cache. type cacheItem struct { // res is the filtering result. - res internal.Result + res filter.Result // host is the cached normalized hostname for later cache key collision // checks. @@ -95,16 +94,17 @@ type cacheItem struct { // Filter is a filter that matches hosts by their hashes based on a hash-prefix // table. It should be initially refreshed with [Filter.RefreshInitial]. type Filter struct { - logger *slog.Logger - cloner *dnsmsg.Cloner - hashes *Storage - refr *refreshable.Refreshable - errColl errcoll.Interface - metrics internal.Metrics - resCache agdcache.Interface[internal.CacheKey, *cacheItem] - id internal.ID - repIP netip.Addr - repFQDN string + logger *slog.Logger + cloner *dnsmsg.Cloner + hashes *Storage + refr *refreshable.Refreshable + errColl errcoll.Interface + hashprefixMtcs Metrics + metrics filter.Metrics + resCache agdcache.Interface[rulelist.CacheKey, *cacheItem] + id filter.ID + repIP netip.Addr + repFQDN string } // IDPrefix is a common prefix for cache IDs, logging, and refreshes of @@ -119,20 +119,21 @@ const IDPrefix = "filters/hashprefix" func NewFilter(c *FilterConfig) (f *Filter, err error) { id := c.ID - resCache := agdcache.NewLRU[internal.CacheKey, *cacheItem](&agdcache.LRUConfig{ + resCache := agdcache.NewLRU[rulelist.CacheKey, *cacheItem](&agdcache.LRUConfig{ Count: c.CacheCount, }) c.CacheManager.Add(path.Join(IDPrefix, string(id)), resCache) f = &Filter{ - logger: c.Logger, - cloner: c.Cloner, - hashes: c.Hashes, - errColl: c.ErrColl, - metrics: c.Metrics, - resCache: resCache, - id: id, + logger: c.Logger, + cloner: c.Cloner, + hashes: c.Hashes, + errColl: c.ErrColl, + hashprefixMtcs: c.HashPrefixMtcs, + metrics: c.Metrics, + resCache: resCache, + id: id, } repHost := c.ReplacementHost @@ -164,20 +165,17 @@ func NewFilter(c *FilterConfig) (f *Filter, err error) { return f, nil } -// type check -var _ internal.RequestFilter = (*Filter)(nil) - -// FilterRequest implements the [internal.RequestFilter] interface for *Filter. +// FilterRequest implements the [composite.RequestFilter] interface for *Filter. // It modifies the request or response if host matches f. func (f *Filter) FilterRequest( ctx context.Context, - req *internal.Request, -) (r internal.Result, err error) { + req *filter.Request, +) (r filter.Result, err error) { host, qt, cl := req.Host, req.QType, req.QClass - cacheKey := internal.NewCacheKey(host, qt, cl, false) + cacheKey := rulelist.NewCacheKey(host, qt, cl, false) item, ok := f.itemFromCache(ctx, cacheKey, host) - f.updateCacheLookupsMetrics(ok) + f.hashprefixMtcs.IncrementLookups(ctx, ok) if ok { return f.clonedResult(req.DNS, item.res), nil } @@ -214,7 +212,7 @@ func (f *Filter) FilterRequest( f.setInCache(cacheKey, r, host) - f.updateCacheSizeMetrics(f.resCache.Len()) + f.hashprefixMtcs.UpdateCacheSize(ctx, f.resCache.Len()) return r, nil } @@ -224,7 +222,7 @@ func (f *Filter) FilterRequest( // false. func (f *Filter) itemFromCache( ctx context.Context, - key internal.CacheKey, + key rulelist.CacheKey, host string, ) (item *cacheItem, ok bool) { item, ok = f.resCache.Get(key) @@ -241,11 +239,6 @@ func (f *Filter) itemFromCache( return item, true } -// ID implements the [internal.RequestFilter] interface for *Filter. -func (f *Filter) ID() (id internal.ID) { - return f.id -} - // isFilterable returns true if the question type is filterable. If the type is // filterable with a blocked page, fam is the address family for the IP // addresses of the blocked page; otherwise fam is [netutil.AddrFamilyNone]. @@ -260,14 +253,14 @@ func isFilterable(qt dnsmsg.RRType) (fam netutil.AddrFamily, ok bool) { } // clonedResult returns a clone of the result based on its type. r must be nil, -// [*internal.ResultModifiedRequest], or [*internal.ResultModifiedResponse]. -func (f *Filter) clonedResult(req *dns.Msg, r internal.Result) (clone internal.Result) { +// [*filter.ResultModifiedRequest], or [*filter.ResultModifiedResponse]. +func (f *Filter) clonedResult(req *dns.Msg, r filter.Result) (clone filter.Result) { switch r := r.(type) { case nil: return nil - case *internal.ResultModifiedRequest: + case *filter.ResultModifiedRequest: return r.Clone(f.cloner) - case *internal.ResultModifiedResponse: + case *filter.ResultModifiedResponse: return r.CloneForReq(f.cloner, req) default: panic(fmt.Errorf("hashprefix: unexpected type for result: %T(%[1]v)", r)) @@ -276,19 +269,19 @@ func (f *Filter) clonedResult(req *dns.Msg, r internal.Result) (clone internal.R // filteredResult returns a filtered request or response. func (f *Filter) filteredResult( - req *internal.Request, + req *filter.Request, matched string, fam netutil.AddrFamily, -) (r internal.Result, err error) { +) (r filter.Result, err error) { if f.repFQDN != "" { // Assume that the repFQDN is a valid domain name then. modReq := f.cloner.Clone(req.DNS) modReq.Question[0].Name = dns.Fqdn(f.repFQDN) - return &internal.ResultModifiedRequest{ + return &filter.ResultModifiedRequest{ Msg: modReq, List: f.id, - Rule: internal.RuleText(matched), + Rule: filter.RuleText(matched), }, nil } @@ -297,17 +290,17 @@ func (f *Filter) filteredResult( return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err) } - return &internal.ResultModifiedResponse{ + return &filter.ResultModifiedResponse{ Msg: resp, List: f.id, - Rule: internal.RuleText(matched), + Rule: filter.RuleText(matched), }, nil } // respForFamily returns a filtered response in accordance with the protocol // family and question type. func (f *Filter) respForFamily( - req *internal.Request, + req *filter.Request, fam netutil.AddrFamily, ) (resp *dns.Msg, err error) { if fam == netutil.AddrFamilyNone { @@ -338,18 +331,18 @@ func (f *Filter) respForFamily( // setInCache sets r in cache. It clones the result to make sure that // modifications to the result message down the pipeline don't interfere with -// the cached value. r must be either [*internal.ResultModifiedRequest] or -// [*internal.ResultModifiedResponse]. +// the cached value. r must be either [*filter.ResultModifiedRequest] or +// [*filter.ResultModifiedResponse]. // // See AGDNS-359. -func (f *Filter) setInCache(k internal.CacheKey, r internal.Result, host string) { +func (f *Filter) setInCache(k rulelist.CacheKey, r filter.Result, host string) { switch r := r.(type) { - case *internal.ResultModifiedRequest: + case *filter.ResultModifiedRequest: f.resCache.Set(k, &cacheItem{ res: r.Clone(f.cloner), host: host, }) - case *internal.ResultModifiedResponse: + case *filter.ResultModifiedResponse: f.resCache.Set(k, &cacheItem{ res: r.Clone(f.cloner), host: host, @@ -359,44 +352,10 @@ func (f *Filter) setInCache(k internal.CacheKey, r internal.Result, host string) } } -// updateCacheSizeMetrics updates cache size metrics. -func (f *Filter) updateCacheSizeMetrics(size int) { - switch id := f.id; id { - case internal.IDSafeBrowsing: - metrics.HashPrefixFilterSafeBrowsingCacheSize.Set(float64(size)) - case internal.IDAdultBlocking: - metrics.HashPrefixFilterAdultBlockingCacheSize.Set(float64(size)) - case internal.IDNewRegDomains: - metrics.HashPrefixFilterNewRegDomainsCacheSize.Set(float64(size)) - default: - panic(fmt.Errorf("unsupported FilterListID %s", id)) - } -} - -// updateCacheLookupsMetrics updates cache lookups metrics. -func (f *Filter) updateCacheLookupsMetrics(hit bool) { - var hitsMetric, missesMetric prometheus.Counter - switch id := f.id; id { - case internal.IDSafeBrowsing: - hitsMetric = metrics.HashPrefixFilterCacheSafeBrowsingHits - missesMetric = metrics.HashPrefixFilterCacheSafeBrowsingMisses - case internal.IDAdultBlocking: - hitsMetric = metrics.HashPrefixFilterCacheAdultBlockingHits - missesMetric = metrics.HashPrefixFilterCacheAdultBlockingMisses - case internal.IDNewRegDomains: - hitsMetric = metrics.HashPrefixFilterCacheNewRegDomainsHits - missesMetric = metrics.HashPrefixFilterCacheNewRegDomainsMisses - default: - panic(fmt.Errorf("unsupported filter list id %s", id)) - } - - metrics.IncrementCond(hit, hitsMetric, missesMetric) -} - // type check -var _ agdservice.Refresher = (*Filter)(nil) +var _ service.Refresher = (*Filter)(nil) -// Refresh implements the [agdservice.Refresher] interface for *Filter. +// Refresh implements the [service.Refresher] interface for *Filter. func (f *Filter) Refresh(ctx context.Context) (err error) { f.logger.InfoContext(ctx, "refresh started") defer f.logger.InfoContext(ctx, "refresh finished") diff --git a/internal/filter/hashprefix/filter_test.go b/internal/filter/hashprefix/filter_test.go index 1b4e6e5..10bb274 100644 --- a/internal/filter/hashprefix/filter_test.go +++ b/internal/filter/hashprefix/filter_test.go @@ -13,7 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" @@ -22,6 +22,9 @@ import ( "github.com/stretchr/testify/require" ) +// type check +var _ composite.RequestFilter = (*hashprefix.Filter)(nil) + func TestFilter_FilterRequest_host(t *testing.T) { t.Parallel() @@ -31,7 +34,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { name string host string replHost string - wantRule internal.RuleText + wantRule filter.RuleText qType dnsmsg.RRType wantResult bool }{{ @@ -101,7 +104,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { ctx := testutil.ContextWithTimeout(t, filtertest.Timeout) req := dnsservertest.NewReq(dns.Fqdn(tc.host), tc.qType, dns.ClassINET) - r, err := f.FilterRequest(ctx, &internal.Request{ + r, err := f.FilterRequest(ctx, &filter.Request{ DNS: req, Messages: msgs, Host: tc.host, @@ -109,7 +112,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { }) require.NoError(t, err) - var wantRes internal.Result + var wantRes filter.Result if tc.wantResult { if tc.replHost == filtertest.HostAdultContentRepl { wantRes = newModReqResult(req, tc.wantRule) @@ -123,7 +126,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { } require.True(t, t.Run("cached_success", func(t *testing.T) { - f := filtertest.NewHashprefixFilter(t, internal.IDAdultBlocking) + f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking) req := filtertest.NewARequest(t, filtertest.HostAdultContent) @@ -138,7 +141,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { })) require.True(t, t.Run("cached_no_match", func(t *testing.T) { - f := filtertest.NewHashprefixFilter(t, internal.IDAdultBlocking) + f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking) req := filtertest.NewARequest(t, filtertest.Host) @@ -153,7 +156,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { })) require.True(t, t.Run("https", func(t *testing.T) { - f := filtertest.NewHashprefixFilter(t, internal.IDAdultBlocking) + f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking) req := filtertest.NewRequest( t, @@ -175,7 +178,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { require.True(t, t.Run("https_ip", func(t *testing.T) { f := filtertest.NewHashprefixFilterWithRepl( t, - internal.IDAdultBlocking, + filter.IDAdultBlocking, filtertest.IPv4AdultContentReplStr, ) @@ -192,7 +195,7 @@ func TestFilter_FilterRequest_host(t *testing.T) { require.NoError(t, err) require.NotNil(t, r) - m := testutil.RequireTypeAssert[*internal.ResultModifiedResponse](t, r) + m := testutil.RequireTypeAssert[*filter.ResultModifiedResponse](t, r) require.NotNil(t, m.Msg) require.Len(t, m.Msg.Question, 1) @@ -207,13 +210,13 @@ func newModRespResult( req *dns.Msg, messages *dnsmsg.Constructor, replIP netip.Addr, -) (r *internal.ResultModifiedResponse) { +) (r *filter.ResultModifiedResponse) { tb.Helper() resp, err := messages.NewRespIP(req, replIP) require.NoError(tb, err) - return &internal.ResultModifiedResponse{ + return &filter.ResultModifiedResponse{ Msg: resp, List: filter.IDAdultBlocking, Rule: filtertest.HostAdultContent, @@ -221,11 +224,11 @@ func newModRespResult( } // newModReqResult is a helper for creating modified results for tests. -func newModReqResult(req *dns.Msg, rule internal.RuleText) (r *internal.ResultModifiedRequest) { +func newModReqResult(req *dns.Msg, rule filter.RuleText) (r *filter.ResultModifiedRequest) { req = dnsmsg.Clone(req) req.Question[0].Name = filtertest.FQDNAdultContentRepl - return &internal.ResultModifiedRequest{ + return &filter.ResultModifiedRequest{ Msg: req, List: filter.IDAdultBlocking, Rule: rule, @@ -248,8 +251,9 @@ func TestFilter_Refresh(t *testing.T) { Hashes: strg, URL: srvURL, ErrColl: agdtest.NewErrorCollector(), + HashPrefixMtcs: hashprefix.EmptyMetrics{}, Metrics: filter.EmptyMetrics{}, - ID: internal.IDAdultBlocking, + ID: filter.IDAdultBlocking, CachePath: cachePath, ReplacementHost: filtertest.HostAdultContentRepl, Staleness: filtertest.Staleness, @@ -300,8 +304,9 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { Hashes: strg, URL: srvURL, ErrColl: agdtest.NewErrorCollector(), + HashPrefixMtcs: hashprefix.EmptyMetrics{}, Metrics: filter.EmptyMetrics{}, - ID: internal.IDAdultBlocking, + ID: filter.IDAdultBlocking, CachePath: cachePath, ReplacementHost: filtertest.HostAdultContentRepl, Staleness: filtertest.Staleness, @@ -330,7 +335,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { require.True(t, t.Run("hit_cached_host", func(t *testing.T) { ctx = testutil.ContextWithTimeout(t, filtertest.Timeout) - var r internal.Result + var r filter.Result r, err = f.FilterRequest(ctx, otherHostReq) require.NoError(t, err) @@ -355,7 +360,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { require.True(t, t.Run("previously_cached", func(t *testing.T) { ctx = testutil.ContextWithTimeout(t, filtertest.Timeout) - var r internal.Result + var r filter.Result r, err = f.FilterRequest(ctx, otherHostReq) require.NoError(t, err) @@ -365,7 +370,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { require.True(t, t.Run("new_host", func(t *testing.T) { ctx = testutil.ContextWithTimeout(t, filtertest.Timeout) - var r internal.Result + var r filter.Result r, err = f.FilterRequest(ctx, hostReq) require.NoError(t, err) diff --git a/internal/filter/hashprefix/metrics.go b/internal/filter/hashprefix/metrics.go new file mode 100644 index 0000000..fca1183 --- /dev/null +++ b/internal/filter/hashprefix/metrics.go @@ -0,0 +1,28 @@ +package hashprefix + +import ( + "context" +) + +// Metrics is an interface used for collection if the hashprefix filter +// statistics. +type Metrics interface { + // IncrementLookups increments the number of lookups. hit is true if the + // lookup returned a value. + IncrementLookups(ctx context.Context, hit bool) + + // UpdateCacheSize is called when the cache size is updated. + UpdateCacheSize(ctx context.Context, cacheLen int) +} + +// EmptyMetrics is the implementation of the [Metrics] interface that does nothing. +type EmptyMetrics struct{} + +// type check +var _ Metrics = EmptyMetrics{} + +// IncrementLookups implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) IncrementLookups(_ context.Context, _ bool) {} + +// UpdateCacheSize implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) UpdateCacheSize(_ context.Context, _ int) {} diff --git a/internal/filter/internal/id.go b/internal/filter/id.go similarity index 70% rename from internal/filter/internal/id.go rename to internal/filter/id.go index 9f6cdee..d28a1e0 100644 --- a/internal/filter/internal/id.go +++ b/internal/filter/id.go @@ -1,13 +1,14 @@ -package internal +package filter import ( "fmt" "unicode/utf8" + "github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate" "github.com/AdguardTeam/golibs/errors" ) -// ID is the identifier of a filter. It is an opaque string. +// ID is the ID of a filter list. It is an opaque string. type ID string // The maximum and minimum lengths of a filter ID. @@ -21,7 +22,7 @@ const ( func NewID(s string) (id ID, err error) { defer func() { err = errors.Annotate(err, "bad filter id %q: %w", s) }() - err = validateInclusion(len(s), MaxIDLen, MinIDLen, unitByte) + err = agdvalidate.Inclusion(len(s), MinIDLen, MaxIDLen, agdvalidate.UnitByte) if err != nil { return IDNone, err } @@ -29,65 +30,32 @@ func NewID(s string) (id ID, err error) { // Allow only the printable, non-whitespace ASCII characters. Technically // we only need to exclude carriage return, line feed, and slash characters, // but let's be more strict just in case. - if i, r := firstNonIDRune(s, true); i != -1 { + if i, r := agdvalidate.FirstNonIDRune(s, true); i != -1 { return IDNone, fmt.Errorf("bad rune %q at index %d", r, i) } return ID(s), nil } -// 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. -// -// TODO(a.garipov): Merge with the one in package agd once the refactoring is -// over. -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. -// -// TODO(a.garipov): Merge with the one in package agd once the refactoring is -// over. -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. -// -// TODO(a.garipov): Merge with the one in package agd once the refactoring is -// over. -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 - } -} - // Special ID values shared across the AdGuard DNS system. // // NOTE: DO NOT change these as other parts of the system depend on these // values. +// +// TODO(a.garipov): Consider removing those that aren't used outside of the +// filter subpackages. const ( // IDNone means that no filter were applied at all. IDNone ID = "" + // IDAdGuardDNS is the special filter ID of the main AdGuard DNS + // filtering-rule list. For this list, rule statistics are collected. + IDAdGuardDNS ID = "adguard_dns_filter" + + // IDAdultBlocking is the special shared filter ID used when a request was + // filtered by the adult content blocking filter. + IDAdultBlocking ID = "adult_blocking" + // IDBlockedService is the shared filter ID used when a request was blocked // by the service blocker. IDBlockedService ID = "blocked_service" @@ -96,29 +64,21 @@ const ( // by a custom profile rule. IDCustom ID = "custom" - // IDAdultBlocking is the special shared filter ID used when a request was - // filtered by the adult content blocking filter. - IDAdultBlocking ID = "adult_blocking" - - // IDSafeBrowsing is the special shared filter ID used when a request was - // filtered by the safe browsing filter. - IDSafeBrowsing ID = "safe_browsing" + // IDGeneralSafeSearch is the shared filter ID used when a request was + // modified by the general safe search filter. + IDGeneralSafeSearch ID = "general_safe_search" // IDNewRegDomains is the special shared filter ID used when a request was // filtered by the newly registered domains filter. IDNewRegDomains ID = "newly_registered_domains" - // IDGeneralSafeSearch is the shared filter ID used when a request was - // modified by the general safe search filter. - IDGeneralSafeSearch ID = "general_safe_search" + // IDSafeBrowsing is the special shared filter ID used when a request was + // filtered by the safe browsing filter. + IDSafeBrowsing ID = "safe_browsing" // IDYoutubeSafeSearch is the special shared filter ID used when a request // was modified by the YouTube safe search filter. IDYoutubeSafeSearch ID = "youtube_safe_search" - - // IDAdGuardDNS is the special filter ID of the main AdGuard DNS - // filtering-rule list. For this list, rule statistics are collected. - IDAdGuardDNS ID = "adguard_dns_filter" ) // RuleText is the text of a single rule within a rule-list filter. @@ -132,7 +92,12 @@ const MaxRuleTextRuneLen = 1024 func NewRuleText(s string) (t RuleText, err error) { defer func() { err = errors.Annotate(err, "bad filter rule text %q: %w", s) }() - err = validateInclusion(utf8.RuneCountInString(s), MaxRuleTextRuneLen, 0, unitRune) + err = agdvalidate.Inclusion( + utf8.RuneCountInString(s), + 0, + MaxRuleTextRuneLen, + agdvalidate.UnitRune, + ) if err != nil { return "", err } @@ -159,7 +124,12 @@ const ( func NewBlockedServiceID(s string) (id BlockedServiceID, err error) { defer func() { err = errors.Annotate(err, "bad blocked service id %q: %w", s) }() - err = validateInclusion(len(s), MaxBlockedServiceIDLen, MinBlockedServiceIDLen, unitByte) + err = agdvalidate.Inclusion( + len(s), + MinBlockedServiceIDLen, + MaxBlockedServiceIDLen, + agdvalidate.UnitByte, + ) if err != nil { return "", err } @@ -167,7 +137,7 @@ func NewBlockedServiceID(s string) (id BlockedServiceID, err error) { // Allow only the printable, non-whitespace ASCII characters. Technically // we only need to exclude carriage return, line feed, and slash characters, // but let's be more strict just in case. - if i, r := firstNonIDRune(s, true); i != -1 { + if i, r := agdvalidate.FirstNonIDRune(s, true); i != -1 { return "", fmt.Errorf("bad char %q at index %d", r, i) } diff --git a/internal/filter/internal/id_test.go b/internal/filter/id_test.go similarity index 86% rename from internal/filter/internal/id_test.go rename to internal/filter/id_test.go index 2db5f85..465b774 100644 --- a/internal/filter/internal/id_test.go +++ b/internal/filter/id_test.go @@ -1,10 +1,10 @@ -package internal_test +package filter_test import ( "strings" "testing" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" ) @@ -38,7 +38,7 @@ func TestNewID(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - id, err := internal.NewID(tc.in) + id, err := filter.NewID(tc.in) testutil.AssertErrorMsg(t, tc.wantErrMsg, err) if tc.wantErrMsg == "" && tc.in != "" { assert.NotEmpty(t, id) @@ -52,8 +52,8 @@ func TestNewID(t *testing.T) { func TestNewRuleText(t *testing.T) { t.Parallel() - tooLong := strings.Repeat("a", internal.MaxRuleTextRuneLen+1) - tooLongUnicode := strings.Repeat("ы", internal.MaxRuleTextRuneLen+1) + tooLong := strings.Repeat("a", filter.MaxRuleTextRuneLen+1) + tooLongUnicode := strings.Repeat("ы", filter.MaxRuleTextRuneLen+1) testCases := []struct { name string @@ -86,7 +86,7 @@ func TestNewRuleText(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - txt, err := internal.NewRuleText(tc.in) + txt, err := filter.NewRuleText(tc.in) testutil.AssertErrorMsg(t, tc.wantErrMsg, err) if tc.wantErrMsg == "" && tc.in != "" { assert.NotEmpty(t, txt) diff --git a/internal/filter/internal/cachekey.go b/internal/filter/internal/cachekey.go deleted file mode 100644 index 9394a1a..0000000 --- a/internal/filter/internal/cachekey.go +++ /dev/null @@ -1,36 +0,0 @@ -package internal - -import ( - "encoding/binary" - "hash/maphash" - - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/golibs/mathutil" -) - -// CacheKey is the cache key type for [NewCacheKey]. -type CacheKey uint64 - -// hashSeed is the seed used by all hashes to create hash keys. -var hashSeed = maphash.MakeSeed() - -// NewCacheKey produces a cache key based on host, qt, and isAns using the -// default algorithm. -func NewCacheKey(host string, qt dnsmsg.RRType, cl dnsmsg.Class, isAns bool) (k CacheKey) { - // Use maphash explicitly instead of using a key structure to reduce - // allocations and optimize interface conversion up the stack. - h := &maphash.Hash{} - h.SetSeed(hashSeed) - - _, _ = h.WriteString(host) - - // Save on allocations by reusing a buffer. - var buf [5]byte - binary.LittleEndian.PutUint16(buf[:2], qt) - binary.LittleEndian.PutUint16(buf[2:4], cl) - buf[4] = mathutil.BoolToNumber[byte](isAns) - - _, _ = h.Write(buf[:]) - - return CacheKey(h.Sum64()) -} diff --git a/internal/filter/internal/composite/composite.go b/internal/filter/internal/composite/composite.go index bf96949..0b5be03 100644 --- a/internal/filter/internal/composite/composite.go +++ b/internal/filter/internal/composite/composite.go @@ -4,15 +4,11 @@ package composite import ( "context" - "fmt" "strings" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/filter" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch" "github.com/miekg/dns" ) @@ -20,7 +16,7 @@ import ( // rule-list filters. type Filter struct { // custom is the custom rule-list filter of the profile, if any. - custom *rulelist.Immutable + custom filter.Custom // ruleLists are the enabled rule-list filters of the profile or filtering // group. @@ -32,29 +28,29 @@ type Filter struct { // reqFilters are the safe-browsing and safe-search request filters in the // composite filter. - reqFilters []internal.RequestFilter + reqFilters []RequestFilter } // Config is the configuration structure for the composite filter. type Config struct { // SafeBrowsing is the safe-browsing filter to apply, if any. - SafeBrowsing *hashprefix.Filter + SafeBrowsing RequestFilter // AdultBlocking is the adult-content filter to apply, if any. - AdultBlocking *hashprefix.Filter + AdultBlocking RequestFilter // NewRegisteredDomains is the newly registered domains filter to apply, if // any. - NewRegisteredDomains *hashprefix.Filter + NewRegisteredDomains RequestFilter // GeneralSafeSearch is the general safe-search filter to apply, if any. - GeneralSafeSearch *safesearch.Filter + GeneralSafeSearch RequestFilter // YouTubeSafeSearch is the youtube safe-search filter to apply, if any. - YouTubeSafeSearch *safesearch.Filter + YouTubeSafeSearch RequestFilter // Custom is the custom rule-list filter of the profile, if any. - Custom *rulelist.Immutable + Custom filter.Custom // RuleLists are the enabled rule-list filters of the profile or filtering // group, if any. All items must not be nil. @@ -65,6 +61,13 @@ type Config struct { ServiceLists []*rulelist.Immutable } +// RequestFilter can filter a request based on the request info. +type RequestFilter interface { + // FilterRequest filters a DNS request based on the information provided + // about the request. req must be valid. + FilterRequest(ctx context.Context, req *filter.Request) (r filter.Result, err error) +} + // New returns a new composite filter. c must not be nil. func New(c *Config) (f *Filter) { f = &Filter{ @@ -74,22 +77,19 @@ func New(c *Config) (f *Filter) { } // DO NOT change the order of request filters without necessity. - f.reqFilters = appendReqFilter(f.reqFilters, c.SafeBrowsing) - f.reqFilters = appendReqFilter(f.reqFilters, c.AdultBlocking) - f.reqFilters = appendReqFilter(f.reqFilters, c.GeneralSafeSearch) - f.reqFilters = appendReqFilter(f.reqFilters, c.YouTubeSafeSearch) - f.reqFilters = appendReqFilter(f.reqFilters, c.NewRegisteredDomains) + f.reqFilters = appendIfNotNil(f.reqFilters, c.SafeBrowsing) + f.reqFilters = appendIfNotNil(f.reqFilters, c.AdultBlocking) + f.reqFilters = appendIfNotNil(f.reqFilters, c.GeneralSafeSearch) + f.reqFilters = appendIfNotNil(f.reqFilters, c.YouTubeSafeSearch) + f.reqFilters = appendIfNotNil(f.reqFilters, c.NewRegisteredDomains) return f } -// appendReqFilter appends flt to flts if flt is not nil. -func appendReqFilter[T *hashprefix.Filter | *safesearch.Filter]( - flts []internal.RequestFilter, - flt T, -) (res []internal.RequestFilter) { +// appendIfNotNil appends flt to flts if flt is not nil. +func appendIfNotNil(flts []RequestFilter, flt RequestFilter) (res []RequestFilter) { if flt != nil { - flts = append(flts, internal.RequestFilter(flt)) + flts = append(flts, flt) } return flts @@ -98,7 +98,7 @@ func appendReqFilter[T *hashprefix.Filter | *safesearch.Filter]( // type check var _ filter.Interface = (*Filter)(nil) -// FilterRequest implements the [internal.Interface] interface for *Filter. The +// FilterRequest implements the [filter.Interface] interface for *Filter. The // order in which the filters are applied is the following: // // 1. Custom filter. @@ -113,23 +113,23 @@ var _ filter.Interface = (*Filter)(nil) // If f is empty, it returns nil with no error. func (f *Filter) FilterRequest( ctx context.Context, - req *internal.Request, -) (r internal.Result, err error) { + req *filter.Request, +) (r filter.Result, err error) { // Prepare common data for filters. Firstly, check the profile's rule-list // filtering, the custom rules, and the rules from blocked services // settings. - rlRes := f.filterReqWithRuleLists(req) + rlRes := f.filterReqWithRuleLists(ctx, req) switch flRes := rlRes.(type) { - case *internal.ResultAllowed: + case *filter.ResultAllowed: // Skip any additional filtering if the domain is explicitly allowed by // user's custom rule. - if flRes.List == internal.IDCustom { + if flRes.List == filter.IDCustom { return flRes, nil } case - *internal.ResultBlocked, - *internal.ResultModifiedRequest, - *internal.ResultModifiedResponse: + *filter.ResultBlocked, + *filter.ResultModifiedRequest, + *filter.ResultModifiedResponse: // Skip any additional filtering if the query is already blocked or // modified. return flRes, nil @@ -152,15 +152,20 @@ func (f *Filter) FilterRequest( // filterReqWithRuleLists filters one question's information through all rule // list filters of the composite filter. req must not be nil. -func (f *Filter) filterReqWithRuleLists(req *internal.Request) (r internal.Result) { +func (f *Filter) filterReqWithRuleLists( + ctx context.Context, + req *filter.Request, +) (r filter.Result) { ip, host, qt := req.RemoteIP, req.Host, req.QType - ufRes := &rulelist.URLFilterResult{} + // TODO(a.garipov): Consider adding a pool of results to the default + // storage and use it here. + ufRes := newURLFilterResult() if f.custom != nil { - id := internal.IDCustom + id := filter.IDCustom // Only use the device name for custom filters of profiles with devices. - dr := f.custom.DNSResult(ip, req.ClientName, host, qt, false) + dr := f.custom.DNSResult(ctx, ip, req.ClientName, host, qt, false) mod := rulelist.ProcessDNSRewrites(req, dr.DNSRewrites(), id) if mod != nil { // Process the DNS rewrites of the custom list and return them @@ -168,7 +173,7 @@ func (f *Filter) filterReqWithRuleLists(req *internal.Request) (r internal.Resul return mod } - ufRes.Add(dr) + ufRes.add(id, "", dr) } for _, rl := range f.ruleLists { @@ -181,26 +186,27 @@ func (f *Filter) filterReqWithRuleLists(req *internal.Request) (r internal.Resul return mod } - ufRes.Add(dr) + ufRes.add(id, "", dr) } for _, rl := range f.svcLists { - ufRes.Add(rl.DNSResult(ip, "", host, qt, false)) + id, svcID := rl.ID() + ufRes.add(id, svcID, rl.DNSResult(ip, "", host, qt, false)) } - return ufRes.ToInternal(f, qt) + return ufRes.toInternal(qt) } -// FilterResponse implements the [internal.Interface] interface for *Filter. It +// FilterResponse implements the [filter.Interface] interface for *Filter. It // returns the action created from the filter list network rule with the highest // priority. If f is empty, it returns nil with no error. Note that rewrite // results are not applied to responses. func (f *Filter) FilterResponse( - _ context.Context, - resp *internal.Response, -) (r internal.Result, err error) { + ctx context.Context, + resp *filter.Response, +) (r filter.Result, err error) { for _, ans := range resp.DNS.Answer { - r = f.filterAnswer(resp, ans) + r = f.filterAnswer(ctx, resp, ans) if r != nil { break } @@ -211,9 +217,13 @@ func (f *Filter) FilterResponse( // filterAnswer filters a single answer of a response. r is not nil if the // response is filtered. -func (f *Filter) filterAnswer(resp *internal.Response, ans dns.RR) (r internal.Result) { +func (f *Filter) filterAnswer( + ctx context.Context, + resp *filter.Response, + ans dns.RR, +) (r filter.Result) { if rr, ok := ans.(*dns.HTTPS); ok { - return f.filterHTTPSAnswer(resp, rr) + return f.filterHTTPSAnswer(ctx, resp, rr) } host, rrType, ok := parseRespAnswer(ans) @@ -221,39 +231,47 @@ func (f *Filter) filterAnswer(resp *internal.Response, ans dns.RR) (r internal.R return nil } - return f.filterRespWithRuleLists(resp, host, rrType) + return f.filterRespWithRuleLists(ctx, resp, host, rrType) } // filterRespWithRuleLists filters one answer's information through all // rule-list filters of the composite filter. func (f *Filter) filterRespWithRuleLists( - resp *internal.Response, + ctx context.Context, + resp *filter.Response, host string, rrType dnsmsg.RRType, -) (r internal.Result) { - ufRes := &rulelist.URLFilterResult{} +) (r filter.Result) { + ufRes := newURLFilterResult() for _, rl := range f.ruleLists { - ufRes.Add(rl.DNSResult(resp.RemoteIP, "", host, rrType, true)) + id, _ := rl.ID() + ufRes.add(id, "", rl.DNSResult(resp.RemoteIP, "", host, rrType, true)) } if f.custom != nil { - ufRes.Add(f.custom.DNSResult(resp.RemoteIP, resp.ClientName, host, rrType, true)) + dr := f.custom.DNSResult(ctx, resp.RemoteIP, resp.ClientName, host, rrType, true) + ufRes.add(filter.IDCustom, "", dr) } for _, rl := range f.svcLists { - ufRes.Add(rl.DNSResult(resp.RemoteIP, "", host, rrType, true)) + id, svcID := rl.ID() + ufRes.add(id, svcID, rl.DNSResult(resp.RemoteIP, "", host, rrType, true)) } - return ufRes.ToInternal(f, rrType) + return ufRes.toInternal(rrType) } // filterHTTPSAnswer filters HTTPS answers information through all rule list // filters of the composite filter. -func (f *Filter) filterHTTPSAnswer(resp *internal.Response, rr *dns.HTTPS) (r internal.Result) { +func (f *Filter) filterHTTPSAnswer( + ctx context.Context, + resp *filter.Response, + rr *dns.HTTPS, +) (r filter.Result) { for _, kv := range rr.Value { switch kv.Key() { case dns.SVCB_IPV4HINT, dns.SVCB_IPV6HINT: - r = f.filterSVCBHint(kv.String(), resp) + r = f.filterSVCBHint(ctx, kv.String(), resp) if r != nil { return r } @@ -267,9 +285,13 @@ func (f *Filter) filterHTTPSAnswer(resp *internal.Response, rr *dns.HTTPS) (r in // filterSVCBHint filters SVCB hint information through all rule list filters of // the composite filter. -func (f *Filter) filterSVCBHint(hint string, resp *internal.Response) (r internal.Result) { +func (f *Filter) filterSVCBHint( + ctx context.Context, + hint string, + resp *filter.Response, +) (r filter.Result) { for _, s := range strings.Split(hint, ",") { - r = f.filterRespWithRuleLists(resp, s, dns.TypeHTTPS) + r = f.filterRespWithRuleLists(ctx, resp, s, dns.TypeHTTPS) if r != nil { return r } @@ -293,31 +315,3 @@ func parseRespAnswer(ans dns.RR) (hostname string, rrType dnsmsg.RRType, ok bool return "", dns.TypeNone, false } } - -// type check -var _ rulelist.IDMapper = (*Filter)(nil) - -// Map implements the [rulelist.IDMapper] interface for *Filter. It returns the -// rule list data by its synthetic integer ID in the urlfilter engine. It -// panics if id is not found. -func (f *Filter) Map(id int) (fltID internal.ID, svcID internal.BlockedServiceID) { - for _, rl := range f.ruleLists { - if rl.URLFilterID() == id { - return rl.ID() - } - } - - if rl := f.custom; rl != nil && rl.URLFilterID() == id { - return rl.ID() - } - - for _, rl := range f.svcLists { - if rl.URLFilterID() == id { - return rl.ID() - } - } - - // Technically shouldn't happen, since id is supposed to be among the rule - // list filters in the composite filter. - panic(fmt.Errorf("filter: synthetic id %d not found", id)) -} diff --git a/internal/filter/internal/composite/composite_internal_test.go b/internal/filter/internal/composite/composite_internal_test.go index 715f58f..a018298 100644 --- a/internal/filter/internal/composite/composite_internal_test.go +++ b/internal/filter/internal/composite/composite_internal_test.go @@ -1,40 +1,40 @@ package composite import ( + "context" "testing" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/miekg/dns" - "github.com/stretchr/testify/require" ) // Sinks for benchmarks. var ( - resultSink internal.Result + resultSink filter.Result ) func BenchmarkFilter_FilterReqWithRuleLists(b *testing.B) { - blockingRL, err := rulelist.NewFromString( + blockingRL := rulelist.NewFromString( filtertest.RuleBlockStr+"\n", "test", "", - rulelist.ResultCacheEmpty{}, + rulelist.EmptyResultCache{}, ) - require.NoError(b, err) f := New(&Config{ RuleLists: []*rulelist.Refreshable{blockingRL}, }) + ctx := context.Background() req := filtertest.NewRequest(b, "", filtertest.HostBlocked, filtertest.IPv4Client, dns.TypeA) b.ReportAllocs() b.ResetTimer() for range b.N { - resultSink = f.filterReqWithRuleLists(req) + resultSink = f.filterReqWithRuleLists(ctx, req) } // Most recent results: diff --git a/internal/filter/internal/composite/composite_test.go b/internal/filter/internal/composite/composite_test.go index 129c113..da57a31 100644 --- a/internal/filter/internal/composite/composite_test.go +++ b/internal/filter/internal/composite/composite_test.go @@ -7,10 +7,12 @@ import ( "testing" "time" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/custom" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" @@ -23,41 +25,26 @@ import ( "github.com/stretchr/testify/require" ) -// newFromStr is a helper to create a rule-list filter from a rule text and a -// filtering-list ID. -func newFromStr(tb testing.TB, text string, id internal.ID) (rl *rulelist.Refreshable) { - tb.Helper() - - rl, err := rulelist.NewFromString(text, id, "", rulelist.ResultCacheEmpty{}) - require.NoError(tb, err) - - return rl -} - -// newImmutable is a helper to create an immutable rule-list filter from a rule -// text and a filtering-list ID. -func newImmutable(tb testing.TB, text string, id internal.ID) (rl *rulelist.Immutable) { - tb.Helper() - - rl, err := rulelist.NewImmutable(text, id, "", rulelist.ResultCacheEmpty{}) - require.NoError(tb, err) - - return rl -} - // newReqData returns data for calling FilterRequest. The context uses // [filtertest.Timeout] and [tb.Cleanup] is used for its cancelation. req uses // [filtertest.FQDNBlocked], [dns.TypeA], and [dns.ClassINET] for the request // data. -func newReqData(tb testing.TB) (ctx context.Context, req *internal.Request) { +func newReqData(tb testing.TB) (ctx context.Context, req *filter.Request) { + tb.Helper() + + return newReqDataWithFQDN(tb, filtertest.FQDNBlocked) +} + +// newReqDataWithFQDN is like [newReqData] but allows setting the FQDN. +func newReqDataWithFQDN(tb testing.TB, fqdn string) (ctx context.Context, req *filter.Request) { tb.Helper() ctx = testutil.ContextWithTimeout(tb, filtertest.Timeout) - req = &internal.Request{ - DNS: dnsservertest.NewReq(filtertest.FQDNBlocked, dns.TypeA, dns.ClassINET), + req = &filter.Request{ + DNS: dnsservertest.NewReq(fqdn, dns.TypeA, dns.ClassINET), Messages: agdtest.NewConstructor(tb), RemoteIP: filtertest.IPv4Client, - Host: filtertest.HostBlocked, + Host: agdnet.NormalizeDomain(fqdn), QType: dns.TypeA, QClass: dns.ClassINET, } @@ -66,29 +53,29 @@ func newReqData(tb testing.TB) (ctx context.Context, req *internal.Request) { } func TestFilter_FilterRequest_customWithClientName(t *testing.T) { - const ( - devName = "MyDevice" - blockRule = filtertest.RuleBlockStr + "$client=" + devName - ) - f := composite.New(&composite.Config{ - Custom: newImmutable(t, blockRule, internal.IDCustom), + Custom: custom.New(&custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: []filter.RuleText{ + filtertest.RuleBlockForClientName, + }, + }), }) - ctx, req := newReqData(t) + ctx, req := newReqDataWithFQDN(t, filtertest.FQDNBlockedForClientName) res, err := f.FilterRequest(ctx, req) require.NoError(t, err) assert.Nil(t, res) - req.ClientName = devName + req.ClientName = filtertest.ClientName res, err = f.FilterRequest(ctx, req) require.NoError(t, err) - wantRes := &internal.ResultBlocked{ - List: internal.IDCustom, - Rule: blockRule, + wantRes := &filter.ResultBlocked{ + List: filter.IDCustom, + Rule: filtertest.RuleBlockForClientName, } assert.Equal(t, wantRes, res) @@ -105,11 +92,11 @@ func TestFilter_FilterRequest_badfilter(t *testing.T) { testCases := []struct { name string - wantRes internal.Result + wantRes filter.Result ruleLists []*rulelist.Refreshable }{{ name: "block", - wantRes: &internal.ResultBlocked{ + wantRes: &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: blockRule, }, @@ -139,11 +126,22 @@ func TestFilter_FilterRequest_badfilter(t *testing.T) { } } +// newFromStr is a helper to create a rule-list filter from a rule text and a +// filtering-list ID. +func newFromStr(tb testing.TB, text string, id filter.ID) (rl *rulelist.Refreshable) { + tb.Helper() + + return rulelist.NewFromString(text, id, "", rulelist.EmptyResultCache{}) +} + func TestFilter_FilterRequest_customAllow(t *testing.T) { - const allowRule = "@@" + filtertest.RuleBlockStr + const allowRule = "@@" + filtertest.RuleBlock blockingRL := newFromStr(t, filtertest.RuleBlockStr, filtertest.RuleListID1) - customRL := newImmutable(t, allowRule, internal.IDCustom) + customRL := custom.New(&custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: []filter.RuleText{allowRule}, + }) f := composite.New(&composite.Config{ Custom: customRL, @@ -154,8 +152,8 @@ func TestFilter_FilterRequest_customAllow(t *testing.T) { res, err := f.FilterRequest(ctx, req) require.NoError(t, err) - want := &internal.ResultAllowed{ - List: internal.IDCustom, + want := &filter.ResultAllowed{ + List: filter.IDCustom, Rule: allowRule, } assert.Equal(t, want, res) @@ -177,10 +175,10 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { var ( rlNonRewrite = newFromStr(t, blockRule, filtertest.RuleListID1) - rlCustomRefused = newImmutable(t, dnsRewriteRuleRefused, internal.IDCustom) - rlCustomCname = newImmutable(t, dnsRewriteRuleCname, internal.IDCustom) - rlCustom2Rules = newImmutable(t, dnsRewrite2Rules, internal.IDCustom) - rlCustomTyped = newImmutable(t, dnsRewriteTypedRules, internal.IDCustom) + rlCustomRefused = newCustom(t, dnsRewriteRuleRefused) + rlCustomCname = newCustom(t, dnsRewriteRuleCname) + rlCustom2Rules = newCustom(t, dnsRewrite2Rules) + rlCustomTyped = newCustom(t, dnsRewriteTypedRules) ) req := dnsservertest.NewReq(filtertest.FQDNBlocked, dns.TypeA, dns.ClassINET) @@ -196,15 +194,15 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { soaReq.Question[0].Qtype = dns.TypeSOA testCases := []struct { - custom *rulelist.Immutable + custom filter.Custom req *dns.Msg - wantRes internal.Result + wantRes filter.Result name string ruleLists []*rulelist.Refreshable }{{ custom: nil, req: req, - wantRes: &internal.ResultBlocked{ + wantRes: &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: blockRule, }, @@ -213,7 +211,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: nil, req: req, - wantRes: &internal.ResultBlocked{ + wantRes: &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: blockRule, }, @@ -222,9 +220,9 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: rlCustomRefused, req: req, - wantRes: &internal.ResultModifiedResponse{ + wantRes: &filter.ResultModifiedResponse{ Msg: dnsservertest.NewResp(dns.RcodeRefused, req), - List: internal.IDCustom, + List: filter.IDCustom, Rule: dnsRewriteRuleRefused, }, name: "dnsrewrite_block", @@ -232,9 +230,9 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: rlCustomCname, req: req, - wantRes: &internal.ResultModifiedRequest{ + wantRes: &filter.ResultModifiedRequest{ Msg: modifiedReq, - List: internal.IDCustom, + List: filter.IDCustom, Rule: dnsRewriteRuleCname, }, name: "dnsrewrite_cname", @@ -242,7 +240,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: rlCustom2Rules, req: req, - wantRes: &internal.ResultModifiedResponse{ + wantRes: &filter.ResultModifiedResponse{ Msg: dnsservertest.NewResp(dns.RcodeSuccess, req, dnsservertest.SectionAnswer{ dnsservertest.NewA( filtertest.FQDNBlocked, @@ -255,7 +253,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { netip.MustParseAddr("1.2.3.5"), ), }), - List: internal.IDCustom, + List: filter.IDCustom, Rule: "", }, name: "dnsrewrite_answers", @@ -263,7 +261,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: rlCustomTyped, req: txtReq, - wantRes: &internal.ResultModifiedResponse{ + wantRes: &filter.ResultModifiedResponse{ Msg: dnsservertest.NewResp(dns.RcodeSuccess, txtReq, dnsservertest.SectionAnswer{ dnsservertest.NewTXT( filtertest.FQDNBlocked, @@ -271,7 +269,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { "abcdefg", ), }), - List: internal.IDCustom, + List: filter.IDCustom, Rule: "", }, name: "dnsrewrite_txt", @@ -279,9 +277,9 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, { custom: rlCustomTyped, req: soaReq, - wantRes: &internal.ResultModifiedResponse{ + wantRes: &filter.ResultModifiedResponse{ Msg: dnsservertest.NewResp(dns.RcodeSuccess, soaReq), - List: internal.IDCustom, + List: filter.IDCustom, }, name: "dnsrewrite_soa", ruleLists: []*rulelist.Refreshable{}, @@ -289,13 +287,15 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - f := composite.New(&composite.Config{ + c := &composite.Config{ Custom: tc.custom, RuleLists: tc.ruleLists, - }) + } + + f := composite.New(c) ctx := context.Background() - res, fltErr := f.FilterRequest(ctx, &internal.Request{ + res, fltErr := f.FilterRequest(ctx, &filter.Request{ DNS: tc.req, Messages: agdtest.NewConstructor(t), Host: filtertest.HostBlocked, @@ -309,6 +309,18 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { } } +// newCustom is a helper to create a cusotm filter from a rule text. +func newCustom(tb testing.TB, text string) (f *custom.Filter) { + tb.Helper() + + return custom.New(&custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: []filter.RuleText{ + filter.RuleText(text), + }, + }) +} + func TestFilter_FilterRequest_hostsRules(t *testing.T) { const ( reqHost4 = "www.example.com" @@ -326,18 +338,18 @@ func TestFilter_FilterRequest_hostsRules(t *testing.T) { RuleLists: []*rulelist.Refreshable{rl}, }) - resBlocked4 := &internal.ResultBlocked{ + resBlocked4 := &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: blockRule4, } - resBlocked6 := &internal.ResultBlocked{ + resBlocked6 := &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: blockRule6, } testCases := []struct { - wantRes internal.Result + wantRes filter.Result name string reqHost string reqType dnsmsg.RRType @@ -379,7 +391,7 @@ func TestFilter_FilterRequest_hostsRules(t *testing.T) { } ctx := context.Background() - fltReq := &internal.Request{ + fltReq := &filter.Request{ DNS: req, Messages: agdtest.NewConstructor(t), Host: tc.reqHost, @@ -399,7 +411,7 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { const rewriteRule = filtertest.RuleSafeSearchGeneralIPv4Str + "\n" cachePath, srvURL := filtertest.PrepareRefreshable(t, nil, rewriteRule, http.StatusOK) - const fltListID = internal.IDGeneralSafeSearch + const fltListID = filter.IDGeneralSafeSearch gen, err := safesearch.New( &safesearch.Config{ @@ -425,10 +437,7 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { GeneralSafeSearch: gen, }) - ctx, req := newReqData(t) - req.DNS.Question[0].Name = filtertest.FQDNSafeSearchGeneralIPv4 - req.Host = filtertest.HostSafeSearchGeneralIPv4 - + ctx, req := newReqDataWithFQDN(t, filtertest.FQDNSafeSearchGeneralIPv4) res, err := f.FilterRequest(ctx, req) require.NoError(t, err) @@ -439,7 +448,7 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { filtertest.IPv4SafeSearchRepl, ), }) - want := &internal.ResultModifiedResponse{ + want := &filter.ResultModifiedResponse{ Msg: wantResp, List: fltListID, Rule: filtertest.HostSafeSearchGeneralIPv4, @@ -448,13 +457,12 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { } func TestFilter_FilterRequest_services(t *testing.T) { - svcRL, err := rulelist.NewImmutable( + svcRL := rulelist.NewImmutable( filtertest.RuleBlockStr, - internal.IDBlockedService, + filter.IDBlockedService, filtertest.BlockedServiceID1, - rulelist.ResultCacheEmpty{}, + rulelist.EmptyResultCache{}, ) - require.NoError(t, err) f := composite.New(&composite.Config{ ServiceLists: []*rulelist.Immutable{svcRL}, @@ -464,9 +472,9 @@ func TestFilter_FilterRequest_services(t *testing.T) { res, err := f.FilterRequest(ctx, req) require.NoError(t, err) - want := &internal.ResultBlocked{ - List: internal.IDBlockedService, - Rule: internal.RuleText(filtertest.BlockedServiceID1), + want := &filter.ResultBlocked{ + List: filter.IDBlockedService, + Rule: filter.RuleText(filtertest.BlockedServiceID1), } assert.Equal(t, want, res) } @@ -499,7 +507,7 @@ func TestFilter_FilterResponse(t *testing.T) { testCases := []struct { name string reqFQDN string - wantRule internal.RuleText + wantRule filter.RuleText respAns dnsservertest.SectionAnswer qType dnsmsg.RRType }{{ @@ -583,11 +591,10 @@ func TestFilter_FilterResponse(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ctx, req := newReqData(t) - req.DNS.Question[0].Name = tc.reqFQDN + ctx, req := newReqDataWithFQDN(t, tc.reqFQDN) req.DNS.Question[0].Qtype = tc.qType - res, err := f.FilterResponse(ctx, &internal.Response{ + res, err := f.FilterResponse(ctx, &filter.Response{ DNS: dnsservertest.NewResp(dns.RcodeSuccess, req.DNS, tc.respAns), RemoteIP: filtertest.IPv4Client, }) @@ -599,7 +606,7 @@ func TestFilter_FilterResponse(t *testing.T) { return } - want := &internal.ResultBlocked{ + want := &filter.ResultBlocked{ List: filtertest.RuleListID1, Rule: tc.wantRule, } diff --git a/internal/filter/internal/composite/result.go b/internal/filter/internal/composite/result.go new file mode 100644 index 0000000..72e185f --- /dev/null +++ b/internal/filter/internal/composite/result.go @@ -0,0 +1,189 @@ +package composite + +import ( + "fmt" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/urlfilter" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" +) + +// urlFilterResult is an entity simplifying the collection and compilation of +// urlfilter results. It contains per-pointer indexes of the IDs of filters +// producing network and host rules. +type urlFilterResult struct { + netRuleIDs map[*rules.NetworkRule]filter.ID + hostRuleIDs map[*rules.HostRule]filter.ID + + netRuleSvcIDs map[*rules.NetworkRule]filter.BlockedServiceID + hostRuleSvcIDs map[*rules.HostRule]filter.BlockedServiceID + + networkRules []*rules.NetworkRule + hostRules4 []*rules.HostRule + hostRules6 []*rules.HostRule +} + +// newURLFilterResult returns a properly initialized *urlFilterResult. +func newURLFilterResult() (r *urlFilterResult) { + return &urlFilterResult{ + netRuleIDs: map[*rules.NetworkRule]filter.ID{}, + hostRuleIDs: map[*rules.HostRule]filter.ID{}, + + netRuleSvcIDs: map[*rules.NetworkRule]filter.BlockedServiceID{}, + hostRuleSvcIDs: map[*rules.HostRule]filter.BlockedServiceID{}, + } +} + +// add appends the rules from dr to the slices within r. If dr is nil, add does +// nothing. +func (r *urlFilterResult) add( + id filter.ID, + svcID filter.BlockedServiceID, + dr *urlfilter.DNSResult, +) { + if dr == nil { + return + } + + for _, nr := range dr.NetworkRules { + r.networkRules = append(r.networkRules, nr) + r.netRuleIDs[nr] = id + if svcID != "" { + r.netRuleSvcIDs[nr] = svcID + } + } + + r.addHostRules(id, svcID, dr.HostRulesV4, dr.HostRulesV6) +} + +// addHostRules adds the host rules to the result. +func (r *urlFilterResult) addHostRules( + id filter.ID, + svcID filter.BlockedServiceID, + hostRules4 []*rules.HostRule, + hostRules6 []*rules.HostRule, +) { + for _, hr4 := range hostRules4 { + r.hostRules4 = append(r.hostRules4, hr4) + r.hostRuleIDs[hr4] = id + if svcID != "" { + r.hostRuleSvcIDs[hr4] = svcID + } + } + + for _, hr6 := range hostRules6 { + r.hostRules6 = append(r.hostRules6, hr6) + r.hostRuleIDs[hr6] = id + if svcID != "" { + r.hostRuleSvcIDs[hr6] = svcID + } + } +} + +// toInternal converts a result of using several urlfilter rulelists into a +// filter.Result. +func (r *urlFilterResult) toInternal(rrType dnsmsg.RRType) (res filter.Result) { + if nr := rules.GetDNSBasicRule(r.networkRules); nr != nil { + return r.netRuleDataToResult(nr) + } + + return r.hostsRulesToResult(rrType) +} + +// netRuleDataToResult converts a urlfilter network rule into a filtering +// result. +func (r *urlFilterResult) netRuleDataToResult(nr *rules.NetworkRule) (res filter.Result) { + fltID, ok := r.netRuleIDs[nr] + if !ok { + // Shouldn't happen, since fltID is supposed to be among the filters + // added to the result. + panic(fmt.Errorf("composite: filter id %q not found", fltID)) + } + + var rule filter.RuleText + if fltID == filter.IDBlockedService { + var svcID filter.BlockedServiceID + svcID, ok = r.netRuleSvcIDs[nr] + if !ok { + // Shouldn't happen, since svcID is supposed to be among the filters + // added to the result. + panic(fmt.Errorf("composite: service id %q not found", svcID)) + } + + rule = filter.RuleText(svcID) + } else { + rule = filter.RuleText(nr.RuleText) + } + + if nr.Whitelist { + return &filter.ResultAllowed{ + List: fltID, + Rule: rule, + } + } + + return &filter.ResultBlocked{ + List: fltID, + Rule: rule, + } +} + +// hostsRulesToResult converts /etc/hosts-style rules into a filtering result. +func (r *urlFilterResult) hostsRulesToResult(rrType dnsmsg.RRType) (res filter.Result) { + if len(r.hostRules4) == 0 && len(r.hostRules6) == 0 { + return nil + } + + // Only use the first matched rule, since we currently don't care about the + // IP addresses in the rule. If the request is neither an A one nor an AAAA + // one, or if there are no matching rules of the requested type, then use + // whatever rule isn't empty. + // + // See also AGDNS-591. + var resHostRule *rules.HostRule + if rrType == dns.TypeA && len(r.hostRules4) > 0 { + resHostRule = r.hostRules4[0] + } else if rrType == dns.TypeAAAA && len(r.hostRules6) > 0 { + resHostRule = r.hostRules6[0] + } else { + if len(r.hostRules4) > 0 { + resHostRule = r.hostRules4[0] + } else { + resHostRule = r.hostRules6[0] + } + } + + return r.hostRuleDataToResult(resHostRule) +} + +// hostRuleDataToResult converts a urlfilter host rule into a filtering result. +func (r *urlFilterResult) hostRuleDataToResult(hr *rules.HostRule) (res filter.Result) { + fltID, ok := r.hostRuleIDs[hr] + if !ok { + // Shouldn't happen, since fltID is supposed to be among the filters + // added to the result. + panic(fmt.Errorf("composite: filter id %q not found", fltID)) + } + + var rule filter.RuleText + if fltID == filter.IDBlockedService { + var svcID filter.BlockedServiceID + svcID, ok = r.hostRuleSvcIDs[hr] + if !ok { + // Shouldn't happen, since svcID is supposed to be among the filters + // added to the result. + panic(fmt.Errorf("composite: service id %q not found", svcID)) + } + + rule = filter.RuleText(svcID) + } else { + rule = filter.RuleText(hr.RuleText) + } + + return &filter.ResultBlocked{ + List: fltID, + Rule: rule, + } +} diff --git a/internal/filter/internal/custom/custom.go b/internal/filter/internal/custom/custom.go deleted file mode 100644 index 1703789..0000000 --- a/internal/filter/internal/custom/custom.go +++ /dev/null @@ -1,161 +0,0 @@ -// Package custom contains the caching storage of filters made from custom -// filtering rules of clients. -package custom - -import ( - "context" - "fmt" - "log/slog" - "strings" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/golibs/stringutil" -) - -// Filters contains custom filters made from custom filtering rules of clients. -type Filters struct { - logger *slog.Logger - cache agdcache.Interface[string, *cacheItem] - errColl errcoll.Interface -} - -// cacheItem is an item of the custom filter cache. -type cacheItem struct { - updTime time.Time - ruleList *rulelist.Immutable -} - -// CacheID is a cache identifier for clients' custom filters. -const CacheID = "filters/" + string(internal.IDCustom) - -// Config is the configuration structure for the custom-filter storage. All -// fields must not be nil. -type Config struct { - // Logger is used to log the operation of the storage. - Logger *slog.Logger - - // ErrColl is used to collect errors arising during engine compilation. - ErrColl errcoll.Interface - - // CacheConf is used as the configuration for the cache. - CacheConf *agdcache.LRUConfig - - // CacheManager is used to create the cache for the storage. - CacheManager agdcache.Manager -} - -// New returns a new custom filter storage. It also adds the cache with ID -// [CacheID] to the cache manager. c must not be nil. -func New(c *Config) (f *Filters) { - cache := agdcache.NewLRU[string, *cacheItem](c.CacheConf) - c.CacheManager.Add(CacheID, cache) - - return &Filters{ - logger: c.Logger, - cache: cache, - errColl: c.ErrColl, - } -} - -// ClientConfig is the configuration for identification or construction of a -// custom filter for a client. -type ClientConfig = internal.ConfigCustom - -// Get returns the custom rule-list filter made from the client configuration. -// c must not be nil. -func (f *Filters) Get(ctx context.Context, c *ClientConfig) (rl *rulelist.Immutable) { - if !c.Enabled || len(c.Rules) == 0 { - // Technically, there could be an old filter left in the cache, but it - // will eventually be evicted, so don't do anything about it. - return nil - } - - // Report the custom filters cache lookup to prometheus so that we could - // keep track of whether the cache size is enough. - defer func() { - // TODO(a.garipov): Add a Metrics interface. - metrics.IncrementCond( - rl == nil, - metrics.FilterCustomCacheLookupsMisses, - metrics.FilterCustomCacheLookupsHits, - ) - }() - - rl = f.get(c) - if rl != nil { - return rl - } - - // TODO(a.garipov): Consider making a copy of [strings.Join] for - // [internal.RuleText]. - textLen := 0 - for _, r := range c.Rules { - textLen += len(r) + len("\n") - } - - b := &strings.Builder{} - b.Grow(textLen) - - for _, r := range c.Rules { - stringutil.WriteToBuilder(b, string(r), "\n") - } - - rl, err := rulelist.NewImmutable( - b.String(), - internal.IDCustom, - "", - // Don't use cache for users' custom filters, because resultcache - // doesn't take $client rules into account. - // - // TODO(a.garipov): Consider enabling caching if necessary. - rulelist.ResultCacheEmpty{}, - ) - if err != nil { - // In a rare situation where the custom rules are so badly formed that - // we cannot even create a filtering engine, consider that there is no - // custom filter, but signal this to the error collector. - err = fmt.Errorf("compiling custom filter for client with id %s: %w", c.ID, err) - f.errColl.Collect(ctx, err) - - return nil - } - - f.logger.DebugContext( - ctx, - "got rules for client", - "client_id", c.ID, - "num_rules", rl.RulesCount(), - ) - - f.set(c, rl) - - return rl -} - -// get returns the cached custom rule-list filter, if there is one and the -// client configuration hasn't changed since the filter was cached. -func (f *Filters) get(c *ClientConfig) (rl *rulelist.Immutable) { - item, ok := f.cache.Get(c.ID) - if !ok { - return nil - } - - if item.updTime.Before(c.UpdateTime) { - return nil - } - - return item.ruleList -} - -// set caches the custom rule-list filter. -func (f *Filters) set(c *ClientConfig, rl *rulelist.Immutable) { - f.cache.Set(c.ID, &cacheItem{ - updTime: c.UpdateTime, - ruleList: rl, - }) -} diff --git a/internal/filter/internal/custom/custom_test.go b/internal/filter/internal/custom/custom_test.go deleted file mode 100644 index 59d6849..0000000 --- a/internal/filter/internal/custom/custom_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package custom_test - -import ( - "context" - "testing" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/custom" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" - "github.com/AdguardTeam/golibs/logutil/slogutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// testClientConfID is the client configuration ID for tests. -const testClientConfID = "cli1234" - -func TestFilters_Get(t *testing.T) { - f := custom.New(&custom.Config{ - Logger: slogutil.NewDiscardLogger(), - ErrColl: agdtest.NewErrorCollector(), - CacheConf: &agdcache.LRUConfig{ - Count: 1, - }, - CacheManager: agdcache.EmptyManager{}, - }) - - c := &custom.ClientConfig{ - ID: testClientConfID, - UpdateTime: time.Now(), - Rules: []internal.RuleText{ - "||first.example", - }, - Enabled: true, - } - - ctx := context.Background() - - rl := f.Get(ctx, c) - require.NotNil(t, rl) - - // Recheck cached. - cachedRL := f.Get(ctx, c) - require.NotNil(t, cachedRL) - - assert.Same(t, rl, cachedRL) -} - -var ruleListSink *rulelist.Immutable - -func BenchmarkFilters_Get(b *testing.B) { - f := custom.New(&custom.Config{ - Logger: slogutil.NewDiscardLogger(), - ErrColl: agdtest.NewErrorCollector(), - CacheConf: &agdcache.LRUConfig{ - Count: 1, - }, - CacheManager: agdcache.EmptyManager{}, - }) - - c := &custom.ClientConfig{ - ID: testClientConfID, - UpdateTime: time.Now(), - Rules: []internal.RuleText{ - "||first.example", - "||second.example", - "||third.example", - }, - Enabled: true, - } - - ctx := context.Background() - - b.Run("cache", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for range b.N { - ruleListSink = f.Get(ctx, c) - } - }) - - b.Run("no_cache", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for range b.N { - // Update the time on each iteration to make sure that the cache is - // never used. - c.UpdateTime = c.UpdateTime.Add(1 * time.Millisecond) - ruleListSink = f.Get(ctx, c) - } - }) - - // Most recent results: - // goos: linux - // goarch: amd64 - // pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/custom - // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics - // BenchmarkFilters_Get/cache-16 5702966 186.7 ns/op 16 B/op 1 allocs/op - // BenchmarkFilters_Get/no_cache-16 61044 18373 ns/op 14488 B/op 89 allocs/op -} diff --git a/internal/filter/internal/filtertest/filtertest.go b/internal/filter/internal/filtertest/filtertest.go index 12de8c0..fe69689 100644 --- a/internal/filter/internal/filtertest/filtertest.go +++ b/internal/filter/internal/filtertest/filtertest.go @@ -7,7 +7,7 @@ import ( "net/netip" "time" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/golibs/errors" "github.com/c2h5oh/datasize" ) @@ -15,6 +15,8 @@ import ( // Common rules for tests. const ( RuleBlockStr = "|" + HostBlocked + "^" + RuleBlockForClientIPStr = "|" + HostBlockedForClientIP + "^$client=" + IPv4ClientStr + RuleBlockForClientNameStr = "|" + HostBlockedForClientName + "^$client=" + ClientName RuleSafeSearchGeneralHostStr = "|" + HostSafeSearchGeneral + "^$dnsrewrite=NOERROR;CNAME;" + HostSafeSearchGeneralRepl RuleSafeSearchGeneralIPv4Str = "|" + HostSafeSearchGeneralIPv4 + "^$dnsrewrite=NOERROR;A;" + @@ -24,7 +26,9 @@ const ( RuleSafeSearchYouTubeStr = "|" + HostSafeSearchYouTube + "^$dnsrewrite=NOERROR;CNAME;" + HostSafeSearchYouTubeRepl - RuleBlock internal.RuleText = RuleBlockStr + RuleBlock filter.RuleText = RuleBlockStr + RuleBlockForClientIP filter.RuleText = RuleBlockForClientIPStr + RuleBlockForClientName filter.RuleText = RuleBlockForClientNameStr ) // Common string representations of IP addresses. @@ -50,6 +54,8 @@ const ( HostAdultContentSub = "a.b.c." + HostAdultContent HostAdultContentRepl = "adult-content-repl.example" HostBlocked = "blocked.example" + HostBlockedForClientIP = "blocked-for-client-ip.example" + HostBlockedForClientName = "blocked-for-client-name.example" HostBlockedService1 = "service-1.example" HostDangerous = "dangerous-domain.example" HostDangerousRepl = "dangerous-domain-repl.example" @@ -66,6 +72,7 @@ const ( FQDNAdultContent = HostAdultContent + "." FQDNAdultContentRepl = HostAdultContentRepl + "." FQDNBlocked = HostBlocked + "." + FQDNBlockedForClientName = HostBlockedForClientName + "." FQDNDangerous = HostDangerous + "." FQDNDangerousRepl = HostDangerousRepl + "." FQDNNewlyRegistered = HostNewlyRegistered + "." @@ -83,9 +90,9 @@ const ( BlockedServiceID2Str = "blocked_service_2" BlockedServiceIDDoesNotExistStr = "blocked_service_none" - BlockedServiceID1 internal.BlockedServiceID = BlockedServiceID1Str - BlockedServiceID2 internal.BlockedServiceID = BlockedServiceID2Str - BlockedServiceIDDoesNotExist internal.BlockedServiceID = BlockedServiceIDDoesNotExistStr + BlockedServiceID1 filter.BlockedServiceID = BlockedServiceID1Str + BlockedServiceID2 filter.BlockedServiceID = BlockedServiceID2Str + BlockedServiceIDDoesNotExist filter.BlockedServiceID = BlockedServiceIDDoesNotExistStr ) // BlockedServiceIndex is a service-index response for tests. @@ -116,8 +123,8 @@ const ( RuleListID1Str = "rule_list_1" RuleListID2Str = "rule_list_2" - RuleListID1 internal.ID = RuleListID1Str - RuleListID2 internal.ID = RuleListID2Str + RuleListID1 filter.ID = RuleListID1Str + RuleListID2 filter.ID = RuleListID2Str ) // NewRuleListIndex returns a rule-list index containing a record for a filter @@ -137,6 +144,9 @@ const CacheTTL = 1 * time.Hour // CacheCount is the common count of cache items for filtering tests. const CacheCount = 100 +// ClientName is the common client name for tests. +const ClientName = "MyDevice1" + // FilterMaxSize is the maximum size of the downloadable rule-list for filtering // tests. const FilterMaxSize = 640 * datasize.KB diff --git a/internal/filter/internal/filtertest/hashprefix.go b/internal/filter/internal/filtertest/hashprefix.go index fba9ad5..0ee1052 100644 --- a/internal/filter/internal/filtertest/hashprefix.go +++ b/internal/filter/internal/filtertest/hashprefix.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/require" @@ -17,16 +17,16 @@ import ( // NewHashprefixFilter is like [NewHashprefixFilterWithRepl], but the // replacement host is also set in accordance with id. -func NewHashprefixFilter(tb testing.TB, id internal.ID) (f *hashprefix.Filter) { +func NewHashprefixFilter(tb testing.TB, id filter.ID) (f *hashprefix.Filter) { tb.Helper() var replHost string switch id { - case internal.IDAdultBlocking: + case filter.IDAdultBlocking: replHost = HostAdultContentRepl - case internal.IDNewRegDomains: + case filter.IDNewRegDomains: replHost = HostNewlyRegisteredRepl - case internal.IDSafeBrowsing: + case filter.IDSafeBrowsing: replHost = HostDangerousRepl default: tb.Fatalf("bad id: %q", id) @@ -39,18 +39,18 @@ func NewHashprefixFilter(tb testing.TB, id internal.ID) (f *hashprefix.Filter) { // tests. The hash data is set in accordance with id. func NewHashprefixFilterWithRepl( tb testing.TB, - id internal.ID, + id filter.ID, replHost string, ) (f *hashprefix.Filter) { tb.Helper() var data string switch id { - case internal.IDAdultBlocking: + case filter.IDAdultBlocking: data = HostAdultContent + "\n" - case internal.IDNewRegDomains: + case filter.IDNewRegDomains: data = HostNewlyRegistered + "\n" - case internal.IDSafeBrowsing: + case filter.IDSafeBrowsing: data = HostDangerous + "\n" default: tb.Fatalf("bad id: %q", id) @@ -62,17 +62,14 @@ func NewHashprefixFilterWithRepl( require.NoError(tb, err) f, err = hashprefix.NewFilter(&hashprefix.FilterConfig{ - Logger: slogutil.NewDiscardLogger(), - // TODO(a.garipov): Use [agdtest.NewCloner] when the import cycle is - // resolved. - Cloner: dnsmsg.NewCloner(dnsmsg.EmptyClonerStat{}), - CacheManager: agdcache.EmptyManager{}, - Hashes: strg, - URL: srvURL, - // TODO(a.garipov): Use [agdtest.NewErrorCollector] when the import - // cycle is resolved. - ErrColl: errColl{}, - Metrics: internal.EmptyMetrics{}, + Logger: slogutil.NewDiscardLogger(), + Cloner: agdtest.NewCloner(), + CacheManager: agdcache.EmptyManager{}, + Hashes: strg, + URL: srvURL, + ErrColl: agdtest.NewErrorCollector(), + HashPrefixMtcs: hashprefix.EmptyMetrics{}, + Metrics: filter.EmptyMetrics{}, ID: id, CachePath: cachePath, ReplacementHost: replHost, diff --git a/internal/filter/internal/filtertest/result.go b/internal/filter/internal/filtertest/result.go index c04c294..b123cb9 100644 --- a/internal/filter/internal/filtertest/result.go +++ b/internal/filter/internal/filtertest/result.go @@ -3,27 +3,26 @@ package filtertest import ( "net/netip" "testing" - "time" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // AssertEqualResult is a test helper that compares two results taking -// [internal.ResultModifiedRequest] and its difference in IDs into account. -func AssertEqualResult(tb testing.TB, want, got internal.Result) (ok bool) { +// [filter.ResultModifiedRequest] and its difference in IDs into account. +func AssertEqualResult(tb testing.TB, want, got filter.Result) (ok bool) { tb.Helper() - wantRM, ok := want.(*internal.ResultModifiedRequest) + wantRM, ok := want.(*filter.ResultModifiedRequest) if !ok { return assert.Equal(tb, want, got) } - gotRM := testutil.RequireTypeAssert[*internal.ResultModifiedRequest](tb, got) + gotRM := testutil.RequireTypeAssert[*filter.ResultModifiedRequest](tb, got) return assert.Equal(tb, wantRM.List, gotRM.List) && assert.Equal(tb, wantRM.Rule, gotRM.Rule) && @@ -56,7 +55,7 @@ func NewRequest( host string, ip netip.Addr, qt dnsmsg.RRType, -) (req *internal.Request) { +) (req *filter.Request) { tb.Helper() dnsReq := &dns.Msg{ @@ -67,22 +66,9 @@ func NewRequest( }}, } - // TODO(a.garipov): Use [agdtest.NewConstructor] when the import cycle is - // resolved. - msgs, err := dnsmsg.NewConstructor(&dnsmsg.ConstructorConfig{ - Cloner: dnsmsg.NewCloner(dnsmsg.EmptyClonerStat{}), - BlockingMode: &dnsmsg.BlockingModeNullIP{}, - StructuredErrors: &dnsmsg.StructuredDNSErrorsConfig{ - Enabled: false, - }, - FilteredResponseTTL: 10 * time.Second, - EDEEnabled: false, - }) - require.NoError(tb, err) - - return &internal.Request{ + return &filter.Request{ DNS: dnsReq, - Messages: msgs, + Messages: agdtest.NewConstructor(tb), RemoteIP: ip, ClientName: cliName, Host: host, @@ -93,7 +79,7 @@ func NewRequest( // NewARequest is like [NewRequest] but cliName is always empty, ip is always // [IPv4Client], and qt is always [dns.TypeA]. -func NewARequest(tb testing.TB, host string) (req *internal.Request) { +func NewARequest(tb testing.TB, host string) (req *filter.Request) { tb.Helper() return NewRequest(tb, "", host, IPv4Client, dns.TypeA) diff --git a/internal/filter/internal/internal.go b/internal/filter/internal/internal.go deleted file mode 100644 index 5f168c0..0000000 --- a/internal/filter/internal/internal.go +++ /dev/null @@ -1,109 +0,0 @@ -// Package internal contains common constants, types, and utilities shared by -// other subpackages of package filter/. -// -// TODO(a.garipov): Merge into package filter. -package internal - -import ( - "context" - "net/netip" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/miekg/dns" -) - -// Interface is the DNS request and response filter interface. -type Interface interface { - // FilterRequest filters a DNS request based on the information provided - // about the request. req must be valid. - FilterRequest(ctx context.Context, req *Request) (r Result, err error) - - // FilterResponse filters a DNS response based on the information provided - // about the response. resp must be valid. - FilterResponse(ctx context.Context, resp *Response) (r Result, err error) -} - -// Request contains information about a request being filtered. -type Request struct { - // DNS is the original DNS request used to create filtered responses. It - // must not be nil and must have exactly one question. - DNS *dns.Msg - - // Messages is used to create filtered responses for this request. It must - // not be nil. - Messages *dnsmsg.Constructor - - // RemoteIP is the remote IP address of the client. - RemoteIP netip.Addr - - // ClientName is the client name for rule-list filtering. - ClientName string - - // Host is the lowercased, non-FQDN version of the hostname from the - // question of the request. - Host string - - // QType is the type of question for this request. - QType dnsmsg.RRType - - // QClass is the class of question for this request. - QClass dnsmsg.Class -} - -// Response contains information about a response being filtered. -type Response struct { - // DNS is the original DNS response used to create filtered responses. It - // must not be nil and must have exactly one question. - DNS *dns.Msg - - // RemoteIP is the remote IP address of the client. - RemoteIP netip.Addr - - // ClientName is the client name for rule-list filtering. - ClientName string -} - -// Empty is an [Interface] implementation that always returns nil. -type Empty struct{} - -// type check -var _ Interface = Empty{} - -// FilterRequest implements the [Interface] interface for Empty. -func (Empty) FilterRequest(_ context.Context, _ *Request) (r Result, err error) { - return nil, nil -} - -// FilterResponse implements the [Interface] interface for Empty. -func (Empty) FilterResponse(_ context.Context, _ *Response) (r Result, err error) { - return nil, nil -} - -// DefaultResolveTimeout is the default timeout for resolving hosts for -// safe-search and safe-browsing filters. -// -// TODO(ameshkov): Consider making configurable. -const DefaultResolveTimeout = 1 * time.Second - -// RequestFilter can filter a request based on the request info. -type RequestFilter interface { - FilterRequest(ctx context.Context, req *Request) (r Result, err error) - ID() (id ID) -} - -// ConfigCustom is the configuration for identification or construction of a -// custom filter for a client. -type ConfigCustom struct { - // ID is the unique ID for this custom filter. - ID string - - // UpdateTime is the last time this configuration has been updated. - UpdateTime time.Time - - // Rules are the filtering rules for this configuration. - Rules []RuleText - - // Enabled shows whether the custom filters are applied at all. - Enabled bool -} diff --git a/internal/filter/internal/refreshable/refreshable.go b/internal/filter/internal/refreshable/refreshable.go index 34a619f..a728630 100644 --- a/internal/filter/internal/refreshable/refreshable.go +++ b/internal/filter/internal/refreshable/refreshable.go @@ -64,10 +64,10 @@ type Config struct { // New returns a new refreshable. c must not be nil. func New(c *Config) (f *Refreshable, err error) { if c.URL == nil { - return nil, fmt.Errorf("internal.NewRefreshable: nil url for refreshable with ID %q", c.ID) + return nil, fmt.Errorf("refreshable.New: nil url for refreshable with ID %q", c.ID) } else if s := c.URL.Scheme; !strings.EqualFold(s, urlutil.SchemeFile) && !urlutil.IsValidHTTPURLScheme(s) { - return nil, fmt.Errorf("internal.NewRefreshable: bad url scheme %q", s) + return nil, fmt.Errorf("refreshable.New: bad url scheme %q", s) } return &Refreshable{ diff --git a/internal/filter/internal/rulelist/cache.go b/internal/filter/internal/rulelist/cache.go new file mode 100644 index 0000000..55acc4c --- /dev/null +++ b/internal/filter/internal/rulelist/cache.go @@ -0,0 +1,100 @@ +package rulelist + +import ( + "encoding/binary" + "hash/maphash" + + "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/golibs/mathutil" + "github.com/AdguardTeam/urlfilter" +) + +// CacheKey is the cache key type for [NewCacheKey]. +type CacheKey uint64 + +// hashSeed is the seed used by all hashes to create hash keys. +var hashSeed = maphash.MakeSeed() + +// NewCacheKey produces a cache key based on the arguments using default +// algorithm. +func NewCacheKey(host string, qt dnsmsg.RRType, cl dnsmsg.Class, isAns bool) (k CacheKey) { + // Use maphash explicitly instead of using a key structure to reduce + // allocations and optimize interface conversion up the stack. + h := &maphash.Hash{} + h.SetSeed(hashSeed) + + _, _ = h.WriteString(host) + + // Save on allocations by reusing a buffer. + var buf [5]byte + binary.LittleEndian.PutUint16(buf[:2], qt) + binary.LittleEndian.PutUint16(buf[2:4], cl) + buf[4] = mathutil.BoolToNumber[byte](isAns) + + _, _ = h.Write(buf[:]) + + return CacheKey(h.Sum64()) +} + +type ( + // ResultCache is a convenient alias for cache to keep types in check. + ResultCache = agdcache.Interface[CacheKey, *CacheItem] + + // EmptyResultCache is a convenient alias for empty cache to keep types in + // check. See [filter.DNSResult]. + EmptyResultCache = agdcache.Empty[CacheKey, *CacheItem] +) + +// NewResultCache returns a new initialized cache with the given element count. +// If useCache is false, it returns a cache implementation that does nothing. +func NewResultCache(count int, useCache bool) (cache ResultCache) { + if !useCache { + return EmptyResultCache{} + } + + return agdcache.NewLRU[CacheKey, *CacheItem](&agdcache.LRUConfig{ + Count: count, + }) +} + +// NewManagedResultCache is like [NewResultCache] but it also adds a newly +// created cache to the cache manager by id. +func NewManagedResultCache( + m agdcache.Manager, + id string, + count int, + useCache bool, +) (cache ResultCache) { + cache = NewResultCache(count, useCache) + m.Add(id, cache) + + return cache +} + +// CacheItem is an item stored in a [ResultCache]. +type CacheItem struct { + // res is the DNS filtering result. + res *urlfilter.DNSResult + + // host is the cached normalized hostname for later cache key collision + // checks. + host string +} + +// itemFromCache retrieves a cache item for the given key. host is used to +// detect key collisions. If there is a key collision, it returns nil and +// false. +func itemFromCache(cache ResultCache, key CacheKey, host string) (item *CacheItem, ok bool) { + item, ok = cache.Get(key) + if !ok { + return nil, false + } + + if item.host != host { + // Cache collision. + return nil, false + } + + return item, true +} diff --git a/internal/filter/internal/rulelist/dnsrewrite.go b/internal/filter/internal/rulelist/dnsrewrite.go index 933cbf7..6b5acfe 100644 --- a/internal/filter/internal/rulelist/dnsrewrite.go +++ b/internal/filter/internal/rulelist/dnsrewrite.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" @@ -15,10 +15,10 @@ import ( // ProcessDNSRewrites processes $dnsrewrite rules dnsr and creates a filtering // result, if id allows it. res.List, if any, is set to id. func ProcessDNSRewrites( - req *internal.Request, + req *filter.Request, dnsr []*rules.NetworkRule, - id internal.ID, -) (res internal.Result) { + id filter.ID, +) (res filter.Result) { if len(dnsr) == 0 { return nil } @@ -35,7 +35,7 @@ func ProcessDNSRewrites( modReq := dnsmsg.Clone(req.DNS) modReq.Question[0].Name = dns.Fqdn(resCanonName) - return &internal.ResultModifiedRequest{ + return &filter.ResultModifiedRequest{ Msg: modReq, List: id, Rule: dnsRewriteResult.ResRuleText, @@ -48,7 +48,7 @@ func ProcessDNSRewrites( // defined statically. resp := req.Messages.NewBlockedRespRCode(req.DNS, dnsmsg.RCode(dnsRewriteResult.RCode)) - return &internal.ResultModifiedResponse{ + return &filter.ResultModifiedResponse{ Msg: resp, List: id, Rule: dnsRewriteResult.ResRuleText, @@ -60,7 +60,7 @@ func ProcessDNSRewrites( return nil } - return &internal.ResultModifiedResponse{ + return &filter.ResultModifiedResponse{ Msg: resp, List: id, } @@ -70,7 +70,7 @@ func ProcessDNSRewrites( type dnsRewriteResult struct { Response dnsRewriteResultResponse CanonName string - ResRuleText internal.RuleText + ResRuleText filter.RuleText Rules []*rules.NetworkRule RCode rules.RCode } @@ -91,7 +91,7 @@ func processDNSRewriteRules(dnsr []*rules.NetworkRule) (res *dnsRewriteResult) { if dr.NewCNAME != "" { // NewCNAME rules have a higher priority than other rules. return &dnsRewriteResult{ - ResRuleText: internal.RuleText(rule.RuleText), + ResRuleText: filter.RuleText(rule.RuleText), CanonName: dr.NewCNAME, } } @@ -105,7 +105,7 @@ func processDNSRewriteRules(dnsr []*rules.NetworkRule) (res *dnsRewriteResult) { // RcodeRefused and other such codes have higher priority. Return // immediately. return &dnsRewriteResult{ - ResRuleText: internal.RuleText(rule.RuleText), + ResRuleText: filter.RuleText(rule.RuleText), RCode: dr.RCode, } } @@ -117,7 +117,7 @@ func processDNSRewriteRules(dnsr []*rules.NetworkRule) (res *dnsRewriteResult) { // filterDNSRewrite handles dnsrewrite filters. It constructs a DNS response // and returns it. dnsrr.RCode should be [dns.RcodeSuccess] and contain a // non-empty dnsrr.Response. -func filterDNSRewrite(req *internal.Request, dnsrr *dnsRewriteResult) (resp *dns.Msg, err error) { +func filterDNSRewrite(req *filter.Request, dnsrr *dnsRewriteResult) (resp *dns.Msg, err error) { if dnsrr.Response == nil { return nil, errors.Error("no dns rewrite rule responses") } @@ -149,7 +149,7 @@ func filterDNSRewrite(req *internal.Request, dnsrr *dnsRewriteResult) (resp *dns // filterDNSRewriteResponse handles a single DNS rewrite response entry. // It returns the properly constructed answer resource record. func filterDNSRewriteResponse( - req *internal.Request, + req *filter.Request, rr rules.RRType, v rules.RRValue, ) (ans dns.RR, err error) { @@ -174,7 +174,7 @@ func filterDNSRewriteResponse( } // newAnswerSRV returns a new resource record created from DNSSRV rules value. -func newAnswerSRV(req *internal.Request, v rules.RRValue, rr rules.RRType) (ans dns.RR, err error) { +func newAnswerSRV(req *filter.Request, v rules.RRValue, rr rules.RRType) (ans dns.RR, err error) { srv, ok := v.(*rules.DNSSRV) if !ok { return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSRV", rr, v) @@ -184,11 +184,7 @@ func newAnswerSRV(req *internal.Request, v rules.RRValue, rr rules.RRType) (ans } // newAnsFromSVCB returns a new resource record created from DNSSVCB rules value. -func newAnsFromSVCB( - req *internal.Request, - v rules.RRValue, - rr rules.RRType, -) (ans dns.RR, err error) { +func newAnsFromSVCB(req *filter.Request, v rules.RRValue, rr rules.RRType) (ans dns.RR, err error) { svcb, ok := v.(*rules.DNSSVCB) if !ok { return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSSVCB", rr, v) @@ -203,7 +199,7 @@ func newAnsFromSVCB( // newAnsFromString returns a new resource record created from string value. func newAnsFromString( - req *internal.Request, + req *filter.Request, v rules.RRValue, rr rules.RRType, ) (ans dns.RR, err error) { @@ -221,7 +217,7 @@ func newAnsFromString( // newAnsFromIP returns a new resource record with an IP address. ip must be an // IPv4 or IPv6 address. -func newAnsFromIP(req *internal.Request, v rules.RRValue, rr rules.RRType) (ans dns.RR, err error) { +func newAnsFromIP(req *filter.Request, v rules.RRValue, rr rules.RRType) (ans dns.RR, err error) { ip, ok := v.(netip.Addr) if !ok { return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v) @@ -237,7 +233,7 @@ func newAnsFromIP(req *internal.Request, v rules.RRValue, rr rules.RRType) (ans } // newAnswerMX returns a new resource record created from DNSMX rules value. -func newAnswerMX(req *internal.Request, v, rr rules.RRValue) (ans dns.RR, err error) { +func newAnswerMX(req *filter.Request, v, rr rules.RRValue) (ans dns.RR, err error) { mx, ok := v.(*rules.DNSMX) if !ok { return nil, fmt.Errorf("value for rr type %d has type %T, not *rules.DNSMX", rr, v) diff --git a/internal/filter/internal/rulelist/immutable.go b/internal/filter/internal/rulelist/immutable.go index 38644bf..f8c3f1b 100644 --- a/internal/filter/internal/rulelist/immutable.go +++ b/internal/filter/internal/rulelist/immutable.go @@ -1,8 +1,6 @@ package rulelist -import ( - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" -) +import "github.com/AdguardTeam/AdGuardDNS/internal/filter" // Immutable is a rule-list filter that doesn't refresh or change. It is used // for users' custom rule-lists as well as in service blocking. @@ -17,23 +15,18 @@ import ( type Immutable struct { // TODO(a.garipov): Find ways to embed it in a way that shows the methods, // doesn't result in double dereferences, and doesn't cause naming issues. - *filter + *baseFilter } // NewImmutable returns a new immutable DNS request and response filter using -// the provided rule text and ID. +// the provided rule text and IDs. func NewImmutable( text string, - id internal.ID, - svcID internal.BlockedServiceID, + id filter.ID, + svcID filter.BlockedServiceID, cache ResultCache, -) (f *Immutable, err error) { - f = &Immutable{} - f.filter, err = newFilter(text, id, svcID, cache) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err +) (f *Immutable) { + return &Immutable{ + baseFilter: newBaseFilter(text, id, svcID, cache), } - - return f, nil } diff --git a/internal/filter/internal/rulelist/refreshable.go b/internal/filter/internal/rulelist/refreshable.go index 9f48500..c7768d6 100644 --- a/internal/filter/internal/rulelist/refreshable.go +++ b/internal/filter/internal/rulelist/refreshable.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/AdguardTeam/urlfilter" @@ -23,7 +23,7 @@ import ( // for multiple rule lists and using it to optimize the filtering using default // filtering groups. type Refreshable struct { - *filter + *baseFilter logger *slog.Logger @@ -42,8 +42,9 @@ type Refreshable struct { // HTTP(S) URL. The initial refresh should be called explicitly if necessary. func NewRefreshable(c *refreshable.Config, cache ResultCache) (f *Refreshable, err error) { f = &Refreshable{ - logger: c.Logger, - mu: &sync.RWMutex{}, + baseFilter: newBaseFilter("", c.ID, "", cache), + logger: c.Logger, + mu: &sync.RWMutex{}, } if strings.EqualFold(c.URL.Scheme, urlutil.SchemeFile) { @@ -63,35 +64,23 @@ func NewRefreshable(c *refreshable.Config, cache ResultCache) (f *Refreshable, e return nil, fmt.Errorf("creating refreshable: %w", err) } - f.filter, err = newFilter("", c.ID, "", cache) - if err != nil { - // Should never happen, since text is empty. - panic(fmt.Errorf("unexpected filter error: %w", err)) - } - return f, nil } // NewFromString returns a new DNS request and response filter using the -// provided rule text and ID. +// provided rule text and IDs. // -// TODO(a.garipov): Only used in tests. Consider removing later. +// TODO(a.garipov): Only used in tests. Consider removing later. func NewFromString( text string, - id internal.ID, - svcID internal.BlockedServiceID, + id filter.ID, + svcID filter.BlockedServiceID, cache ResultCache, -) (f *Refreshable, err error) { - filter, err := newFilter(text, id, svcID, cache) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err - } - +) (f *Refreshable) { return &Refreshable{ - mu: &sync.RWMutex{}, - filter: filter, - }, nil + mu: &sync.RWMutex{}, + baseFilter: newBaseFilter(text, id, svcID, cache), + } } // DNSResult returns the result of applying the urlfilter DNS filtering engine. @@ -106,7 +95,7 @@ func (f *Refreshable) DNSResult( f.mu.RLock() defer f.mu.RUnlock() - return f.filter.DNSResult(clientIP, clientName, host, rrType, isAns) + return f.baseFilter.DNSResult(clientIP, clientName, host, rrType, isAns) } // Refresh reloads the rule list data. If acceptStale is true, do not try to @@ -121,7 +110,6 @@ func (f *Refreshable) Refresh(ctx context.Context, acceptStale bool) (err error) // TODO(a.garipov): Add filterlist.BytesRuleList. strList := &filterlist.StringRuleList{ - ID: f.urlFilterID, RulesText: text, IgnoreCosmetic: true, } @@ -148,5 +136,5 @@ func (f *Refreshable) RulesCount() (n int) { f.mu.RLock() defer f.mu.RUnlock() - return f.filter.RulesCount() + return f.baseFilter.RulesCount() } diff --git a/internal/filter/internal/rulelist/refreshable_test.go b/internal/filter/internal/rulelist/refreshable_test.go index d871dfa..4f80c83 100644 --- a/internal/filter/internal/rulelist/refreshable_test.go +++ b/internal/filter/internal/rulelist/refreshable_test.go @@ -5,7 +5,7 @@ import ( "net/netip" "testing" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" @@ -23,27 +23,20 @@ const testReqHost = "blocked.example" var testRemoteIP = netip.MustParseAddr("1.2.3.4") // testFltListID is the common filter list IDs for tests. -const testFltListID internal.ID = "fl1" +const testFltListID filter.ID = "fl1" // testBlockRule is the common blocking rule for tests. const testBlockRule = "||" + testReqHost + "\n" func TestRefreshable_RulesCount(t *testing.T) { - rl, err := rulelist.NewFromString( - testBlockRule, - testFltListID, - "", - rulelist.ResultCacheEmpty{}, - ) - require.NoError(t, err) + rl := rulelist.NewFromString(testBlockRule, testFltListID, "", rulelist.EmptyResultCache{}) assert.Equal(t, 1, rl.RulesCount()) } func TestRefreshable_DNSResult_cache(t *testing.T) { cache := rulelist.NewResultCache(filtertest.CacheCount, true) - rl, err := rulelist.NewFromString(testBlockRule, testFltListID, "", cache) - require.NoError(t, err) + rl := rulelist.NewFromString(testBlockRule, testFltListID, "", cache) const qt = dns.TypeA @@ -71,14 +64,8 @@ func TestRefreshable_DNSResult_cache(t *testing.T) { } func TestRefreshable_ID(t *testing.T) { - const svcID = internal.BlockedServiceID("test_service") - rl, err := rulelist.NewFromString( - testBlockRule, - testFltListID, - svcID, - rulelist.ResultCacheEmpty{}, - ) - require.NoError(t, err) + const svcID = filter.BlockedServiceID("test_service") + rl := rulelist.NewFromString(testBlockRule, testFltListID, svcID, rulelist.EmptyResultCache{}) gotID, gotSvcID := rl.ID() assert.Equal(t, testFltListID, gotID) diff --git a/internal/filter/internal/rulelist/result.go b/internal/filter/internal/rulelist/result.go deleted file mode 100644 index b1ccf1b..0000000 --- a/internal/filter/internal/rulelist/result.go +++ /dev/null @@ -1,94 +0,0 @@ -package rulelist - -import ( - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" - "github.com/AdguardTeam/urlfilter" - "github.com/AdguardTeam/urlfilter/rules" - "github.com/miekg/dns" -) - -// URLFilterResult is an entity simplifying the collection and compilation of -// urlfilter results. -type URLFilterResult struct { - networkRules []*rules.NetworkRule - hostRules4 []*rules.HostRule - hostRules6 []*rules.HostRule -} - -// Add appends the rules from dr to the slices within r. If dr is nil, Add does -// nothing. -func (r *URLFilterResult) Add(dr *urlfilter.DNSResult) { - if dr != nil { - r.networkRules = append(r.networkRules, dr.NetworkRules...) - r.hostRules4 = append(r.hostRules4, dr.HostRulesV4...) - r.hostRules6 = append(r.hostRules6, dr.HostRulesV6...) - } -} - -// ToInternal converts a result of using several urlfilter rulelists into an -// internal.Result. -func (r *URLFilterResult) ToInternal(m IDMapper, rrType dnsmsg.RRType) (res internal.Result) { - if nr := rules.GetDNSBasicRule(r.networkRules); nr != nil { - return ruleDataToResult(m, nr.FilterListID, nr.RuleText, nr.Whitelist) - } - - return r.hostsRulesToResult(m, rrType) -} - -// IDMapper maps an internal urlfilter ID to AdGuard DNS IDs. -type IDMapper interface { - Map(ufID int) (id internal.ID, svcID internal.BlockedServiceID) -} - -// hostsRulesToResult converts /etc/hosts-style rules into a filtering result. -func (r *URLFilterResult) hostsRulesToResult(m IDMapper, rrType dnsmsg.RRType) (res internal.Result) { - if len(r.hostRules4) == 0 && len(r.hostRules6) == 0 { - return nil - } - - // Only use the first matched rule, since we currently don't care about the - // IP addresses in the rule. If the request is neither an A one nor an AAAA - // one, or if there are no matching rules of the requested type, then use - // whatever rule isn't empty. - // - // See also AGDNS-591. - var resHostRule *rules.HostRule - if rrType == dns.TypeA && len(r.hostRules4) > 0 { - resHostRule = r.hostRules4[0] - } else if rrType == dns.TypeAAAA && len(r.hostRules6) > 0 { - resHostRule = r.hostRules6[0] - } else { - if len(r.hostRules4) > 0 { - resHostRule = r.hostRules4[0] - } else { - resHostRule = r.hostRules6[0] - } - } - - return ruleDataToResult(m, resHostRule.FilterListID, resHostRule.RuleText, false) -} - -// ruleDataToResult converts a urlfilter rule data into a filtering result. -func ruleDataToResult(m IDMapper, ufID int, ruleText string, isAllowlist bool) (r internal.Result) { - fltID, svcID := m.Map(ufID) - - var rule internal.RuleText - if fltID == internal.IDBlockedService { - rule = internal.RuleText(svcID) - } else { - rule = internal.RuleText(ruleText) - } - - if isAllowlist { - return &internal.ResultAllowed{ - List: fltID, - Rule: rule, - } - } - - return &internal.ResultBlocked{ - List: fltID, - Rule: rule, - } -} diff --git a/internal/filter/internal/rulelist/rulelist.go b/internal/filter/internal/rulelist/rulelist.go index 0849f5d..009fa97 100644 --- a/internal/filter/internal/rulelist/rulelist.go +++ b/internal/filter/internal/rulelist/rulelist.go @@ -4,99 +4,18 @@ package rulelist import ( "fmt" - "math/rand" "net/netip" - "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter/filterlist" "github.com/miekg/dns" ) -// newURLFilterID returns a new random ID for the urlfilter DNS engine to use. -func newURLFilterID() (id int) { - // #nosec G404 -- Do not use cryptographically random ID generation, since - // these are only used in ../composite/Filter.mustRuleListDataByURLFilterID - // and are not used in any security-sensitive context. - // - // Despite the fact that the type of integer filter list IDs in module - // urlfilter is int, the module actually assumes that the ID is a - // non-negative integer, or at least not a largely negative one. Otherwise, - // some of its low-level optimizations seem to break. - return int(rand.Int31()) -} - -type ( - // ResultCache is a convenient alias for cache to keep types in check. - ResultCache = agdcache.Interface[internal.CacheKey, *CacheItem] - - // ResultCacheEmpty is a convenient alias for empty cache to keep types in - // check. See [filter.DNSResult]. - ResultCacheEmpty = agdcache.Empty[internal.CacheKey, *CacheItem] -) - -// NewResultCache returns a new initialized cache with the given element count. -// If useCache is false, it returns a cache implementation that does nothing. -func NewResultCache(count int, useCache bool) (cache ResultCache) { - if !useCache { - return ResultCacheEmpty{} - } - - return agdcache.NewLRU[internal.CacheKey, *CacheItem](&agdcache.LRUConfig{ - Count: count, - }) -} - -// NewManagedResultCache is like [NewResultCache] but it also adds a newly -// created cache to the cache manager by id. -func NewManagedResultCache( - m agdcache.Manager, - id string, - count int, - useCache bool, -) (cache ResultCache) { - cache = NewResultCache(count, useCache) - m.Add(id, cache) - - return cache -} - -// CacheItem represents an item that we will store in the cache. -type CacheItem struct { - // res is the DNS filtering result. - res *urlfilter.DNSResult - - // host is the cached normalized hostname for later cache key collision - // checks. - host string -} - -// itemFromCache retrieves a cache item for the given key. host is used to -// detect key collisions. If there is a key collision, it returns nil and -// false. -func itemFromCache( - cache ResultCache, - key internal.CacheKey, - host string, -) (item *CacheItem, ok bool) { - item, ok = cache.Get(key) - if !ok { - return nil, false - } - - if item.host != host { - // Cache collision. - return nil, false - } - - return item, true -} - -// filter is the basic rule-list filter that doesn't refresh or change in any +// baseFilter is the vase rule-list filter that doesn't refresh or change in any // other way. -type filter struct { +type baseFilter struct { // engine is the DNS filtering engine. // // NOTE: Do not save the [filterlist.RuleList] used to create the engine to @@ -110,53 +29,53 @@ type filter struct { cache ResultCache // id is the filter list ID, if any. - id internal.ID + id filter.ID // svcID is the additional identifier for blocked service lists. If id is - svcID internal.BlockedServiceID - - // urlFilterID is the synthetic integer identifier for the urlfilter engine. - // - // TODO(a.garipov): Change the type to a string in module urlfilter and - // remove this crutch. - urlFilterID int + svcID filter.BlockedServiceID } -// newFilter returns a new basic DNS request and response filter using the -// provided rule text and ID. -func newFilter( +// newBaseFilter returns a new base DNS request and response filter using the +// provided rule text and IDs. +func newBaseFilter( text string, - id internal.ID, - svcID internal.BlockedServiceID, - cache agdcache.Interface[internal.CacheKey, *CacheItem], -) (f *filter, err error) { - f = &filter{ - cache: cache, - id: id, - svcID: svcID, - urlFilterID: newURLFilterID(), + id filter.ID, + svcID filter.BlockedServiceID, + cache ResultCache, +) (f *baseFilter) { + f = &baseFilter{ + cache: cache, + id: id, + svcID: svcID, } // TODO(a.garipov): Add filterlist.BytesRuleList. strList := &filterlist.StringRuleList{ - ID: f.urlFilterID, RulesText: text, IgnoreCosmetic: true, } s, err := filterlist.NewRuleStorage([]filterlist.RuleList{strList}) if err != nil { - return nil, fmt.Errorf("creating rule storage: %w", err) + // Should never happen, there is only one filter list, and the only + // error that is currently returned from [filterlist.NewRuleStorage] is + // about duplicated IDs. + panic(fmt.Errorf( + "rulelist: compiling storage for filter id %q and svc id %q: %w", + id, + svcID, + err, + )) } f.engine = urlfilter.NewDNSEngine(s) - return f, nil + return f } // DNSResult returns the result of applying the urlfilter DNS filtering engine. // If the request is not filtered, DNSResult returns nil. -func (f *filter) DNSResult( +func (f *baseFilter) DNSResult( clientIP netip.Addr, clientName string, host string, @@ -164,15 +83,15 @@ func (f *filter) DNSResult( isAns bool, ) (res *urlfilter.DNSResult) { var ok bool - var cacheKey internal.CacheKey + var cacheKey CacheKey var item *CacheItem // Don't waste resources on computing the cache key if the cache is not // enabled. - _, emptyCache := f.cache.(ResultCacheEmpty) + _, emptyCache := f.cache.(EmptyResultCache) if !emptyCache { // TODO(a.garipov): Add real class here. - cacheKey = internal.NewCacheKey(host, rrType, dns.ClassINET, isAns) + cacheKey = NewCacheKey(host, rrType, dns.ClassINET, isAns) item, ok = itemFromCache(f.cache, cacheKey, host) if ok { return item.res @@ -202,16 +121,11 @@ func (f *filter) DNSResult( // ID returns the filter list ID of this rule list filter, as well as the ID of // the blocked service, if any. -func (f *filter) ID() (id internal.ID, svcID internal.BlockedServiceID) { +func (f *baseFilter) ID() (id filter.ID, svcID filter.BlockedServiceID) { return f.id, f.svcID } // RulesCount returns the number of rules in the filter's engine. -func (f *filter) RulesCount() (n int) { +func (f *baseFilter) RulesCount() (n int) { return f.engine.RulesCount } - -// URLFilterID returns the synthetic ID used for the urlfilter module. -func (f *filter) URLFilterID() (n int) { - return f.urlFilterID -} diff --git a/internal/filter/internal/safesearch/safesearch.go b/internal/filter/internal/safesearch/safesearch.go index ade529a..7d211be 100644 --- a/internal/filter/internal/safesearch/safesearch.go +++ b/internal/filter/internal/safesearch/safesearch.go @@ -7,7 +7,8 @@ import ( "fmt" "time" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/miekg/dns" @@ -45,14 +46,14 @@ func New(c *Config, cache rulelist.ResultCache) (f *Filter, err error) { } // type check -var _ internal.RequestFilter = (*Filter)(nil) +var _ composite.RequestFilter = (*Filter)(nil) -// FilterRequest implements the [internal.RequestFilter] interface for *Filter. +// FilterRequest implements the [composite.RequestFilter] interface for *Filter. // It modifies the response if host matches f. func (f *Filter) FilterRequest( ctx context.Context, - req *internal.Request, -) (r internal.Result, err error) { + req *filter.Request, +) (r filter.Result, err error) { qt := req.QType switch qt { case dns.TypeA, dns.TypeAAAA, dns.TypeHTTPS: @@ -73,28 +74,21 @@ func (f *Filter) FilterRequest( } // replaceRule replaces the r.Rule with host if r is not nil. r must be nil, -// [*internal.ResultModifiedRequest], or [*internal.ResultModifiedResponse]. -func replaceRule(r internal.Result, host string) { - rule := internal.RuleText(host) +// [*filter.ResultModifiedRequest], or [*filter.ResultModifiedResponse]. +func replaceRule(r filter.Result, host string) { + rule := filter.RuleText(host) switch r := r.(type) { case nil: // Do nothing. - case *internal.ResultModifiedRequest: + case *filter.ResultModifiedRequest: r.Rule = rule - case *internal.ResultModifiedResponse: + case *filter.ResultModifiedResponse: r.Rule = rule default: panic(fmt.Errorf("safesearch: unexpected type for result: %T(%[1]v)", r)) } } -// ID implements the [internal.RequestFilter] interface for *Filter. -func (f *Filter) ID() (id internal.ID) { - id, _ = f.flt.ID() - - return id -} - // Refresh reloads the rule list data. If acceptStale is true, and the cache // file exists, the data is read from there regardless of its staleness. func (f *Filter) Refresh(ctx context.Context, acceptStale bool) (err error) { diff --git a/internal/filter/internal/safesearch/safesearch_test.go b/internal/filter/internal/safesearch/safesearch_test.go index eabafb2..a341a1c 100644 --- a/internal/filter/internal/safesearch/safesearch_test.go +++ b/internal/filter/internal/safesearch/safesearch_test.go @@ -10,7 +10,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" @@ -50,7 +50,7 @@ func TestFilter(t *testing.T) { &safesearch.Config{ Refreshable: &refreshable.Config{ Logger: slogutil.NewDiscardLogger(), - ID: internal.IDGeneralSafeSearch, + ID: filter.IDGeneralSafeSearch, URL: srvURL, CachePath: cachePath, Staleness: filtertest.Staleness, @@ -100,10 +100,10 @@ func TestFilter(t *testing.T) { res, err := f.FilterRequest(ctx, req) require.NoError(t, err) - rm := testutil.RequireTypeAssert[*internal.ResultModifiedResponse](t, res) + rm := testutil.RequireTypeAssert[*filter.ResultModifiedResponse](t, res) require.Len(t, rm.Msg.Answer, 1) - assert.Equal(t, rm.Rule, internal.RuleText(testEngineWithIP)) + assert.Equal(t, rm.Rule, filter.RuleText(testEngineWithIP)) a := testutil.RequireTypeAssert[*dns.A](t, rm.Msg.Answer[0]) assert.Equal(t, net.IP(testIPOfEngineWithIP.AsSlice()), a.A) @@ -111,7 +111,7 @@ func TestFilter(t *testing.T) { t.Run("cached", func(t *testing.T) { newReq := newReq(t, testEngineWithIP, dns.TypeA) - var cachedRes internal.Result + var cachedRes filter.Result cachedRes, err = f.FilterRequest(ctx, newReq) require.NoError(t, err) @@ -119,7 +119,7 @@ func TestFilter(t *testing.T) { // result of a safe search is always cloned. But assert that the // non-clonable fields are equal and that the message has reply // fields set properly. - cachedMR := testutil.RequireTypeAssert[*internal.ResultModifiedResponse](t, cachedRes) + cachedMR := testutil.RequireTypeAssert[*filter.ResultModifiedResponse](t, cachedRes) assert.NotSame(t, cachedMR, rm) assert.Equal(t, cachedMR.Msg.Id, newReq.DNS.Id) assert.Equal(t, cachedMR.List, rm.List) @@ -133,12 +133,12 @@ func TestFilter(t *testing.T) { res, err := f.FilterRequest(ctx, req) require.NoError(t, err) - rm := testutil.RequireTypeAssert[*internal.ResultModifiedRequest](t, res) + rm := testutil.RequireTypeAssert[*filter.ResultModifiedRequest](t, res) require.NotNil(t, rm.Msg) require.Len(t, rm.Msg.Question, 1) assert.False(t, rm.Msg.Response) - assert.Equal(t, rm.Rule, internal.RuleText(testEngineWithDomain)) + assert.Equal(t, rm.Rule, filter.RuleText(testEngineWithDomain)) q := rm.Msg.Question[0] assert.Equal(t, dns.TypeA, q.Qtype) @@ -151,12 +151,12 @@ func TestFilter(t *testing.T) { res, err := f.FilterRequest(ctx, req) require.NoError(t, err) - rm := testutil.RequireTypeAssert[*internal.ResultModifiedRequest](t, res) + rm := testutil.RequireTypeAssert[*filter.ResultModifiedRequest](t, res) require.NotNil(t, rm.Msg) require.Len(t, rm.Msg.Question, 1) assert.False(t, rm.Msg.Response) - assert.Equal(t, rm.Rule, internal.RuleText(testEngineWithDomain)) + assert.Equal(t, rm.Rule, filter.RuleText(testEngineWithDomain)) q := rm.Msg.Question[0] assert.Equal(t, dns.TypeHTTPS, q.Qtype) @@ -166,10 +166,10 @@ func TestFilter(t *testing.T) { // newReq is a test helper that returns the filtering request with the given // data. -func newReq(tb testing.TB, host string, qt dnsmsg.RRType) (req *internal.Request) { +func newReq(tb testing.TB, host string, qt dnsmsg.RRType) (req *filter.Request) { tb.Helper() - return &internal.Request{ + return &filter.Request{ DNS: dnsservertest.NewReq(host, qt, dns.ClassINET), Messages: agdtest.NewConstructor(tb), Host: host, diff --git a/internal/filter/internal/serviceblock/index.go b/internal/filter/internal/serviceblock/index.go index c6b8a69..040964a 100644 --- a/internal/filter/internal/serviceblock/index.go +++ b/internal/filter/internal/serviceblock/index.go @@ -9,7 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/golibs/errors" ) @@ -38,7 +38,7 @@ func (r *indexResp) toInternal( errs := make([]error, len(r.BlockedServices)) for i, svc := range r.BlockedServices { var ( - svcID internal.BlockedServiceID + svcID filter.BlockedServiceID rl *rulelist.Immutable ) @@ -71,7 +71,7 @@ type indexRespService struct { const cachePrefix = "filters" // toInternal converts the service from the index to a rule-list filter. It -// also adds the cache with ID "[internal.IDBlockedService]/[svc.ID]" to +// also adds the cache with ID "[filter.IDBlockedService]/[svc.ID]" to // the cache manager. func (svc *indexRespService) toInternal( ctx context.Context, @@ -80,8 +80,8 @@ func (svc *indexRespService) toInternal( cacheManager agdcache.Manager, cacheCount int, useCache bool, -) (svcID internal.BlockedServiceID, rl *rulelist.Immutable, err error) { - svcID, err = internal.NewBlockedServiceID(svc.ID) +) (svcID filter.BlockedServiceID, rl *rulelist.Immutable, err error) { + svcID, err = filter.NewBlockedServiceID(svc.ID) if err != nil { return "", nil, fmt.Errorf("validating id: %w", err) } @@ -91,17 +91,9 @@ func (svc *indexRespService) toInternal( logger.WarnContext(ctx, "service has no rules", "svc_id", svcID) } - fltIDStr := path.Join(cachePrefix, string(internal.IDBlockedService), string(svcID)) + fltIDStr := path.Join(cachePrefix, string(filter.IDBlockedService), string(svcID)) cache := rulelist.NewManagedResultCache(cacheManager, fltIDStr, cacheCount, useCache) - rl, err = rulelist.NewImmutable( - strings.Join(svc.Rules, "\n"), - internal.IDBlockedService, - svcID, - cache, - ) - if err != nil { - return "", nil, fmt.Errorf("compiling %s: %w", svc.ID, err) - } + rl = rulelist.NewImmutable(strings.Join(svc.Rules, "\n"), filter.IDBlockedService, svcID, cache) logger.InfoContext(ctx, "converted service", "svc_id", svcID, "num_rules", rl.RulesCount()) diff --git a/internal/filter/internal/serviceblock/serviceblock.go b/internal/filter/internal/serviceblock/serviceblock.go index d2d5001..c99b760 100644 --- a/internal/filter/internal/serviceblock/serviceblock.go +++ b/internal/filter/internal/serviceblock/serviceblock.go @@ -14,7 +14,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" ) @@ -30,11 +30,11 @@ type Filter struct { services serviceRuleLists errColl errcoll.Interface - metrics internal.Metrics + metrics filter.Metrics } // serviceRuleLists is convenient alias for an ID to filter mapping. -type serviceRuleLists = map[internal.BlockedServiceID]*rulelist.Immutable +type serviceRuleLists = map[filter.BlockedServiceID]*rulelist.Immutable // Config is the configuration for the service-blocking filter. type Config struct { @@ -48,7 +48,7 @@ type Config struct { // Metrics are the metrics for the service-blocking filter. It must not be // nil. - Metrics internal.Metrics + Metrics filter.Metrics } // New returns a fully initialized service blocker. c must not be nil and must @@ -73,7 +73,7 @@ func New(c *Config) (f *Filter, err error) { // The order of the elements in rls is undefined. func (f *Filter) RuleLists( ctx context.Context, - ids []internal.BlockedServiceID, + ids []filter.BlockedServiceID, ) (rls []*rulelist.Immutable) { if len(ids) == 0 { return nil @@ -105,7 +105,7 @@ func (f *Filter) Refresh( var count int defer func() { // TODO(a.garipov): Consider using [agdtime.Clock]. - f.metrics.SetFilterStatus(ctx, string(internal.IDBlockedService), time.Now(), count, err) + f.metrics.SetFilterStatus(ctx, string(filter.IDBlockedService), time.Now(), count, err) }() resp, err := f.loadIndex(ctx, acceptStale) diff --git a/internal/filter/internal/serviceblock/serviceblock_test.go b/internal/filter/internal/serviceblock/serviceblock_test.go index 3ed5f46..e174b99 100644 --- a/internal/filter/internal/serviceblock/serviceblock_test.go +++ b/internal/filter/internal/serviceblock/serviceblock_test.go @@ -8,7 +8,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" - "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/serviceblock" @@ -31,7 +30,7 @@ func TestFilter(t *testing.T) { Refreshable: &refreshable.Config{ Logger: slogutil.NewDiscardLogger(), URL: srvURL, - ID: internal.IDBlockedService, + ID: filter.IDBlockedService, CachePath: cachePath, Staleness: filtertest.Staleness, Timeout: filtertest.Timeout, @@ -49,23 +48,23 @@ func TestFilter(t *testing.T) { testutil.RequireReceive(t, reqCh, filtertest.Timeout) - rls := f.RuleLists(ctx, []internal.BlockedServiceID{ + rls := f.RuleLists(ctx, []filter.BlockedServiceID{ filtertest.BlockedServiceID1, filtertest.BlockedServiceID2, filtertest.BlockedServiceIDDoesNotExist, }) require.Len(t, rls, 2) - wantSvcIDs := []internal.BlockedServiceID{ + wantSvcIDs := []filter.BlockedServiceID{ filtertest.BlockedServiceID1, filtertest.BlockedServiceID2, } - gotFltIDs := make([]internal.ID, 2) - gotSvcIDs := make([]internal.BlockedServiceID, 2) + gotFltIDs := make([]filter.ID, 2) + gotSvcIDs := make([]filter.BlockedServiceID, 2) gotFltIDs[0], gotSvcIDs[0] = rls[0].ID() gotFltIDs[1], gotSvcIDs[1] = rls[1].ID() - assert.Equal(t, internal.IDBlockedService, gotFltIDs[0]) - assert.Equal(t, internal.IDBlockedService, gotFltIDs[1]) + assert.Equal(t, filter.IDBlockedService, gotFltIDs[0]) + assert.Equal(t, filter.IDBlockedService, gotFltIDs[1]) assert.ElementsMatch(t, wantSvcIDs, gotSvcIDs) } diff --git a/internal/filter/internal/metrics.go b/internal/filter/metrics.go similarity index 85% rename from internal/filter/internal/metrics.go rename to internal/filter/metrics.go index 130ceb5..f75353e 100644 --- a/internal/filter/internal/metrics.go +++ b/internal/filter/metrics.go @@ -1,10 +1,13 @@ -package internal +package filter import ( "context" "time" ) +// TODO(a.garipov): Consider re-adding some metrics for custom filters after +// AGDNS-1519. + // Metrics is the interface for metrics of filters. type Metrics interface { // SetFilterStatus sets the status of a filter by its id. If err is not diff --git a/internal/filter/internal/result.go b/internal/filter/result.go similarity index 99% rename from internal/filter/internal/result.go rename to internal/filter/result.go index d926a2c..1d5fcff 100644 --- a/internal/filter/internal/result.go +++ b/internal/filter/result.go @@ -1,4 +1,4 @@ -package internal +package filter import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" diff --git a/internal/geoip/asntops.go b/internal/geoip/asntops.go index 4e0b562..b9b3b85 100644 --- a/internal/geoip/asntops.go +++ b/internal/geoip/asntops.go @@ -6,6 +6,8 @@ import "github.com/AdguardTeam/golibs/container" // DefaultTopASNs contains all specially handled ASNs. var DefaultTopASNs = container.NewMapSet[ASN]( + 3, + 6, 137, 174, 209, @@ -16,14 +18,13 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 680, 701, 719, - 760, 766, 786, + 802, 803, 812, 852, 855, - 906, 967, 984, 1103, @@ -39,6 +40,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 1653, 1659, 1680, + 1756, 1759, 1764, 1835, @@ -53,7 +55,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 2110, 2116, 2119, - 2200, 2497, 2514, 2516, @@ -69,12 +70,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 2614, 2740, 2764, - 2843, 2847, 2852, 2856, 2860, - 3132, 3209, 3212, 3215, @@ -89,11 +88,9 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 3269, 3292, 3301, - 3302, 3303, 3308, 3320, - 3326, 3329, 3342, 3352, @@ -103,6 +100,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 3549, 3559, 3605, + 3661, 3695, 3741, 3758, @@ -114,6 +112,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 4134, 4181, 4230, + 4515, 4538, 4609, 4638, @@ -123,7 +122,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 4713, 4721, 4725, - 4739, 4760, 4761, 4764, @@ -145,18 +143,16 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 5089, 5377, 5378, - 5379, 5384, - 5390, 5391, 5408, 5410, 5413, 5416, 5432, + 5438, 5466, - 5470, - 5479, + 5482, 5483, 5518, 5532, @@ -186,17 +182,17 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 6639, 6661, 6677, - 6682, 6697, 6700, 6703, 6713, 6730, 6739, - 6747, 6752, 6758, + 6769, 6772, + 6774, 6799, 6802, 6805, @@ -213,16 +209,17 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 6939, 7018, 7029, + 7054, 7057, 7122, 7131, 7303, + 7311, 7315, 7418, 7438, 7470, 7482, - 7497, 7522, 7524, 7545, @@ -234,7 +231,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 7738, 7794, 7922, - 7979, 7992, 8014, 8048, @@ -244,12 +240,11 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 8167, 8193, 8200, - 8220, + 8240, 8248, 8251, 8257, 8273, - 8285, 8290, 8301, 8339, @@ -260,6 +255,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 8374, 8376, 8386, + 8393, 8400, 8402, 8412, @@ -270,9 +266,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 8449, 8452, 8462, - 8468, 8473, - 8517, 8542, 8544, 8551, @@ -292,6 +286,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 8767, 8771, 8772, + 8778, 8781, 8814, 8818, @@ -300,6 +295,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 8849, 8866, 8881, + 8899, 8926, 8948, 8953, @@ -316,18 +312,20 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 9051, 9063, 9070, + 9080, 9105, 9119, 9121, 9123, 9125, 9129, + 9141, 9145, 9146, 9155, 9158, + 9186, 9198, - 9199, 9208, 9231, 9245, @@ -335,9 +333,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 9249, 9260, 9269, + 9293, 9299, - 9303, 9304, + 9313, 9316, 9318, 9329, @@ -362,6 +361,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 9674, 9694, 9751, + 9770, 9790, 9808, 9824, @@ -371,13 +371,13 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 9873, 9902, 9908, + 9919, 9922, 9924, 9930, 9931, 9934, - 9946, - 9976, + 9988, 9997, 10010, 10013, @@ -385,24 +385,23 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 10036, 10066, 10075, - 10076, 10094, 10099, 10101, + 10103, 10118, 10131, 10139, 10143, 10214, 10219, + 10222, 10226, 10269, 10292, 10299, 10396, - 10412, 10429, - 10474, 10617, 10620, 10796, @@ -415,7 +414,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 11259, 11260, 11290, - 11315, 11351, 11367, 11426, @@ -426,15 +424,12 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 11580, 11594, 11664, - 11721, 11776, 11814, - 11815, 11816, 11830, 11845, 11888, - 11992, 12046, 12066, 12083, @@ -444,6 +439,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 12297, 12301, 12302, + 12312, 12322, 12334, 12338, @@ -452,24 +448,30 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 12361, 12365, 12389, - 12390, 12392, 12400, 12406, 12414, + 12426, 12430, 12436, 12444, + 12453, 12455, 12479, + 12483, 12491, + 12496, 12508, 12539, 12552, + 12556, + 12570, 12576, 12578, 12597, 12605, + 12620, 12668, 12684, 12709, @@ -479,13 +481,11 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 12741, 12754, 12764, - 12796, 12810, 12829, 12849, 12874, 12876, - 12905, 12912, 12929, 12946, @@ -493,15 +493,18 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 12969, 12975, 12978, + 12993, 12997, 13000, 13030, 13036, 13037, + 13044, 13045, 13046, 13092, 13099, + 13101, 13110, 13122, 13124, @@ -514,20 +517,20 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 13213, 13280, 13285, - 13306, 13335, 13489, + 13682, 13771, 13999, 14061, 14080, 14117, + 14232, 14234, 14259, 14434, 14522, 14593, - 14618, 14638, 14709, 14754, @@ -536,6 +539,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 14956, 14979, 14988, + 15128, 15146, 15169, 15311, @@ -548,7 +552,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 15397, 15399, 15404, - 15425, 15433, 15435, 15440, @@ -562,30 +565,28 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 15557, 15600, 15614, - 15623, 15659, 15704, 15706, 15723, 15735, 15751, - 15765, 15766, 15774, 15796, 15802, 15805, + 15806, 15808, 15836, 15895, 15897, - 15899, 15924, 15943, + 15955, 15958, 15962, 15964, - 15975, 15994, 16010, 16019, @@ -594,6 +595,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 16082, 16086, 16116, + 16117, 16125, 16135, 16178, @@ -628,7 +630,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 17421, 17451, 17458, - 17465, 17470, 17480, 17488, @@ -642,13 +643,13 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 17557, 17621, 17622, - 17623, + 17638, 17639, 17665, 17676, 17698, 17705, - 17716, + 17726, 17809, 17816, 17828, @@ -663,6 +664,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 18001, 18004, 18013, + 18021, 18024, 18049, 18053, @@ -674,18 +676,18 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 18199, 18200, 18209, - 18278, 18371, 18390, 18403, 18412, 18419, + 18429, 18734, - 18747, 18809, 18822, 18840, 18881, + 18895, 19108, 19114, 19180, @@ -693,6 +695,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 19246, 19422, 19429, + 19447, 19624, 19711, 19863, @@ -711,12 +714,14 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 20473, 20485, 20545, + 20626, 20634, 20661, + 20676, 20771, 20776, - 20804, 20845, + 20846, 20860, 20875, 20879, @@ -725,20 +730,20 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 20960, 20963, 20978, + 21001, 21003, 21021, 21040, 21050, 21056, - 21100, 21107, - 21171, 21183, + 21193, 21211, - 21221, 21230, 21232, 21246, + 21263, 21271, 21277, 21283, @@ -765,7 +770,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 22047, 22069, 22085, - 22313, 22423, 22581, 22652, @@ -781,9 +785,9 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 23201, 23243, 23383, - 23470, 23487, 23520, + 23563, 23655, 23657, 23673, @@ -791,19 +795,18 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 23688, 23693, 23700, + 23750, 23752, - 23764, - 23783, 23860, 23889, 23900, + 23917, 23923, 23944, 23955, 23956, 23969, 24016, - 24020, 24033, 24086, 24157, @@ -811,11 +814,9 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 24163, 24164, 24165, - 24183, 24186, 24203, 24309, - 24324, 24337, 24378, 24389, @@ -833,7 +834,8 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 24560, 24589, 24608, - 24631, + 24634, + 24641, 24645, 24651, 24691, @@ -849,12 +851,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 24889, 24921, 24940, - 24953, 24955, - 24971, 25019, 25106, - 25117, + 25129, 25133, 25135, 25139, @@ -862,38 +862,38 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 25159, 25184, 25190, - 25198, + 25211, 25229, 25248, 25250, 25255, 25274, + 25311, 25369, 25374, - 25375, 25400, 25406, 25424, 25429, 25441, - 25447, 25454, 25467, 25471, 25472, - 25490, 25491, - 25509, 25512, 25513, + 25528, 25543, 25607, 25620, 25668, 25695, + 25820, 26130, 26210, 26383, + 26548, 26599, 26611, 26615, @@ -904,13 +904,12 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 27660, 27665, 27668, + 27669, 27672, 27680, 27694, - 27695, 27696, 27699, - 27708, 27717, 27725, 27729, @@ -928,7 +927,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 27789, 27796, 27800, - 27806, 27813, 27831, 27833, @@ -951,7 +949,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 27932, 27947, 27951, - 27953, 27983, 27984, 27988, @@ -964,9 +961,8 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 28036, 28048, 28049, - 28061, - 28067, 28075, + 28080, 28094, 28103, 28104, @@ -975,7 +971,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 28146, 28182, 28186, - 28191, + 28198, 28201, 28210, 28220, @@ -1015,23 +1011,27 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 28787, 28812, 28840, + 28849, + 28854, 28878, 28884, 28885, - 28919, 28952, 28964, 28972, 29027, 29030, + 29032, + 29039, 29049, 29061, 29070, 29084, 29091, 29119, + 29124, + 29148, 29170, - 29182, 29208, 29222, 29238, @@ -1039,16 +1039,14 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 29247, 29256, 29286, + 29287, 29310, 29314, - 29340, - 29348, 29355, 29357, 29405, 29447, 29465, - 29467, 29485, 29492, 29518, @@ -1058,15 +1056,14 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 29571, 29580, 29582, - 29584, - 29600, 29614, + 29672, 29687, - 29689, 29695, 29975, 30036, 30058, + 30272, 30344, 30526, 30600, @@ -1077,12 +1074,11 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 30844, 30848, 30873, - 30896, - 30929, 30982, 30983, 30985, 30986, + 30987, 30990, 30992, 30999, @@ -1099,37 +1095,41 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 31143, 31148, 31163, - 31169, 31200, 31204, 31205, + 31208, 31213, 31224, 31242, 31245, 31246, 31252, - 31263, 31272, 31287, - 31390, 31404, 31423, 31435, 31452, 31499, + 31510, + 31520, 31543, 31549, 31615, + 31638, 31655, + 31679, 31689, 31721, 31726, + 31736, 31856, 31898, 31960, 32020, 32098, + 32167, 32398, 32860, 33363, @@ -1150,49 +1150,51 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 33885, 33915, 33922, + 33947, 33983, 34001, - 34032, 34058, - 34080, 34087, + 34120, 34170, + 34187, 34224, 34244, 34245, - 34295, 34296, - 34318, 34362, + 34368, 34447, 34458, 34471, - 34488, 34515, + 34525, 34533, + 34547, 34557, 34577, 34594, 34661, 34666, - 34683, 34700, 34702, + 34705, 34718, - 34724, 34743, 34772, 34779, 34797, 34803, - 34820, + 34841, 34857, 34876, + 34918, 34977, 34984, 34989, 35046, 35047, + 35063, 35091, 35104, 35107, @@ -1210,35 +1212,42 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 35328, 35346, 35362, + 35370, 35432, 35444, 35457, + 35506, 35518, 35549, 35566, 35567, 35568, 35612, + 35613, 35699, + 35706, 35725, 35729, 35753, 35790, 35805, 35807, + 35816, 35819, + 35892, 35900, + 35911, 36290, - 36384, - 36511, + 36352, + 36492, 36549, - 36599, 36864, 36865, 36866, 36873, 36874, 36877, + 36881, 36884, 36890, 36892, @@ -1251,11 +1260,11 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 36912, 36913, 36914, + 36916, 36920, 36924, 36925, 36926, - 36930, 36935, 36937, 36939, @@ -1266,6 +1275,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 36962, 36963, 36965, + 36969, 36972, 36974, 36977, @@ -1278,10 +1288,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37002, 37006, 37009, + 37012, 37014, 37020, 37027, - 37028, 37030, 37035, 37037, @@ -1296,7 +1306,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37074, 37075, 37076, - 37081, 37084, 37090, 37094, @@ -1307,12 +1316,13 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37113, 37119, 37123, - 37124, + 37126, 37129, 37133, 37136, 37141, 37148, + 37150, 37154, 37163, 37164, @@ -1331,6 +1341,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37208, 37211, 37215, + 37218, 37219, 37223, 37228, @@ -1343,6 +1354,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37282, 37284, 37287, + 37292, 37294, 37303, 37305, @@ -1359,8 +1371,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37342, 37343, 37349, - 37350, - 37353, 37358, 37371, 37376, @@ -1405,14 +1415,13 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37552, 37559, 37563, + 37568, 37575, 37577, - 37580, 37582, 37584, 37594, 37604, - 37608, 37611, 37612, 37614, @@ -1420,25 +1429,29 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 37622, 37642, 37645, - 37647, 37649, 37654, + 37662, 37665, 37671, + 37677, 37678, 37680, 37682, 37693, 37697, 37705, - 37713, + 37711, 37721, + 37723, 37963, 38009, 38019, 38067, 38077, 38136, + 38172, + 38176, 38195, 38198, 38201, @@ -1449,7 +1462,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 38264, 38266, 38322, - 38333, 38442, 38466, 38511, @@ -1460,7 +1472,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 38623, 38742, 38800, - 38805, 38819, 38841, 38875, @@ -1468,44 +1479,47 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 38999, 39007, 39010, + 39024, 39032, 39067, + 39120, + 39122, 39184, - 39199, 39216, 39232, + 39251, 39273, - 39280, + 39279, + 39308, 39344, 39351, 39374, 39397, + 39401, 39402, 39440, - 39544, + 39507, + 39574, 39603, 39608, 39611, - 39615, 39642, 39647, 39686, - 39791, - 39823, + 39699, + 39766, 39824, 39826, 39891, - 39906, - 39912, - 40021, + 39927, 40029, + 40065, 40786, 40788, + 40934, 40945, - 41007, - 41019, + 41046, 41068, - 41096, 41124, 41164, 41202, @@ -1514,25 +1528,25 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 41313, 41329, 41330, - 41378, - 41454, - 41496, + 41371, 41549, 41557, 41563, 41564, + 41627, 41676, 41704, - 41714, + 41712, 41733, 41738, 41745, 41750, 41798, + 41820, 41833, + 41881, 41897, 41956, - 41997, 42003, 42013, 42082, @@ -1540,8 +1554,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 42109, 42162, 42183, + 42201, 42232, 42235, + 42248, 42298, 42306, 42313, @@ -1558,18 +1574,18 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 42580, 42581, 42610, + 42621, 42652, 42689, 42708, 42713, - 42714, 42772, 42779, 42828, 42837, 42841, + 42852, 42863, - 42864, 42905, 42908, 42912, @@ -1578,55 +1594,51 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 42991, 43019, 43060, - 43118, + 43061, + 43104, 43139, + 43192, 43197, 43205, + 43227, 43242, 43248, 43256, - 43284, 43350, 43375, + 43444, 43451, 43452, 43513, 43529, - 43533, 43568, - 43581, 43612, 43627, 43633, - 43656, + 43653, 43700, - 43708, - 43709, 43733, 43754, 43766, 43769, - 43791, + 43811, 43824, - 43873, - 43892, - 43922, + 43870, 43925, 43939, 43940, - 43957, + 43994, + 44021, 44027, 44034, + 44066, 44086, 44087, - 44118, 44134, 44143, 44213, 44217, - 44234, 44244, - 44285, 44313, 44327, 44377, @@ -1636,20 +1648,26 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 44483, 44489, 44515, - 44549, 44558, 44566, - 44575, + 44631, + 44692, 44702, 44708, + 44709, + 44725, 44735, + 44803, 44869, + 44901, 44925, + 45007, 45090, 45102, 45143, 45177, 45178, + 45193, 45245, 45267, 45271, @@ -1664,9 +1682,9 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 45543, 45588, 45609, - 45637, 45650, 45669, + 45671, 45700, 45754, 45758, @@ -1678,155 +1696,133 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 45905, 45916, 45925, - 45935, 45960, 46198, 46408, - 46573, 46650, - 46868, - 46941, 47139, + 47159, 47169, - 47204, - 47217, 47232, 47237, 47253, + 47292, 47331, + 47376, 47377, 47394, - 47474, + 47402, 47485, - 47493, 47524, - 47583, + 47556, 47588, 47589, + 47782, 47790, - 47794, 47881, 47883, 47887, - 47898, + 47890, 47943, 47956, - 47959, 47962, - 48014, 48092, - 48133, - 48147, 48161, 48190, 48206, - 48239, 48252, 48260, - 48266, 48288, - 48289, - 48418, - 48431, + 48408, 48437, - 48480, 48492, 48503, 48506, - 48584, 48629, + 48642, 48675, + 48695, + 48715, 48728, - 48747, - 48781, 48830, 48832, 48847, 48887, 48917, - 48944, 48966, 49020, - 49040, 49044, 49056, 49100, 49101, 49110, - 49115, 49117, 49129, 49223, 49242, 49273, - 49282, - 49362, - 49453, - 49460, + 49455, 49561, 49628, + 49711, 49724, 49760, 49798, 49800, + 49801, 49808, + 49840, 49889, 49902, 49911, 49914, 49981, + 49985, 50010, 50181, - 50195, 50223, - 50231, 50251, 50261, 50266, 50274, 50294, - 50304, - 50318, - 50334, 50349, 50436, 50463, 50467, 50482, 50500, - 50506, - 50558, 50581, 50613, 50616, 50635, 50648, + 50666, 50670, 50685, + 50698, 50749, 50767, 50770, 50810, 50821, 50825, - 50917, + 50925, + 50953, 50959, 50973, 50979, 51018, 51020, - 51026, + 51069, 51104, 51110, - 51132, - 51142, 51167, 51175, 51184, 51207, 51265, 51336, - 51342, 51346, 51375, 51395, @@ -1835,7 +1831,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 51495, 51504, 51582, - 51604, 51645, 51653, 51684, @@ -1844,13 +1839,14 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 51809, 51825, 51847, - 51873, + 51852, 51878, 51896, - 51947, 52075, + 52116, 52173, 52228, + 52232, 52233, 52238, 52242, @@ -1872,7 +1868,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 52433, 52436, 52444, - 52455, 52465, 52468, 52471, @@ -1881,14 +1876,12 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 53006, 53667, 53764, - 53813, - 53926, + 54115, 54198, 54614, - 54801, 55081, - 55286, 55330, + 55387, 55391, 55392, 55415, @@ -1896,12 +1889,9 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 55427, 55430, 55501, - 55523, - 55561, 55577, 55685, 55699, - 55720, 55769, 55805, 55821, @@ -1910,6 +1900,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 55853, 55900, 55915, + 55933, 55943, 55944, 55990, @@ -1923,6 +1914,7 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 56047, 56048, 56055, + 56068, 56089, 56099, 56120, @@ -1934,23 +1926,24 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 56300, 56309, 56329, - 56354, 56369, + 56400, 56410, - 56465, 56468, 56478, - 56494, + 56491, 56497, 56568, + 56630, 56653, 56655, + 56656, 56665, 56696, + 56704, 56709, - 56882, + 56821, 56902, - 56933, 56971, 56995, 57000, @@ -1963,21 +1956,21 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 57218, 57223, 57248, + 57256, 57269, - 57279, 57293, 57344, - 57367, - 57370, 57374, 57388, 57389, + 57391, 57443, 57513, 57564, - 57566, 57630, 57634, + 57722, + 57728, 57743, 57760, 57761, @@ -1985,22 +1978,25 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 57778, 57794, 57852, + 57858, 57869, 57888, + 57961, 58061, 58065, - 58087, 58098, - 58118, + 58121, 58130, 58224, 58309, + 58321, 58322, 58328, 58453, 58460, 58461, 58485, + 58495, 58504, 58507, 58524, @@ -2014,68 +2010,67 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 58731, 58821, 58895, - 58945, 58952, 58955, 59108, 59125, 59126, + 59253, 59257, - 59355, + 59317, 59362, 59381, - 59396, 59497, + 59573, 59588, 59625, 59668, + 59702, 59729, 59847, 59861, 59890, 59989, 60068, + 60111, 60171, 60258, 60294, - 60296, 60304, 60352, 60367, 60372, 60377, + 60398, + 60404, 60415, 60471, - 60587, + 60515, + 60588, 60602, - 60631, - 60656, - 60690, 60725, 60754, 60757, 60781, - 60791, 60806, + 60886, + 60895, 60999, 61006, - 61010, 61071, 61079, 61112, - 61135, - 61138, 61143, + 61174, 61189, + 61208, 61211, 61272, 61275, 61287, 61307, - 61317, 61367, 61424, - 61449, 61461, 61466, 61468, @@ -2084,81 +2079,82 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 61512, 62005, 62013, + 62059, 62161, 62179, 62211, 62212, - 62214, 62240, 62250, - 62281, 62282, + 62336, 62419, 62563, + 62627, 62651, 63023, 63199, + 63393, 63526, + 63852, 63859, - 63888, 63949, 63969, 63991, - 64037, + 63996, 64043, 64073, - 64098, 64105, + 64126, 64134, - 64443, + 64139, 64466, 131090, 131111, 131178, + 131198, 131207, 131267, 131284, 131285, - 131316, 131429, 131445, 131464, 131471, - 131584, 131591, 131596, 131602, 131627, 131706, 131769, + 131965, 132061, - 132077, + 132080, 132148, 132165, 132173, 132199, 132203, 132204, + 132222, + 132280, 132298, 132429, 132447, - 132449, 132462, 132468, 132471, 132525, + 132608, 132618, - 132686, - 132815, - 133012, 133076, - 133199, 133200, 133210, 133334, - 133384, + 133385, + 133398, 133433, - 133480, + 133440, 133481, 133524, 133533, @@ -2168,18 +2164,17 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 133623, 133661, 133752, - 133875, + 133798, 133894, 133897, 133982, 134090, 134113, 134134, - 134139, 134204, 134356, - 134359, 134489, + 134525, 134562, 134651, 134674, @@ -2188,11 +2183,12 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 134715, 134732, 134783, + 134806, 134810, 134840, 134995, 135043, - 135097, + 135059, 135126, 135298, 135341, @@ -2210,87 +2206,83 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 135607, 135887, 136000, + 136030, 136039, 136093, 136119, 136141, 136167, - 136210, 136238, - 136239, 136255, - 136379, 136380, 136384, - 136400, 136442, 136454, - 136474, + 136461, 136479, + 136480, 136515, 136525, 136557, - 136617, + 136780, 136787, 136873, 136907, - 136919, + 136929, 136969, 136972, 136986, 136994, - 137040, 137047, 137080, - 137226, + 137266, 137409, 137412, 137424, - 137443, 137449, 137453, 137503, 137526, 137561, + 137580, 137853, 137872, 137895, 137959, 137967, + 137989, 138089, + 138109, + 138123, 138134, - 138167, + 138152, 138179, 138197, - 138345, 138346, 138368, 138384, 138423, + 138500, 138529, 138558, 138590, - 138606, 138640, 138655, 138754, 138886, 138915, - 138926, 138964, 138965, - 138997, 139009, 139029, 139043, - 139058, 139224, 139238, + 139285, 139325, 139628, - 139692, - 139704, - 139741, + 139651, + 139659, 139759, 139820, 139831, @@ -2300,214 +2292,209 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 139898, 139922, 139952, + 139967, 139981, 139994, 140045, 140061, 140072, - 140091, 140220, + 140243, 140292, - 140345, + 140401, + 140457, 140485, 140499, 140504, 140594, - 140659, + 140638, 140726, - 140867, 140900, 141015, 141024, 141031, 141039, 141047, + 141077, 141127, 141140, 141145, - 141216, 141342, 141421, 141607, 141680, 141681, 141711, + 141739, 141767, 141778, 141995, - 142032, 142065, 142139, 142271, 142295, + 142319, 142386, 142647, - 146961, - 147023, 147049, + 147184, 147302, + 147314, 149034, 149173, 149359, 149360, 149419, 149456, - 149487, 149707, 149771, - 150222, - 150331, + 149866, 150371, - 150407, + 150381, 150452, 150683, 150692, - 150748, 150750, 150774, - 151080, 151396, - 151491, - 151636, - 151965, 151983, 152178, 152317, - 152329, - 152337, + 152448, 152605, 152677, + 152918, + 153323, 196640, - 196838, + 196735, + 196854, 196874, + 196906, 196925, 196961, + 197013, + 197119, 197207, 197225, 197248, 197296, 197301, + 197307, 197350, 197398, 197423, - 197470, 197540, 197556, + 197623, 197706, 197716, 197830, 197862, 197882, - 197897, - 198023, + 198004, 198068, - 198256, 198265, 198279, - 198288, - 198440, 198441, 198471, - 198499, 198504, 198589, 198605, - 198632, 198668, 198820, 198890, - 198930, 198961, 198966, - 199081, + 199128, 199140, + 199152, 199155, 199173, + 199239, + 199274, 199276, - 199326, 199469, 199490, 199493, 199524, 199620, 199636, + 199698, 199707, 199731, 199736, 199739, - 199785, - 200019, - 200076, + 199995, 200134, 200154, 200200, 200313, 200446, + 200475, 200590, - 200665, + 200640, + 200683, 200724, 200736, + 200740, 200742, 200845, 200865, - 201004, 201019, 201073, 201089, - 201107, + 201150, 201167, 201241, 201249, 201411, - 201476, 201505, 201540, 201577, 201596, - 201601, 201603, - 201746, 201749, 201767, 201776, + 201814, 201838, 201884, - 201973, + 201890, 201986, - 201997, 202023, + 202053, 202087, 202098, 202204, + 202246, 202254, 202293, 202316, + 202376, + 202391, 202422, - 202433, + 202425, 202441, - 202468, 202498, 202561, 202613, 202616, 202632, + 202635, 202651, - 202652, 202662, + 202672, 202710, 202759, 202870, - 202876, - 202921, + 202895, 202940, - 202960, 202987, 203020, 203136, 203143, 203206, 203214, - 203217, 203257, 203424, 203451, @@ -2515,129 +2502,129 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 203622, 203675, 203680, - 203715, - 203811, + 203744, + 203827, 203877, - 203912, 203916, - 203917, 203936, 203953, 203964, 203971, 203995, - 203999, - 204020, 204106, - 204108, 204151, 204170, - 204267, 204274, 204279, 204317, 204342, + 204356, + 204383, + 204390, + 204403, + 204429, 204457, - 204467, - 204566, 204592, 204595, 204649, + 204666, + 204716, 204793, - 204816, + 204802, + 204894, 204918, + 204926, 204957, 204986, - 205015, + 204996, + 205007, 205110, - 205168, 205244, 205254, 205278, - 205367, 205368, - 205371, 205547, 205638, 205647, 205714, 205800, - 205814, + 205832, 205889, - 206013, 206026, 206065, 206067, + 206088, + 206092, 206119, + 206170, 206206, 206238, 206260, 206262, - 206283, 206358, 206375, 206406, - 206429, 206446, + 206471, 206478, 206519, - 206557, - 206575, 206610, 206666, 206774, 206783, - 206784, + 206804, 206892, 206920, 206977, + 207044, 207097, 207137, + 207154, 207192, 207251, + 207348, 207369, 207375, - 207408, - 207502, 207569, 207589, + 207766, 207782, 207810, 207876, + 207980, 207990, 207991, + 208031, + 208077, + 208161, 208286, 208320, 208324, - 208339, - 208343, - 208448, 208570, 208592, + 208666, + 208668, 208671, 208730, 208734, 208859, + 208864, 208905, 208972, 208997, - 209012, 209046, - 209049, + 209181, 209193, 209196, 209240, 209262, - 209273, 209277, - 209302, 209360, 209424, - 209429, 209442, - 209491, 209531, - 209778, + 209641, + 209816, 209835, 209839, 209854, @@ -2646,12 +2633,10 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 210016, 210021, 210022, - 210061, - 210070, + 210116, 210125, 210147, 210218, - 210273, 210278, 210315, 210402, @@ -2660,30 +2645,30 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 210616, 210625, 210644, + 210693, 210740, - 210747, 210808, - 210865, 211028, 211057, + 211098, 211211, - 211235, + 211250, 211309, 211356, 211450, - 211458, 211468, + 211531, 211555, 211559, - 211759, + 211841, 211908, 211995, + 212046, 212183, 212238, - 212271, 212330, - 212444, 212449, + 212477, 212531, 212572, 212616, @@ -2691,54 +2676,66 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 212645, 212655, 212661, - 212766, - 212851, - 212865, 212898, + 212910, 212999, + 213139, 213155, 213207, + 213261, 213295, 213320, 213398, 213402, - 213957, + 213946, + 214185, + 214359, + 214515, + 214576, + 214668, 214739, 214790, + 214808, 214996, + 215025, 215052, 215284, - 215304, + 215287, + 215311, 215346, 215355, + 215416, 215421, 215423, 215501, 215540, 215597, + 215733, 215746, + 215859, 215886, 215910, 215968, 216046, 216071, + 216086, 216139, - 216165, + 216154, 216183, 216200, + 216312, 216374, 216463, 262145, 262146, 262159, + 262179, 262181, 262186, - 262188, 262191, 262197, 262202, 262210, - 262215, 262223, 262234, 262239, @@ -2746,22 +2743,21 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 262248, 262253, 262262, - 262287, - 262354, 262378, 262468, 262481, - 262494, 262589, 262663, 262753, 262773, 262916, + 262932, 263073, 263175, + 263189, 263210, - 263216, 263222, + 263223, 263224, 263238, 263242, @@ -2769,7 +2765,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 263248, 263292, 263327, - 263684, 263686, 263689, 263694, @@ -2777,7 +2772,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 263703, 263717, 263725, - 263732, 263749, 263750, 263751, @@ -2785,18 +2779,19 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 263762, 263763, 263765, + 263767, 263783, - 263785, 263791, 263792, - 263793, 263805, 263824, 263980, 264221, + 264344, 264605, 264609, 264628, + 264631, 264635, 264637, 264640, @@ -2807,33 +2802,35 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 264685, 264686, 264694, + 264696, 264731, 264733, 264738, - 264744, 264750, - 264754, 264756, 264758, + 264764, 264770, 264778, 264779, 264780, 264783, + 264796, 264814, - 264821, 264825, 264837, + 264838, 264847, 264984, 265509, 265540, 265561, 265594, + 265605, 265606, 265608, 265629, - 265631, + 265630, 265632, 265636, 265675, @@ -2845,105 +2842,107 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 265721, 265724, 265727, - 265762, + 265735, + 265749, 265767, 265780, 265798, 265799, - 265818, 265822, - 265825, 265855, + 265862, 265867, 265880, 266445, 266668, 266673, - 266694, + 266695, + 266710, 266725, - 266742, + 266734, + 266755, 266757, + 266762, 266792, - 266802, + 266812, 266814, 266815, + 266828, 266831, - 266832, 266841, 266853, - 266858, 266860, + 266870, + 266880, 266893, 266894, 266904, 267684, 267685, 267699, - 267705, 267708, 267713, 267749, 267761, 267765, - 267767, + 267790, 267797, 267803, 267809, + 267818, 267828, 267845, 267846, + 267869, 267882, 267883, - 267900, 267904, - 267905, - 267916, - 267920, - 268323, 268976, 269036, + 269194, + 269725, 269729, 269730, 269733, 269734, + 269748, 269749, - 269750, - 269769, + 269763, 269783, - 269788, 269797, 269804, 269806, 269816, 269820, 269822, + 269829, 269831, 269832, + 269838, 269840, - 269843, 269846, 269853, 269862, - 269894, + 269868, + 269898, 269901, 269908, + 269909, + 269918, 269919, 269921, 269927, 269931, 269934, + 269936, 269940, 269946, - 269950, - 269953, - 269964, 269965, 269973, 269976, - 269981, - 269983, 269984, 269989, + 270006, 270007, 270026, 270029, @@ -2954,146 +2953,156 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 270058, 270068, 270071, - 270075, - 270081, 270096, - 270098, + 270108, + 270158, 270161, - 270186, 271773, 271781, 271791, + 271793, 271795, - 271806, 271808, 271814, + 271819, 271835, 271837, + 271868, 271874, + 271880, 271899, 271907, 271909, 271911, - 271916, 271929, - 271930, 271933, 271935, - 271942, - 271943, 271951, - 271956, 271965, + 271968, 271971, - 272018, + 271978, + 271979, + 271988, + 272025, 272026, 272027, - 272062, - 272066, + 272057, + 272065, + 272075, 272083, - 272094, 272099, 272102, - 272104, 272106, 272110, 272112, - 272120, 272122, 272132, 272134, - 272138, 272809, - 272814, 272818, 272827, 272836, - 272838, 272851, - 272869, + 272854, 272882, 272886, 272906, 272916, 272921, + 272928, + 272943, + 272946, 272953, 272955, 272962, 272991, + 273009, 273019, - 273024, + 273023, + 273034, 273054, - 273063, 273067, - 273093, - 273098, 273113, + 273123, 273133, 273139, + 273171, 273172, 273173, - 273180, + 273189, 273203, + 273205, + 273221, + 273309, + 273683, 273824, + 273832, 273872, + 273960, + 273961, + 273966, + 274035, 327687, 327693, 327697, 327707, 327712, + 327714, 327716, 327724, 327725, 327728, + 327733, 327738, 327750, - 327754, 327756, + 327760, 327765, 327769, - 327771, 327776, 327782, 327786, + 327794, 327795, 327798, 327799, 327802, + 327804, + 327809, 327819, 327820, 327828, 327829, 327830, 327862, - 327863, 327864, 327871, + 327876, + 327879, 327885, 327900, 327901, - 327903, - 327921, 327931, - 327932, 327934, + 327941, 327947, 327972, 327975, - 327987, 327990, - 327991, - 328001, 328061, 328073, 328075, 328079, 328088, + 328098, 328111, 328136, 328140, 328146, 328169, 328181, + 328184, 328191, 328196, 328198, @@ -3101,28 +3110,28 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 328207, 328223, 328228, - 328230, - 328249, + 328244, 328250, 328253, 328258, - 328282, + 328269, + 328271, 328284, 328286, + 328293, 328297, - 328304, + 328309, 328319, - 328320, 328331, 328341, + 328351, 328358, 328411, - 328432, - 328436, + 328427, 328442, + 328460, 328469, 328471, - 328473, 328475, 328479, 328480, @@ -3130,7 +3139,6 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 328490, 328491, 328494, - 328509, 328510, 328514, 328535, @@ -3139,46 +3147,47 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 328549, 328567, 328570, - 328576, 328581, 328586, 328590, 328594, + 328599, 328605, 328610, 328611, - 328614, 328619, 328628, + 328636, 328638, - 328652, 328659, 328676, 328697, 328702, 328708, - 328716, 328717, 328727, - 328753, + 328743, 328755, 328770, 328777, 328817, 328844, + 328850, 328856, 328857, 328858, 328880, - 328895, 328899, + 328907, 328923, + 328935, + 328939, 328943, - 328950, 328954, 328959, 328961, 328965, + 328975, 328977, 328987, 328988, @@ -3188,69 +3197,71 @@ var DefaultTopASNs = container.NewMapSet[ASN]( 329014, 329027, 329029, + 329041, 329044, + 329067, 329078, 329082, - 329095, 329101, 329103, - 329110, + 329109, 329126, 329129, + 329135, 329140, - 329155, 329167, - 329170, 329174, 329179, 329183, 329192, + 329198, 329205, + 329207, 329211, 329219, 329220, - 329223, 329253, + 329255, 329261, 329274, 329286, - 329288, 329301, - 329322, - 329373, + 329341, 329387, 329390, 329411, 329415, 329422, - 329425, - 329426, + 329435, 329437, 329472, + 329475, 329504, + 393275, + 393559, + 393573, 393629, 393894, 394311, 394381, 394684, - 395092, 395561, - 395916, 395965, + 396082, 396304, 396338, + 396356, 396357, - 396420, 396982, - 397735, 397961, 398228, + 398326, 398721, 398901, - 399077, 399498, 399724, - 400099, + 400304, + 400741, ) // DefaultCountryTopASNs is a mapping of a country to their top ASNs. @@ -3263,9 +3274,9 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryAL: 50973, CountryAM: 43733, CountryAO: 37119, - CountryAQ: 199707, + CountryAQ: 214808, CountryAR: 7303, - CountryAS: 9751, + CountryAS: 12684, CountryAT: 8412, CountryAU: 1221, CountryAW: 11816, @@ -3279,29 +3290,30 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryBG: 8866, CountryBH: 5416, CountryBI: 327799, - CountryBJ: 37424, - CountryBM: 32020, + CountryBJ: 328228, + CountryBL: 14593, + CountryBM: 3855, CountryBN: 10094, CountryBO: 6568, - CountryBQ: 28104, + CountryBQ: 27745, CountryBR: 26599, CountryBS: 15146, CountryBT: 18024, - CountryBW: 37014, + CountryBW: 14988, CountryBY: 25106, CountryBZ: 10269, - CountryCA: 812, - CountryCD: 37020, + CountryCA: 577, + CountryCD: 37447, CountryCF: 37460, CountryCG: 36924, - CountryCH: 6730, + CountryCH: 3303, CountryCI: 29571, CountryCK: 10131, CountryCL: 27651, CountryCM: 30992, CountryCN: 4134, CountryCO: 10620, - CountryCR: 52263, + CountryCR: 11830, CountryCU: 27725, CountryCV: 37517, CountryCW: 52233, @@ -3314,14 +3326,14 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryDO: 6400, CountryDZ: 36947, CountryEC: 27947, - CountryEE: 3249, + CountryEE: 2586, CountryEG: 8452, CountryER: 24757, CountryES: 3352, CountryET: 24757, CountryFI: 51765, CountryFJ: 38442, - CountryFK: 198605, + CountryFK: 204649, CountryFM: 139759, CountryFO: 15389, CountryFR: 3215, @@ -3332,11 +3344,11 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryGF: 3215, CountryGG: 8680, CountryGH: 30986, - CountryGI: 8301, + CountryGI: 44477, CountryGL: 8818, CountryGM: 37552, CountryGN: 37461, - CountryGP: 3215, + CountryGP: 16028, CountryGQ: 37173, CountryGR: 6799, CountryGT: 14754, @@ -3355,7 +3367,7 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryIN: 55836, CountryIO: 17458, CountryIQ: 203214, - CountryIR: 197207, + CountryIR: 58224, CountryIS: 44735, CountryIT: 1267, CountryJE: 8681, @@ -3367,28 +3379,27 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryKH: 38623, CountryKI: 134783, CountryKM: 328061, - CountryKN: 11139, - CountryKP: 199707, + CountryKN: 36290, CountryKR: 4766, CountryKW: 29357, CountryKY: 6639, - CountryKZ: 206026, - CountryLA: 9873, - CountryLB: 57513, + CountryKZ: 48503, + CountryLA: 131267, + CountryLB: 42003, CountryLC: 15344, - CountryLI: 136787, + CountryLI: 20634, CountryLK: 18001, - CountryLR: 37094, - CountryLS: 37057, + CountryLR: 37410, + CountryLS: 33567, CountryLT: 8764, CountryLU: 6661, - CountryLV: 24921, + CountryLV: 1257, CountryLY: 328286, CountryMA: 36903, CountryMC: 6758, CountryMD: 8926, CountryME: 43940, - CountryMF: 33392, + CountryMF: 393894, CountryMG: 37054, CountryMH: 24439, CountryMK: 6821, @@ -3397,7 +3408,7 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryMN: 17882, CountryMO: 4609, CountryMP: 7131, - CountryMQ: 16028, + CountryMQ: 20776, CountryMR: 29544, CountryMS: 396304, CountryMT: 12709, @@ -3410,12 +3421,14 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryNA: 36996, CountryNC: 18200, CountryNE: 37531, + CountryNF: 198605, CountryNG: 29465, CountryNI: 14754, CountryNL: 1136, CountryNO: 9009, CountryNP: 17501, - CountryNR: 140504, + CountryNR: 14593, + CountryNU: 198605, CountryNZ: 9790, CountryOM: 28885, CountryPA: 11556, @@ -3426,13 +3439,13 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryPK: 45669, CountryPL: 5617, CountryPM: 3695, - CountryPR: 14638, + CountryPR: 21928, CountryPS: 12975, - CountryPT: 3243, + CountryPT: 12353, CountryPW: 17893, CountryPY: 23201, CountryQA: 8781, - CountryRE: 3215, + CountryRE: 199140, CountryRO: 8708, CountryRS: 8400, CountryRU: 8359, @@ -3441,17 +3454,16 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountrySB: 45891, CountrySC: 36958, CountrySD: 15706, - CountrySE: 60068, + CountrySE: 1257, CountrySG: 4773, - CountrySH: 17400, CountrySI: 21283, CountrySK: 6855, - CountrySL: 36988, + CountrySL: 37164, CountrySM: 15433, CountrySN: 8346, CountrySO: 37371, CountrySR: 27775, - CountrySS: 37594, + CountrySS: 14593, CountryST: 328191, CountrySV: 14754, CountrySX: 27781, @@ -3462,13 +3474,13 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryTG: 36924, CountryTH: 131445, CountryTJ: 43197, - CountryTK: 55523, CountryTL: 58731, CountryTM: 20661, CountryTN: 37693, - CountryTO: 38201, + CountryTO: 38198, CountryTR: 47331, CountryTT: 27800, + CountryTV: 23917, CountryTW: 3462, CountryTZ: 36908, CountryUA: 15895, @@ -3479,16 +3491,16 @@ var DefaultCountryTopASNs = map[Country]ASN{ CountryVA: 8978, CountryVC: 46408, CountryVE: 8048, - CountryVG: 11139, + CountryVG: 14813, CountryVI: 14434, CountryVN: 7552, - CountryVU: 9249, + CountryVU: 132429, CountryWF: 45879, CountryWS: 38800, CountryXK: 21246, CountryYE: 30873, - CountryYT: 3215, + CountryYT: 49902, CountryZA: 37457, CountryZM: 37287, - CountryZW: 56696, + CountryZW: 328088, } diff --git a/internal/geoip/file.go b/internal/geoip/file.go index b9d15d6..1c47978 100644 --- a/internal/geoip/file.go +++ b/internal/geoip/file.go @@ -10,10 +10,10 @@ import ( "sync" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/service" "github.com/oschwald/maxminddb-golang" ) @@ -34,6 +34,9 @@ type FileConfig struct { // CacheManager is the global cache manager. CacheManager must not be nil. CacheManager agdcache.Manager + // Metrics is used for the collection of the Geo IP database statistics. + Metrics Metrics + // AllTopASNs contains all subnets from CountryTopASNs. While scanning the // statistics data file this set is used to check if the current ASN // included in CountryTopASNs. @@ -72,6 +75,8 @@ type File struct { asn *maxminddb.Reader country *maxminddb.Reader + metrics Metrics + // TODO(a.garipov): Consider switching fully to the country ASN method and // removing these. // @@ -144,7 +149,8 @@ func NewFile(c *FileConfig) (f *File) { c.CacheManager.Add(cacheIDIP, ipCache) return &File{ - logger: c.Logger, + logger: c.Logger, + metrics: c.Metrics, mu: &sync.RWMutex{}, @@ -242,8 +248,11 @@ func (f *File) SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Pr // Data implements the Interface interface for *File. If ip is netip.Addr{}, // Data tries to lookup and return the data based on host, unless it's empty. func (f *File) Data(host string, ip netip.Addr) (l *Location, err error) { + // TODO(e.burkov): Add context to the [Interface] methods. + ctx := context.TODO() + if ip == (netip.Addr{}) { - return f.dataByHost(host), nil + return f.dataByHost(ctx, host), nil } else if ip.Is4In6() { // This can really only happen when querying data for ECS addresses, // since the remote IP address is normalized in dnssvc.ipFromNetAddr. @@ -254,12 +263,12 @@ func (f *File) Data(host string, ip netip.Addr) (l *Location, err error) { cacheKey := ipToCacheKey(ip) item, ok := f.ipCache.Get(cacheKey) if ok { - metrics.GeoIPCacheLookupsHits.Inc() + f.metrics.IncrementIPCacheLookups(ctx, true) return item, nil } - metrics.GeoIPCacheLookupsMisses.Inc() + f.metrics.IncrementIPCacheLookups(ctx, false) f.mu.RLock() defer f.mu.RUnlock() @@ -285,16 +294,17 @@ func (f *File) Data(host string, ip netip.Addr) (l *Location, err error) { } // dataByHost returns GeoIP data that has been cached previously. -func (f *File) dataByHost(host string) (l *Location) { +func (f *File) dataByHost(ctx context.Context, host string) (l *Location) { item, ok := f.hostCache.Get(host) + if ok { + f.metrics.IncrementHostCacheLookups(ctx, true) - metrics.IncrementCond( - ok, - metrics.GeoIPHostCacheLookupsHits, - metrics.GeoIPHostCacheLookupsMisses, - ) + return item + } - return item + f.metrics.IncrementHostCacheLookups(ctx, false) + + return nil } // asnResult is used to retrieve autonomous system number data from a GeoIP @@ -368,24 +378,29 @@ func (f *File) setCaches(host string, ipCacheKey any, l *Location) { f.hostCache.Set(host, l) } -// Refresh implements the [agdservice.Refresher] interface for *File. It -// reopens the GeoIP database files. +// type check +var _ service.Refresher = (*File)(nil) + +// Refresh implements the [service.Refresher] interface for *File. It reopens +// the GeoIP database files. func (f *File) Refresh(ctx context.Context) (err error) { f.logger.InfoContext(ctx, "refresh started") defer f.logger.InfoContext(ctx, "refresh finished") - asn, err := geoIPFromFile(f.asnPath) - if err != nil { - metrics.GeoIPUpdateStatus.WithLabelValues(f.asnPath).Set(0) + var asnErr, ctryErr error + defer func() { + f.metrics.HandleASNUpdateStatus(ctx, asnErr) + f.metrics.HandleCountryUpdateStatus(ctx, ctryErr) + }() - return fmt.Errorf("reading asn geoip: %w", err) - } + asn, asnErr := geoIPFromFile(f.asnPath) + country, ctryErr := geoIPFromFile(f.countryPath) - country, err := geoIPFromFile(f.countryPath) - if err != nil { - metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0) - - return fmt.Errorf("reading country geoip: %w", err) + if asnErr != nil || ctryErr != nil { + return errors.Join( + errors.Annotate(asnErr, "reading asn geoip: %w"), + errors.Annotate(ctryErr, "reading country geoip: %w"), + ) } err = f.resetSubnetMappings(ctx, asn, country) @@ -393,11 +408,6 @@ func (f *File) Refresh(ctx context.Context) (err error) { return fmt.Errorf("resetting geoip: %w", err) } - metrics.GeoIPUpdateTime.WithLabelValues(f.asnPath).SetToCurrentTime() - metrics.GeoIPUpdateStatus.WithLabelValues(f.asnPath).Set(1) - metrics.GeoIPUpdateTime.WithLabelValues(f.countryPath).SetToCurrentTime() - metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(1) - f.mu.Lock() defer f.mu.Unlock() @@ -427,7 +437,9 @@ func (f *File) resetSubnetMappings( ipv4, ipv6, locErr = f.resetLocationSubnets(ctx, asn, country) if locErr != nil { - metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0) + // TODO(a.garipov): Clarify which metrics should be updated in case + // of location. + f.metrics.HandleCountryUpdateStatus(ctx, locErr) locErr = fmt.Errorf("location subnet data: %w", locErr) } @@ -445,7 +457,7 @@ func (f *File) resetSubnetMappings( ipv4, ipv6, ctryErr = resetCountrySubnets(ctx, f.logger, country) if ctryErr != nil { - metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0) + f.metrics.HandleCountryUpdateStatus(ctx, ctryErr) ctryErr = fmt.Errorf("country subnet data: %w", ctryErr) } diff --git a/internal/geoip/file_test.go b/internal/geoip/file_test.go index 7fd3c5b..e3370f1 100644 --- a/internal/geoip/file_test.go +++ b/internal/geoip/file_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdcache" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" @@ -16,9 +15,6 @@ import ( "github.com/stretchr/testify/require" ) -// type check -var _ agdservice.Refresher = (*geoip.File)(nil) - // testTimeout is the common timeout for tests and contexts. const testTimeout = 1 * time.Second @@ -36,6 +32,7 @@ func newFile(tb testing.TB, conf *geoip.FileConfig) (g *geoip.File) { func TestFile_Data_cityDB(t *testing.T) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: cityPath, @@ -63,6 +60,7 @@ func TestFile_Data_cityDB(t *testing.T) { func TestFile_Data_countryDB(t *testing.T) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: countryPath, @@ -90,6 +88,7 @@ func TestFile_Data_countryDB(t *testing.T) { func TestFile_Data_hostCache(t *testing.T) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: cityPath, @@ -120,6 +119,7 @@ func TestFile_Data_hostCache(t *testing.T) { func TestFile_SubnetByLocation(t *testing.T) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: cityPath, @@ -199,6 +199,7 @@ var ( func BenchmarkFile_Data(b *testing.B) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: cityPath, @@ -251,6 +252,7 @@ func BenchmarkFile_Data(b *testing.B) { func BenchmarkFile_Refresh(b *testing.B) { conf := &geoip.FileConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: geoip.EmptyMetrics{}, CacheManager: agdcache.EmptyManager{}, ASNPath: asnPath, CountryPath: cityPath, diff --git a/internal/geoip/metrics.go b/internal/geoip/metrics.go new file mode 100644 index 0000000..15a0d1c --- /dev/null +++ b/internal/geoip/metrics.go @@ -0,0 +1,43 @@ +package geoip + +import "context" + +// Metrics is an interface that is used for the collection of the GeoIP database +// statistics. +type Metrics interface { + // HandleASNUpdateStatus updates the GeoIP ASN database update status. + HandleASNUpdateStatus(ctx context.Context, err error) + + // HandleCountryUpdateStatus updates the GeoIP countries database update + // status. + HandleCountryUpdateStatus(ctx context.Context, err error) + + // IncrementHostCacheLookups increments the number of GeoIP cache lookups + // for hostnames. + IncrementHostCacheLookups(ctx context.Context, hit bool) + + // IncrementIPCacheLookups increments the number of GeoIP cache lookups for + // IP addresses. + IncrementIPCacheLookups(ctx context.Context, hit bool) +} + +// EmptyMetrics is the implementation of the [Metrics] interface that does +// nothing. +type EmptyMetrics struct{} + +// type check +var _ Metrics = EmptyMetrics{} + +// HandleASNUpdateStatus implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) HandleASNUpdateStatus(_ context.Context, _ error) {} + +// HandleCountryUpdateStatus implements the [Metrics] interface for +// EmptyMetrics. +func (EmptyMetrics) HandleCountryUpdateStatus(_ context.Context, _ error) {} + +// IncrementHostCacheLookups implements the [Metrics] interface for +// EmptyMetrics. +func (EmptyMetrics) IncrementHostCacheLookups(_ context.Context, _ bool) {} + +// IncrementIPCacheLookups implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) IncrementIPCacheLookups(_ context.Context, _ bool) {} diff --git a/internal/metrics/billstat.go b/internal/metrics/billstat.go index 55381be..620534c 100644 --- a/internal/metrics/billstat.go +++ b/internal/metrics/billstat.go @@ -12,9 +12,9 @@ import ( // Billstat is the Prometheus-based implementation of the [billstat.Metrics] // interface. type Billstat struct { - // bufSize is a gauge with the total count of records in the local billing - // statistics database. - bufSize prometheus.Gauge + // recordCount is a gauge with the total count of records in the local + // billing statistics database. + recordCount prometheus.Gauge // uploadStatus is a gauge with the status of the last billing statistics // upload. @@ -33,15 +33,15 @@ type Billstat struct { // properly initialized [Billstat]. func NewBillstat(namespace string, reg prometheus.Registerer) (m *Billstat, err error) { const ( - bufSize = "buf_size" + recordCount = "buf_size" uploadStatus = "bill_stat_upload_status" uploadTimestamp = "bill_stat_upload_timestamp" uploadDuration = "bill_stat_upload_duration" ) m = &Billstat{ - bufSize: prometheus.NewGauge(prometheus.GaugeOpts{ - Name: bufSize, + recordCount: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: recordCount, Namespace: namespace, Subsystem: subsystemBillStat, Help: "Count of records in the local billstat DB.", @@ -69,8 +69,8 @@ func NewBillstat(namespace string, reg prometheus.Registerer) (m *Billstat, err var errs []error collectors := container.KeyValues[string, prometheus.Collector]{{ - Key: bufSize, - Value: m.bufSize, + Key: recordCount, + Value: m.recordCount, }, { Key: uploadStatus, Value: m.uploadStatus, @@ -96,20 +96,22 @@ func NewBillstat(namespace string, reg prometheus.Registerer) (m *Billstat, err return m, nil } -// BufferSizeSet implements the [billstat.Metrics] interface for *Billstat. -func (m *Billstat) BufferSizeSet(_ context.Context, n float64) { - m.bufSize.Set(n) +// SetRecordCount implements the [billstat.Metrics] interface for *Billstat. +func (m *Billstat) SetRecordCount(_ context.Context, count int) { + m.recordCount.Set(float64(count)) } // HandleUploadDuration implements the [billstat.Metrics] interface for // *Billstat. -func (m *Billstat) HandleUploadDuration(_ context.Context, dur float64, isSuccess bool) { +func (m *Billstat) HandleUploadDuration(_ context.Context, dur float64, err error) { m.uploadDuration.Observe(dur) - if isSuccess { - m.uploadTimestamp.SetToCurrentTime() - m.uploadStatus.Set(1) - } else { + if err != nil { m.uploadStatus.Set(0) + + return } + + m.uploadStatus.Set(1) + m.uploadTimestamp.SetToCurrentTime() } diff --git a/internal/metrics/connlimiter.go b/internal/metrics/connlimiter.go index 154d8d1..1bea677 100644 --- a/internal/metrics/connlimiter.go +++ b/internal/metrics/connlimiter.go @@ -1,45 +1,152 @@ package metrics import ( + "context" + "fmt" + "time" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -// ConnLimiterLimits is the gauge vector for showing the configured limits of -// the number of active stream-connections. -var ConnLimiterLimits = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "limits", - Namespace: namespace, - Subsystem: subsystemConnLimiter, - Help: `The current limits of the number of active stream-connections: ` + - `kind="stop" for the stopping limit and kind="resume" for the resuming one.`, -}, []string{"kind"}) +// ConnLimiterConnMetricsData is an alias for a structure that contains +// information about a stream-connection. All fields must not be empty. +// +// See [connlimiter.ConnMetricsData]. +type ConnLimiterConnMetricsData = struct { + // Addr is the address that the server is configured to listen on. + Addr string -// ConnLimiterActiveStreamConns is the gauge vector for the number of active -// stream-connections. -var ConnLimiterActiveStreamConns = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "active_stream_conns", - Namespace: namespace, - Subsystem: subsystemConnLimiter, - Help: `The number of currently active stream-connections.`, -}, []string{"name", "proto", "addr"}) + // Name is the name of the server. + Name string -// StreamConnWaitDuration is a histogram with the duration of waiting times for -// accepting stream connections. -var StreamConnWaitDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "stream_conn_wait_duration_seconds", - Subsystem: subsystemConnLimiter, - Namespace: namespace, - Help: "How long a stream connection waits for an accept, in seconds.", - Buckets: []float64{0.00001, 0.01, 0.1, 1, 10, 30, 60}, -}, []string{"name", "proto", "addr"}) + // Proto is the protocol of the server. + Proto string +} -// StreamConnLifeDuration is a histogram with the duration of lives of stream -// connections. -var StreamConnLifeDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ - Name: "stream_conn_life_duration_seconds", - Subsystem: subsystemConnLimiter, - Namespace: namespace, - Help: "How long a stream connection lives, in seconds.", - Buckets: []float64{0.1, 1, 5, 10, 30, 60}, -}, []string{"name", "proto", "addr"}) +// ConnLimiter is a Prometheus-based implementation of the [connlimiter.Metrics] +// interface. +type ConnLimiter struct { + // activeConnections is the metrics gauge of currently active stream + // connections. + activeConnections *prometheus.GaugeVec + + // lifeDuration is a histogram with the duration of stream connection lives. + lifeDuration *prometheus.HistogramVec + + // limits is the gauge vector for showing the configured limits for active + // stream connections. + limits *prometheus.GaugeVec + + // waitingDuration is a histogram with the duration of waiting times for + // accepting stream connections. + waitingDuration *prometheus.HistogramVec +} + +// NewConnLimiter registers the stream-connections metrics in reg and returns a +// properly initialized [*ConnLimiter]. +func NewConnLimiter(namespace string, reg prometheus.Registerer) (m *ConnLimiter, err error) { + const ( + activeConnections = "active_stream_conns" + lifeDuration = "stream_conn_life_duration_seconds" + limits = "limits" + waitingDuration = "stream_conn_wait_duration_seconds" + ) + + m = &ConnLimiter{ + activeConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: activeConnections, + Subsystem: subsystemConnLimiter, + Namespace: namespace, + Help: `The number of currently active stream-connections.`, + }, []string{"name", "proto", "addr"}), + lifeDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: lifeDuration, + Subsystem: subsystemConnLimiter, + Namespace: namespace, + Help: "How long a stream connection lives, in seconds.", + Buckets: []float64{0.1, 1, 5, 10, 30, 60}, + }, []string{"name", "proto", "addr"}), + limits: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: limits, + Namespace: namespace, + Subsystem: subsystemConnLimiter, + Help: `The current limits of the number of active stream-connections: ` + + `kind="stop" for the stopping limit and kind="resume" for the resuming one.`, + }, []string{"kind"}), + waitingDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: waitingDuration, + Subsystem: subsystemConnLimiter, + Namespace: namespace, + Help: "How long a stream connection waits for an accept, in seconds.", + Buckets: []float64{0.00001, 0.01, 0.1, 1, 10, 30, 60}, + }, []string{"name", "proto", "addr"}), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: activeConnections, + Value: m.activeConnections, + }, { + Key: lifeDuration, + Value: m.lifeDuration, + }, { + Key: limits, + Value: m.limits, + }, { + Key: waitingDuration, + Value: m.waitingDuration, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// IncrementActive implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) IncrementActive(_ context.Context, m *ConnLimiterConnMetricsData) { + c.activeConnections.WithLabelValues(m.Name, m.Proto, m.Addr).Inc() +} + +// DecrementActive implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) DecrementActive(_ context.Context, m *ConnLimiterConnMetricsData) { + c.activeConnections.WithLabelValues(m.Name, m.Proto, m.Addr).Dec() +} + +// ObserveLifeDuration implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) ObserveLifeDuration( + _ context.Context, + m *ConnLimiterConnMetricsData, + dur time.Duration, +) { + c.lifeDuration.WithLabelValues(m.Name, m.Proto, m.Addr).Observe(dur.Seconds()) +} + +// ObserveWaitingDuration implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) ObserveWaitingDuration( + _ context.Context, + m *ConnLimiterConnMetricsData, + dur time.Duration, +) { + c.waitingDuration.WithLabelValues(m.Name, m.Proto, m.Addr).Observe(dur.Seconds()) +} + +// SetStopLimit implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) SetStopLimit(_ context.Context, n uint64) { + c.limits.WithLabelValues("stop").Set(float64(n)) +} + +// SetResumeLimit implements the [Metrics] interface for *ConnLimiter. +func (c *ConnLimiter) SetResumeLimit(_ context.Context, n uint64) { + c.limits.WithLabelValues("resume").Set(float64(n)) +} diff --git a/internal/metrics/dnscheck.go b/internal/metrics/dnscheck.go index ddd2938..91405b1 100644 --- a/internal/metrics/dnscheck.go +++ b/internal/metrics/dnscheck.go @@ -1,25 +1,85 @@ package metrics import ( + "context" + "fmt" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -// DNSCheckRequestTotal is a counter with the total number of dnscheck requests. -// "type" can be "dns" or "http". "valid" can be "1" or "0". -var DNSCheckRequestTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "request_total", - Namespace: namespace, - Subsystem: subsystemDNSCheck, - Help: "The number of requests to the DNSCheck service.", -}, []string{"type", "valid"}) +// DNSCheck is the Prometheus-based implementation of the [dnscheck.Metrics] +// interface. +type DNSCheck struct { + // requestTotal is a counter with the total number of dnscheck requests + // labeled by type and validity. + requestTotal *prometheus.CounterVec -// DNSCheckErrorTotal is a gauge with the total number of errors occurred with -// dnscheck requests. "source" can be "dns" or "http". "type" is either -// "timeout", "ratelimit" or "other". -var DNSCheckErrorTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "error_total", - Namespace: namespace, - Subsystem: subsystemDNSCheck, - Help: "The total number of errors with requests to the DNSCheck service.", -}, []string{"source", "type"}) + // errorTotal is a gauge with the total number of errors occurred with + // dnscheck requests labeled by request and error types. + errorTotal *prometheus.GaugeVec +} + +// NewDNSCheck registers the DNS checker metrics in reg and returns a properly +// initialized [*DNSCheck]. +func NewDNSCheck(namespace string, reg prometheus.Registerer) (m *DNSCheck, err error) { + const ( + reqTotal = "request_total" + errTotal = "error_total" + ) + + m = &DNSCheck{ + requestTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: reqTotal, + Namespace: namespace, + Subsystem: subsystemDNSCheck, + Help: "The number of requests to the DNSCheck service.", + }, []string{"type", "valid"}), + errorTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: errTotal, + Namespace: namespace, + Subsystem: subsystemDNSCheck, + Help: "The total number of errors with requests to the DNSCheck service.", + }, []string{"source", "type"}), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: reqTotal, + Value: m.requestTotal, + }, { + Key: errTotal, + Value: m.errorTotal, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// HandleError implements the [dnscheck.Metrics] interface for *DNSCheck. +// reqType must be "dns" or "http". errType must be either "timeout", +// "ratelimit", "other" or an empty string. +func (m *DNSCheck) HandleError(_ context.Context, reqType, errType string) { + if errType == "" { + return + } + + m.errorTotal.WithLabelValues(reqType, errType).Inc() +} + +// HandleRequest implements the [dnscheck.Metrics] interface for *DNSCheck. +// reqType must be "dns" or "http". +func (m *DNSCheck) HandleRequest(_ context.Context, reqType string, isValid bool) { + m.requestTotal.WithLabelValues(reqType, BoolString(isValid)).Inc() +} diff --git a/internal/metrics/dnsdb.go b/internal/metrics/dnsdb.go index 1d7ad39..9f15508 100644 --- a/internal/metrics/dnsdb.go +++ b/internal/metrics/dnsdb.go @@ -1,34 +1,94 @@ package metrics import ( + "context" + "fmt" + "time" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -var ( - // DNSDBBufferSize is a gauge with the total count of records in the - // in-memory temporary buffer. - DNSDBBufferSize = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "buffer_size", - Namespace: namespace, - Subsystem: subsystemDNSDB, - Help: "Count of records in the in-memory buffer.", - }) +// DNSDB is the Prometheus-based implementation of the [dnsdb.Metrics] +// interface. +type DNSDB struct { + // recordCount is a gauge with the total count of records in the in-memory + // temporary buffer. + recordCount prometheus.Gauge - // DNSDBRotateTime is a gauge with the time when the DNSDB was rotated. - DNSDBRotateTime = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "rotate_time", - Namespace: namespace, - Subsystem: subsystemDNSDB, - Help: "Last time when the database was rotated.", - }) + // rotateTime is a gauge with the time at which the DNS database was + // rotated. + rotateTime prometheus.Gauge - // DNSDBSaveDuration is a histogram with the time elapsed on rotating the - // buffer for sending over HTTP. - DNSDBSaveDuration = promauto.NewHistogram(prometheus.HistogramOpts{ - Name: "save_duration", - Namespace: namespace, - Subsystem: subsystemDNSDB, - Help: "Time elapsed on rotating the buffer for sending over HTTP.", - }) -) + // rotateDuration is a histogram that stores the time elapsed during the + // rotation of the DNS database. + rotateDuration prometheus.Histogram +} + +// NewDNSDB registers the filtering rule metrics in reg and returns a properly +// initialized [*DNSDB]. +func NewDNSDB(namespace string, reg prometheus.Registerer) (m *DNSDB, err error) { + const ( + recordCount = "buffer_size" + rotateTime = "rotate_time" + rotateDuration = "save_duration" + ) + + m = &DNSDB{ + recordCount: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: recordCount, + Namespace: namespace, + Subsystem: subsystemDNSDB, + Help: "Count of records in the in-memory buffer.", + }), + rotateTime: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: rotateTime, + Namespace: namespace, + Subsystem: subsystemDNSDB, + Help: "Last time when the database was rotated.", + }), + rotateDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: rotateDuration, + Namespace: namespace, + Subsystem: subsystemDNSDB, + Help: "Time elapsed on rotating the buffer for sending over HTTP.", + }), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: recordCount, + Value: m.recordCount, + }, { + Key: rotateTime, + Value: m.rotateTime, + }, { + Key: rotateDuration, + Value: m.rotateDuration, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// SetRecordCount implements the [dnsdb.Metrics] interface for *DNSDB. +func (m *DNSDB) SetRecordCount(_ context.Context, count int) { + m.recordCount.Set(float64(count)) +} + +// ObserveRotation implements the [dnsdb.Metrics] interface for *DNSDB. +func (m *DNSDB) ObserveRotation(_ context.Context, dur time.Duration) { + m.rotateTime.SetToCurrentTime() + m.rotateDuration.Observe(dur.Seconds()) +} diff --git a/internal/metrics/ecscache.go b/internal/metrics/ecscache.go index 9608e9e..fa2641f 100644 --- a/internal/metrics/ecscache.go +++ b/internal/metrics/ecscache.go @@ -1,89 +1,122 @@ package metrics import ( + "context" + "fmt" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -// Cache size metrics. -var ( - // ecsCacheSize is the gauge with the total number of items in a cache. - // "supports" is either "yes" (the metric is for hostnames that support ECS) - // or "no" (the metric is for hostnames that don't support ECS). - ecsCacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "size", +// ECSCache is a Prometheus-based implementation of the [ecscache.Metrics] +// interface. +type ECSCache struct { + // supportedCount is a gauge with the total number of items in the cache for + // domain names that support ECS. + supportedCount prometheus.Gauge + + // unsupportedCount is a gauge with the total number of items in the cache + // for domain names that do not support ECS. + unsupportedCount prometheus.Gauge + + // hitsTotal is a counter with the total number of ECS cache hits. + hitsTotal prometheus.Counter + + // missesTotal is a counter with the total number of ECS cache misses. + missesTotal prometheus.Counter + + // supportedHitsTotal is a counter with the total number of ECS cache hits + // for hosts that support ECS. + supportedHitsTotal prometheus.Counter + + // supportedMissesTotal is a counter with the total number of ECS cache + // misses for hosts that support ECS. + supportedMissesTotal prometheus.Counter + + // unsupportedHitsTotal is a counter with the total number of ECS cache hits + // for hosts that don't support ECS. + unsupportedHitsTotal prometheus.Counter + + // unsupportedMissesTotal is a counter with the total number of ECS cache + // misses for hosts that don't support ECS. + unsupportedMissesTotal prometheus.Counter +} + +// NewECSCache registers the ECS cache metrics in reg and returns a properly +// initialized [*ECSCache]. +func NewECSCache(namespace string, reg prometheus.Registerer) (m *ECSCache, err error) { + const ( + size = "size" + cacheLookupTotal = "total_cache_lookups" + ) + + ecsCacheSize := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: size, Namespace: namespace, Subsystem: subsystemECSCache, Help: "The total number of items in the ECS cache.", }, []string{"supports"}) - - // ECSNoSupportCacheSize is the gauge with the total number of items in - // the cache for domain names that do not support ECS. - ECSNoSupportCacheSize = ecsCacheSize.With(prometheus.Labels{ - "supports": "no", - }) - - // ECSHasSupportCacheSize is the gauge with the total number of items in - // the cache for domain names that support ECS. - ECSHasSupportCacheSize = ecsCacheSize.With(prometheus.Labels{ - "supports": "yes", - }) -) - -// Lookup metrics. -var ( - // ecsCacheLookups is a counter with the total number of the ECS cache - // lookups. "hit" is either "1" (item found) or "0" (item not found). - // "supports" is either "yes" (the metric is for hostnames that support - // ECS), "no" (the metric is for hostnames that don't support ECS), or "all" - // (the metric is for all hosts). - ecsCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "total_cache_lookups", + ecsCacheLookups := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: cacheLookupTotal, Subsystem: subsystemECSCache, Namespace: namespace, - Help: "The total number of ECS cache lookups. " + - "hit=1 means that a cached item was found.", + Help: "The total number of ECS cache lookups. hit=1 means that a " + + "cached item was found.", }, []string{"supports", "hit"}) - // ECSCacheLookupTotalHits is a counter with the total number of ECS cache - // hits. - ECSCacheLookupTotalHits = ecsCacheLookups.With(prometheus.Labels{ - "hit": "1", - "supports": "all", - }) + m = &ECSCache{ + supportedCount: ecsCacheSize.WithLabelValues("yes"), + unsupportedCount: ecsCacheSize.WithLabelValues("no"), - // ECSCacheLookupHasSupportHits is a counter with the number of ECS cache - // hits for hosts that support ECS. - ECSCacheLookupHasSupportHits = ecsCacheLookups.With(prometheus.Labels{ - "hit": "1", - "supports": "yes", - }) + hitsTotal: ecsCacheLookups.WithLabelValues("all", "1"), + missesTotal: ecsCacheLookups.WithLabelValues("all", "0"), - // ECSCacheLookupNoSupportHits is a counter with the number of ECS cache - // hits for hosts that don't support ECS. - ECSCacheLookupNoSupportHits = ecsCacheLookups.With(prometheus.Labels{ - "hit": "1", - "supports": "no", - }) + supportedHitsTotal: ecsCacheLookups.WithLabelValues("yes", "1"), + supportedMissesTotal: ecsCacheLookups.WithLabelValues("yes", "0"), - // ECSCacheLookupTotalMisses is a counter with the total number of ECS cache - // misses. - ECSCacheLookupTotalMisses = ecsCacheLookups.With(prometheus.Labels{ - "hit": "0", - "supports": "all", - }) + unsupportedHitsTotal: ecsCacheLookups.WithLabelValues("no", "1"), + unsupportedMissesTotal: ecsCacheLookups.WithLabelValues("no", "0"), + } - // ECSCacheLookupHasSupportMisses is a counter with the number of ECS cache - // misses for hosts that support ECS. - ECSCacheLookupHasSupportMisses = ecsCacheLookups.With(prometheus.Labels{ - "hit": "0", - "supports": "yes", - }) + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: size, + Value: ecsCacheSize, + }, { + Key: cacheLookupTotal, + Value: ecsCacheLookups, + }} - // ECSCacheLookupNoSupportMisses is a counter with the number of ECS cache - // misses for hosts that don't support ECS. - ECSCacheLookupNoSupportMisses = ecsCacheLookups.With(prometheus.Labels{ - "hit": "0", - "supports": "no", - }) -) + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// SetElementsCount implements the [ecscache.Metrics] interface for *ECSCache. +func (m *ECSCache) SetElementsCount(_ context.Context, supportsECS bool, count int) { + if supportsECS { + m.supportedCount.Set(float64(count)) + } else { + m.unsupportedCount.Set(float64(count)) + } +} + +// IncrementLookups implements the [ecscache.Metrics] interface for *ECSCache. +func (m *ECSCache) IncrementLookups(_ context.Context, supportsECS, hit bool) { + IncrementCond(hit, m.hitsTotal, m.missesTotal) + if hit { + IncrementCond(supportsECS, m.supportedHitsTotal, m.unsupportedHitsTotal) + } else { + IncrementCond(supportsECS, m.supportedMissesTotal, m.unsupportedMissesTotal) + } +} diff --git a/internal/metrics/filter.go b/internal/metrics/filter.go index 2945932..be3d29b 100644 --- a/internal/metrics/filter.go +++ b/internal/metrics/filter.go @@ -8,109 +8,6 @@ import ( "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" -) - -var ( - // filterCustomCacheLookups is a counter with the total number of lookups to - // the custom filtering rules cache. "hit" is "1" if the filter was found - // in the cache, otherwise it is "0". - filterCustomCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "custom_cache_lookups", - Subsystem: subsystemFilter, - Namespace: namespace, - Help: "Total number of custom filters cache lookups.", - }, []string{"hit"}) - - // FilterCustomCacheLookupsHits is a counter with the total number of the - // custom filter cache hits. - FilterCustomCacheLookupsHits = filterCustomCacheLookups.With(prometheus.Labels{"hit": "1"}) - - // FilterCustomCacheLookupsMisses is a counter with the total number of the - // custom filter cache misses. - FilterCustomCacheLookupsMisses = filterCustomCacheLookups.With(prometheus.Labels{"hit": "0"}) -) - -var ( - // hashPrefixFilterCacheSize is a gauge with the total count of records in - // the HashStorage cache. - hashPrefixFilterCacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "hash_prefix_cache_size", - Subsystem: subsystemFilter, - Namespace: namespace, - Help: "The total number of items in the HashPrefixFilter cache.", - }, []string{"filter"}) - - // HashPrefixFilterSafeBrowsingCacheSize is the gauge with the total number - // of items in the cache for domain names for safe browsing filter. - HashPrefixFilterSafeBrowsingCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ - "filter": "safe_browsing", - }) - - // HashPrefixFilterAdultBlockingCacheSize is the gauge with the total number - // of items in the cache for domain names for adult blocking filter. - HashPrefixFilterAdultBlockingCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ - "filter": "adult_blocking", - }) - - // HashPrefixFilterNewRegDomainsCacheSize is the gauge with the total number - // of items in the cache for domain names for safe browsing newly registered - // domains filter. - HashPrefixFilterNewRegDomainsCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ - "filter": "newly_registered_domains", - }) - - // hashPrefixFilterCacheLookups is a counter with the total number of host - // cache lookups. "hit" is either "1" (item found) or "0". (item not found). - hashPrefixFilterCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "hash_prefix_cache_lookups", - Subsystem: subsystemFilter, - Namespace: namespace, - Help: "The number of HashPrefixFilter host cache lookups. " + - "hit=1 means that a cached item was found.", - }, []string{"hit", "filter"}) - - // HashPrefixFilterCacheSafeBrowsingHits is a counter with the total number - // of safe browsing filter cache hits. - HashPrefixFilterCacheSafeBrowsingHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "1", - "filter": "safe_browsing", - }) - - // HashPrefixFilterCacheSafeBrowsingMisses is a counter with the total number - // of safe browsing filter cache misses. - HashPrefixFilterCacheSafeBrowsingMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "0", - "filter": "safe_browsing", - }) - - // HashPrefixFilterCacheAdultBlockingHits is a counter with the total number - // of adult blocking filter cache hits. - HashPrefixFilterCacheAdultBlockingHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "1", - "filter": "adult_blocking", - }) - - // HashPrefixFilterCacheAdultBlockingMisses is a counter with the total number - // of adult blocking filter cache misses. - HashPrefixFilterCacheAdultBlockingMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "0", - "filter": "adult_blocking", - }) - - // HashPrefixFilterCacheNewRegDomainsHits is a counter with the total number - // of newly registered domains filter cache hits. - HashPrefixFilterCacheNewRegDomainsHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "1", - "filter": "newly_registered_domains", - }) - - // HashPrefixFilterCacheNewRegDomainsMisses is a counter with the total - // number of newly registered domains filter cache misses. - HashPrefixFilterCacheNewRegDomainsMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ - "hit": "0", - "filter": "newly_registered_domains", - }) ) // Filter is the Prometheus-based implementation of the [Filter] @@ -189,7 +86,7 @@ func NewFilter(namespace string, reg prometheus.Registerer) (m *Filter, err erro // SetFilterStatus implements the [filter.Metrics] interface for *Filter. func (m *Filter) SetFilterStatus( - ctx context.Context, + _ context.Context, id string, updTime time.Time, ruleCount int, diff --git a/internal/metrics/geoip.go b/internal/metrics/geoip.go index cec1c63..11abb08 100644 --- a/internal/metrics/geoip.go +++ b/internal/metrics/geoip.go @@ -1,67 +1,148 @@ package metrics import ( + "context" + "fmt" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -var ( - // GeoIPUpdateTime is a gauge with the timestamp of the last GeoIP database - // update. - GeoIPUpdateTime = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "update_time", - Subsystem: subsystemGeoIP, - Namespace: namespace, - Help: "The time when the GeoIP was loaded last time.", - }, []string{"path"}) +// GeoIP is the Prometheus-based implementation of the [geoip.Metrics] +// interface. +type GeoIP struct { + updateASNTimestamp prometheus.Gauge + updateASNStatus prometheus.Gauge - // GeoIPUpdateStatus is a gauge with the last GeoIP database update status. - // 1 means success, 0 means an error occurred. - GeoIPUpdateStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{ - Name: "update_status", - Subsystem: subsystemGeoIP, - Namespace: namespace, - Help: "Status of the last GeoIP update. 1 is okay, 0 means that something went wrong.", - }, []string{"path"}) -) + updateCountryTimestamp prometheus.Gauge + updateCountryStatus prometheus.Gauge -var ( - // geoIPCacheLookups is a counter with the total number of the GeoIP IP - // cache lookups. "hit" is either "1" (item found) or "0" (item not found). - geoIPCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "cache_lookups", + hostHits prometheus.Counter + hostMisses prometheus.Counter + + ipHits prometheus.Counter + ipMisses prometheus.Counter +} + +// NewGeoIP registers the GeoIP metrics in reg and returns a properly +// initialized GeoIP. +func NewGeoIP( + namespace string, + reg prometheus.Registerer, + asnPath string, + ctryPath string, +) (m *GeoIP, err error) { + const ( + updateStatus = "update_status" + updateTime = "update_time" + ipCacheLookups = "cache_lookups" + hostCacheLookups = "host_cache_lookups" + ) + + ipCacheLookupsCount := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: ipCacheLookups, Subsystem: subsystemGeoIP, Namespace: namespace, Help: "The number of GeoIP IP cache lookups. " + "hit=1 means that a cached item was found.", }, []string{"hit"}) - - // GeoIPCacheLookupsHits is a counter with the total number of the GeoIP IP - // cache hits. - GeoIPCacheLookupsHits = geoIPCacheLookups.With(prometheus.Labels{"hit": "1"}) - - // GeoIPCacheLookupsMisses is a counter with the total number of the GeoIP - // IP cache misses. - GeoIPCacheLookupsMisses = geoIPCacheLookups.With(prometheus.Labels{"hit": "0"}) -) - -var ( - // geoIPHostCacheLookups is a counter with the total number of the GeoIP - // hostname cache lookups. "hit" is either "1" (item found) or "0" (item - // not found). - geoIPHostCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "host_cache_lookups", + hostCacheLookupsCount := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: hostCacheLookups, Subsystem: subsystemGeoIP, Namespace: namespace, Help: "The number of GeoIP hostname cache lookups. " + "hit=1 means that a cached item was found.", }, []string{"hit"}) + updateTimestampGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: updateTime, + Subsystem: subsystemGeoIP, + Namespace: namespace, + Help: "The time when the GeoIP was loaded last time.", + }, []string{"path"}) + updateStatusGauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: updateStatus, + Subsystem: subsystemGeoIP, + Namespace: namespace, + Help: "Status of the last GeoIP update. " + + "1 is okay, 0 means that something went wrong.", + }, []string{"path"}) - // GeoIPHostCacheLookupsHits is a counter with the total number of the GeoIP - // hostname cache hits. - GeoIPHostCacheLookupsHits = geoIPHostCacheLookups.With(prometheus.Labels{"hit": "1"}) + m = &GeoIP{ + updateASNTimestamp: updateTimestampGauge.WithLabelValues(asnPath), + updateASNStatus: updateStatusGauge.WithLabelValues(asnPath), - // GeoIPHostCacheLookupsMisses is a counter with the total number of the - // GeoIP hostname cache misses. - GeoIPHostCacheLookupsMisses = geoIPHostCacheLookups.With(prometheus.Labels{"hit": "0"}) -) + updateCountryTimestamp: updateTimestampGauge.WithLabelValues(ctryPath), + updateCountryStatus: updateStatusGauge.WithLabelValues(ctryPath), + + hostHits: hostCacheLookupsCount.WithLabelValues("1"), + hostMisses: hostCacheLookupsCount.WithLabelValues("0"), + + ipHits: ipCacheLookupsCount.WithLabelValues("1"), + ipMisses: ipCacheLookupsCount.WithLabelValues("0"), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: updateStatus, + Value: updateStatusGauge, + }, { + Key: updateTime, + Value: updateTimestampGauge, + }, { + Key: ipCacheLookups, + Value: ipCacheLookupsCount, + }, { + Key: hostCacheLookups, + Value: hostCacheLookupsCount, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// HandleASNUpdateStatus implements the [geoip.Metrics] interface for *GeoIP. +func (m *GeoIP) HandleASNUpdateStatus(_ context.Context, err error) { + if err != nil { + m.updateASNStatus.Set(0) + + return + } + + m.updateASNStatus.Set(1) + m.updateASNTimestamp.SetToCurrentTime() +} + +// HandleCountryUpdateStatus implements the [geoip.Metrics] interface for +// *GeoIP. +func (m *GeoIP) HandleCountryUpdateStatus(_ context.Context, err error) { + if err != nil { + m.updateCountryStatus.Set(0) + + return + } + + m.updateCountryStatus.Set(1) + m.updateCountryTimestamp.SetToCurrentTime() +} + +// IncrementHostCacheLookups implements the [geoip.Metrics] interface for +// *GeoIP. +func (m *GeoIP) IncrementHostCacheLookups(_ context.Context, hit bool) { + IncrementCond(hit, m.hostHits, m.hostMisses) +} + +// IncrementIPCacheLookups implements the [geoip.Metrics] interface for *GeoIP. +func (m *GeoIP) IncrementIPCacheLookups(_ context.Context, hit bool) { + IncrementCond(hit, m.ipHits, m.ipMisses) +} diff --git a/internal/metrics/hashprefix.go b/internal/metrics/hashprefix.go new file mode 100644 index 0000000..0e938e2 --- /dev/null +++ b/internal/metrics/hashprefix.go @@ -0,0 +1,97 @@ +package metrics + +import ( + "context" + "fmt" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" + "github.com/prometheus/client_golang/prometheus" +) + +// HashPrefixFilter is the Prometheus-based implementation of the +// [hashprefix.Metrics] interface. +type HashPrefixFilter struct { + // cacheSize is a gauge with the total count of records in the HashStorage + // cache. + cacheSize prometheus.Gauge + + // hits is a counter of the total number of lookups to the HashStorage + // cache that succeeded. + hits prometheus.Counter + + // misses is a counter of the total number of lookups to the HashStorage + // cache that resulted in a miss. + misses prometheus.Counter +} + +// NewHashPrefixFilter registers the filtering metrics in reg and returns a +// properly initialized *HashPrefixFilter. filterName must be a valid label +// name. +func NewHashPrefixFilter( + namespace string, + filterName string, + reg prometheus.Registerer, +) (m *HashPrefixFilter, err error) { + const ( + cacheLookups = "hash_prefix_cache_lookups" + cacheSize = "hash_prefix_cache_size" + ) + + labels := prometheus.Labels{"filter": filterName} + + lookups := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: cacheLookups, + Subsystem: subsystemFilter, + Namespace: namespace, + Help: "Total number of lookups to HashPrefixFilter host cache lookups. " + + "Label hit is the lookup result, either 1 for hit or 0 for miss.", + ConstLabels: labels, + }, []string{"hit"}) + + m = &HashPrefixFilter{ + cacheSize: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: cacheSize, + Subsystem: subsystemFilter, + Namespace: namespace, + Help: "The total number of items in the HashPrefixFilter cache.", + ConstLabels: labels, + }), + hits: lookups.WithLabelValues("1"), + misses: lookups.WithLabelValues("0"), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: cacheSize, + Value: m.cacheSize, + }, { + Key: cacheLookups, + Value: lookups, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// IncrementLookups implements the [hashprefix.Metrics] interface for +// *HashPrefixFilter. +func (m *HashPrefixFilter) IncrementLookups(_ context.Context, hit bool) { + IncrementCond(hit, m.hits, m.misses) +} + +// UpdateCacheSize implements the [hashprefix.Metrics] interface for +// *HashPrefixFilter. +func (m *HashPrefixFilter) UpdateCacheSize(_ context.Context, size int) { + m.cacheSize.Set(float64(size)) +} diff --git a/internal/metrics/mainmw.go b/internal/metrics/mainmw.go index 3b25f95..254b267 100644 --- a/internal/metrics/mainmw.go +++ b/internal/metrics/mainmw.go @@ -3,6 +3,7 @@ package metrics import ( "context" "fmt" + "log/slog" "net/netip" "strconv" "time" @@ -63,8 +64,10 @@ type DefaultMainMiddleware struct { } // NewDefaultMainMiddleware registers the filtering-middleware metrics in reg -// and returns a properly initialized *DefaultMainMiddleware. +// and returns a properly initialized *DefaultMainMiddleware. All arguments +// must be set. func NewDefaultMainMiddleware( + logger *slog.Logger, namespace string, reg prometheus.Registerer, ) (m *DefaultMainMiddleware, err error) { @@ -144,7 +147,7 @@ func NewDefaultMainMiddleware( Help: "The approximate number of DNS users for the last 1 hour.", }) - m.userCounter = NewUserCounter(ipsLastHour, ipsLastDay) + m.userCounter = NewUserCounter(logger, ipsLastHour, ipsLastDay) var errs []error collectors := container.KeyValues[string, prometheus.Collector]{{ @@ -182,7 +185,7 @@ func NewDefaultMainMiddleware( } // OnRequest implements the [Metrics] interface for *DefaultMainMiddleware. -func (m *DefaultMainMiddleware) OnRequest(_ context.Context, rm *MainMiddlewareRequestMetrics) { +func (m *DefaultMainMiddleware) OnRequest(ctx context.Context, rm *MainMiddlewareRequestMetrics) { m.filteringDuration.Observe(rm.FilteringDuration.Seconds()) asnStr := strconv.FormatUint(uint64(rm.ASN), 10) @@ -197,5 +200,5 @@ func (m *DefaultMainMiddleware) OnRequest(_ context.Context, rm *MainMiddlewareR // Assume that ip is the remote IP address, which has already been unmapped // by [netutil.NetAddrToAddrPort]. ipArr := rm.RemoteIP.As16() - m.userCounter.Record(time.Now(), ipArr[:], false) + m.userCounter.Record(ctx, time.Now(), ipArr[:], false) } diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 73c2259..4b0ac86 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -3,13 +3,20 @@ package metrics_test import ( "github.com/AdguardTeam/AdGuardDNS/internal/backendpb" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" + "github.com/AdguardTeam/AdGuardDNS/internal/connlimiter" "github.com/AdguardTeam/AdGuardDNS/internal/consul" + "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" + "github.com/AdguardTeam/AdGuardDNS/internal/ecscache" "github.com/AdguardTeam/AdGuardDNS/internal/filter" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" + "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/AdGuardDNS/internal/querylog" "github.com/AdguardTeam/AdGuardDNS/internal/remotekv/rediskv" + "github.com/AdguardTeam/AdGuardDNS/internal/rulestat" "github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig" ) @@ -21,15 +28,22 @@ var ( _ backendpb.GRPCMetrics = (*metrics.BackendGRPC)(nil) _ backendpb.ProfileDBMetrics = (*metrics.BackendProfileDB)(nil) _ backendpb.RemoteKVMetrics = (*metrics.BackendRemoteKV)(nil) + _ connlimiter.Metrics = (*metrics.ConnLimiter)(nil) _ billstat.Metrics = (*metrics.Billstat)(nil) _ consul.Metrics = (*metrics.Allowlist)(nil) + _ dnscheck.Metrics = (*metrics.DNSCheck)(nil) _ dnsmsg.ClonerStat = metrics.ClonerStat{} _ dnssvc.MainMiddlewareMetrics = (*metrics.DefaultMainMiddleware)(nil) _ dnssvc.MainMiddlewareMetrics = metrics.MainMiddleware(nil) _ dnssvc.RatelimitMiddlewareMetrics = (*metrics.DefaultRatelimitMiddleware)(nil) _ dnssvc.RatelimitMiddlewareMetrics = metrics.RatelimitMiddleware(nil) + _ ecscache.Metrics = (*metrics.ECSCache)(nil) _ filter.Metrics = (*metrics.Filter)(nil) + _ geoip.Metrics = (*metrics.GeoIP)(nil) + _ hashprefix.Metrics = (*metrics.HashPrefixFilter)(nil) _ profiledb.Metrics = (*metrics.ProfileDB)(nil) + _ querylog.Metrics = (*metrics.QueryLog)(nil) _ rediskv.Metrics = (*metrics.RedisKV)(nil) + _ rulestat.Metrics = (*metrics.RuleStat)(nil) _ tlsconfig.Metrics = (*metrics.TLSConfig)(nil) ) diff --git a/internal/metrics/querylog.go b/internal/metrics/querylog.go index ef9d82a..deaf1ab 100644 --- a/internal/metrics/querylog.go +++ b/internal/metrics/querylog.go @@ -1,39 +1,100 @@ package metrics import ( + "context" + "fmt" + "time" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" + "github.com/c2h5oh/datasize" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -// QueryLogItemsCount is a counter with the total number of query log items -// written to the file. -var QueryLogItemsCount = promauto.NewCounter(prometheus.CounterOpts{ - Name: "items_total", - Subsystem: subsystemQueryLog, - Namespace: namespace, - Help: "The total number of query log items written.", -}) +// QueryLog is the Prometheus-based implementation of the [querylog.Metrics] +// interface. +type QueryLog struct { + itemsTotal prometheus.Counter + itemSize prometheus.Histogram + writeDuration prometheus.Histogram +} -// QueryLogItemSize is a histogram with the query log items size. -var QueryLogItemSize = promauto.NewHistogram(prometheus.HistogramOpts{ - Name: "items_size_bytes", - Subsystem: subsystemQueryLog, - Namespace: namespace, - Help: "A histogram with the query log items size.", - // Query log items are measured in bytes. Most of the space might be taken - // by domain names and filtering rules which might in theory be pretty long, - // therefore buckets are up to 2000 bytes. - Buckets: []float64{50, 100, 200, 300, 400, 600, 800, 1000, 2000}, -}) +// NewQueryLog creates a new Prometheus-based query log metrics collector. +func NewQueryLog(namespace string, reg prometheus.Registerer) (m *QueryLog, err error) { + const ( + itemsTotal = "items_total" + itemSize = "items_size_bytes" + writeDuration = "write_duration_seconds" + ) -// QueryLogWriteDuration is a histogram with the time spent writing a query log -// item to the file. -var QueryLogWriteDuration = promauto.NewHistogram(prometheus.HistogramOpts{ - Name: "write_duration_seconds", - Subsystem: subsystemQueryLog, - Namespace: namespace, - Help: "A histogram with the query log items size.", - // We chose buckets considering that writing to a file is a fast operation. - // If for some reason it takes over 1ms, something went terribly wrong. - Buckets: []float64{0.00001, 0.0001, 0.001, 0.01, 0.1, 1}, -}) + m = &QueryLog{ + itemsTotal: prometheus.NewCounter(prometheus.CounterOpts{ + Name: itemsTotal, + Subsystem: subsystemQueryLog, + Namespace: namespace, + Help: "The total number of query log items written.", + }), + itemSize: prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: itemSize, + Subsystem: subsystemQueryLog, + Namespace: namespace, + Help: "A histogram with the query log items size.", + // Query log items are measured in bytes. Most of the space might be + // taken by domain names and filtering rules which might in theory + // be pretty long, therefore buckets are up to 2000 bytes. + Buckets: []float64{50, 100, 200, 300, 400, 600, 800, 1000, 2000}, + }), + writeDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: writeDuration, + Subsystem: subsystemQueryLog, + Namespace: namespace, + Help: "A histogram with the query log items size.", + // We chose buckets considering that writing to a file is a fast + // operation. If for some reason it takes over 1ms, something went + // terribly wrong. + Buckets: []float64{0.00001, 0.0001, 0.001, 0.01, 0.1, 1}, + }), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: itemsTotal, + Value: m.itemsTotal, + }, { + Key: itemSize, + Value: m.itemSize, + }, { + Key: writeDuration, + Value: m.writeDuration, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// IncrementItemsCount implements the [querylog.Metrics] interface for +// *QueryLog. +func (m *QueryLog) IncrementItemsCount(_ context.Context) { + m.itemsTotal.Inc() +} + +// ObserveItemSize implements the [querylog.Metrics] interface for *QueryLog. +func (m *QueryLog) ObserveItemSize(_ context.Context, size datasize.ByteSize) { + m.itemSize.Observe(float64(size)) +} + +// ObserveWriteDuration implements the [querylog.Metrics] interface for +// *QueryLog. +func (m *QueryLog) ObserveWriteDuration(_ context.Context, dur time.Duration) { + m.writeDuration.Observe(dur.Seconds()) +} diff --git a/internal/metrics/remotekv.go b/internal/metrics/remotekv.go index e7a09d3..3f988ca 100644 --- a/internal/metrics/remotekv.go +++ b/internal/metrics/remotekv.go @@ -69,10 +69,10 @@ func NewRedisKV(namespace string, reg prometheus.Registerer) (m *RedisKV, err er } // UpdateMetrics implements the [rediskv.Metrics] interface for *RedisKV. -func (m *RedisKV) UpdateMetrics(_ context.Context, val uint, isSuccess bool) { +func (m *RedisKV) UpdateMetrics(_ context.Context, val uint, err error) { m.activeConnections.Set(float64(val)) - if !isSuccess { + if err != nil { m.errors.Inc() } } diff --git a/internal/metrics/rulestat.go b/internal/metrics/rulestat.go index 0cd4a1f..5d5712d 100644 --- a/internal/metrics/rulestat.go +++ b/internal/metrics/rulestat.go @@ -1,32 +1,99 @@ package metrics import ( + "context" + "fmt" + + "github.com/AdguardTeam/golibs/container" + "github.com/AdguardTeam/golibs/errors" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) -var ( - // RuleStatCacheSize is a gauge with the count of recorded rule hits not - // yet uploaded. - RuleStatCacheSize = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "stats_cache_size", - Namespace: namespace, - Subsystem: subsystemRuleStat, - Help: "Count of recorded rule hits not yet dumped.", - }) - // RuleStatUploadStatus is a gauge with the status of the last stats upload. - RuleStatUploadStatus = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "stats_upload_status", - Namespace: namespace, - Subsystem: subsystemRuleStat, - Help: "Status of the last stats upload.", - }) - // RuleStatUploadTimestamp is a gauge with the timestamp of the last stats - // upload. - RuleStatUploadTimestamp = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "stats_upload_timestamp", - Namespace: namespace, - Subsystem: subsystemRuleStat, - Help: "Time when stats were uploaded last time.", - }) -) +// RuleStat is the Prometheus-based implementation of the [rulestat.Metrics] +// interface. +type RuleStat struct { + // hitCount is a gauge with the count of recorded rule hits that have not + // yet been uploaded. + hitCount prometheus.Gauge + + // uploadStatus is a gauge with the status of the last stats upload. + uploadStatus prometheus.Gauge + + // uploadTimestamp is a gauge with the timestamp of the last successful + // stats upload. + uploadTimestamp prometheus.Gauge +} + +// NewRuleStat registers the filtering rule metrics in reg and returns a +// properly initialized [*RuleStat]. +func NewRuleStat(namespace string, reg prometheus.Registerer) (m *RuleStat, err error) { + const ( + // TODO(s.chzhen): Check if this is correct. + hitCount = "stats_cache_size" + uploadStatus = "stats_upload_status" + uploadTimestamp = "stats_upload_timestamp" + ) + + m = &RuleStat{ + hitCount: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: hitCount, + Namespace: namespace, + Subsystem: subsystemRuleStat, + Help: "Count of recorded rule hits not yet dumped.", + }), + uploadStatus: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: uploadStatus, + Namespace: namespace, + Subsystem: subsystemRuleStat, + Help: "Status of the last stats upload.", + }), + uploadTimestamp: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: uploadTimestamp, + Namespace: namespace, + Subsystem: subsystemRuleStat, + Help: "Time when stats were uploaded last time.", + }), + } + + var errs []error + collectors := container.KeyValues[string, prometheus.Collector]{{ + Key: hitCount, + Value: m.hitCount, + }, { + Key: uploadStatus, + Value: m.uploadStatus, + }, { + Key: uploadTimestamp, + Value: m.uploadTimestamp, + }} + + for _, c := range collectors { + err = reg.Register(c.Value) + if err != nil { + errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err)) + } + } + + if err = errors.Join(errs...); err != nil { + return nil, err + } + + return m, nil +} + +// SetHitCount implements the [rulestat.Metrics] interface for *RuleStat. +func (m *RuleStat) SetHitCount(_ context.Context, count int64) { + m.hitCount.Set(float64(count)) +} + +// HandleUploadStatus implements the [rulestat.Metrics] interface for *RuleStat. +func (m *RuleStat) HandleUploadStatus(_ context.Context, err error) { + if err != nil { + m.uploadStatus.Set(0) + + return + } + + m.uploadStatus.Set(1) + m.uploadTimestamp.SetToCurrentTime() +} diff --git a/internal/metrics/tls.go b/internal/metrics/tls.go index 5aed2e3..7c7edcf 100644 --- a/internal/metrics/tls.go +++ b/internal/metrics/tls.go @@ -210,8 +210,8 @@ func (m *TLSConfig) SetCertificateInfo(_ context.Context, algo, subj string, not // SetSessionTicketRotationStatus implements the [tlsconfig.Metrics] interface // for *TLSConfig. -func (m *TLSConfig) SetSessionTicketRotationStatus(_ context.Context, enabled bool) { - if !enabled { +func (m *TLSConfig) SetSessionTicketRotationStatus(_ context.Context, err error) { + if err != nil { m.sessionTicketsRotateStatus.Set(0) return diff --git a/internal/metrics/usercount.go b/internal/metrics/usercount.go index 49ace3b..0e304bd 100644 --- a/internal/metrics/usercount.go +++ b/internal/metrics/usercount.go @@ -1,11 +1,13 @@ package metrics import ( + "context" "fmt" + "log/slog" "sync" "time" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/timeutil" "github.com/axiomhq/hyperloglog" "github.com/prometheus/client_golang/prometheus" @@ -24,6 +26,9 @@ const ( // // TODO(a.garipov): Improve and move to golibs. type UserCounter struct { + // logger is used to report errors. + logger *slog.Logger + // lastHour is a gauge with an approximate number of DNS users for the // last 1 hour. lastHour prometheus.Gauge @@ -55,9 +60,11 @@ type UserCounter struct { } // NewUserCounter initializes and returns a properly initialized *UserCounter -// that uses the given gauges to estimate the user count. -func NewUserCounter(lastHour, lastDay prometheus.Gauge) (c *UserCounter) { +// that uses the given gauges to estimate the user count. All arguments must +// not be nil. +func NewUserCounter(logger *slog.Logger, lastHour, lastDay prometheus.Gauge) (c *UserCounter) { return &UserCounter{ + logger: logger, lastHour: lastHour, lastDay: lastDay, currentMu: &sync.Mutex{}, @@ -80,7 +87,7 @@ func NewUserCounter(lastHour, lastDay prometheus.Gauge) (c *UserCounter) { // synchronously. It is currently only used in tests. // // It currently assumes that it will be called at least once per day. -func (c *UserCounter) Record(now time.Time, userData []byte, syncUpdate bool) { +func (c *UserCounter) Record(ctx context.Context, now time.Time, userData []byte, syncUpdate bool) { hour, minute, _ := now.Clock() minuteOfDay := hour*minutesPerHour + minute @@ -98,9 +105,9 @@ func (c *UserCounter) Record(now time.Time, userData []byte, syncUpdate bool) { // counters, since there are none. if prevMinute != -1 { if syncUpdate { - c.updateCounters(prevMinute, hour, prevMinuteCounter) + c.updateCounters(ctx, prevMinute, hour, prevMinuteCounter) } else { - go c.updateCounters(prevMinute, hour, prevMinuteCounter) + go c.updateCounters(ctx, prevMinute, hour, prevMinuteCounter) } } } @@ -112,11 +119,12 @@ func (c *UserCounter) Record(now time.Time, userData []byte, syncUpdate bool) { // the metrics. It also clears all the stale hourly counters from the previous // day. func (c *UserCounter) updateCounters( + ctx context.Context, prevMinute int, currentHour int, prevMinuteCounter *hyperloglog.Sketch, ) { - defer log.OnPanic("metrics.userCounter.updateCounters") + defer slogutil.RecoverAndLog(ctx, c.logger) prevMinuteOfHour := prevMinute % minutesPerHour hourOfPrevMinute := prevMinute / minutesPerHour diff --git a/internal/metrics/usercount_test.go b/internal/metrics/usercount_test.go index d29c610..3788c81 100644 --- a/internal/metrics/usercount_test.go +++ b/internal/metrics/usercount_test.go @@ -1,6 +1,8 @@ package metrics_test import ( + "context" + "math/rand/v2" "net" "net/netip" "strconv" @@ -8,15 +10,15 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/rand" ) -// Use a constant seed to make the test reproducible. -const randSeed = 1234 +// Use the same seed to make the test reproducible. +var randSeed = [32]byte([]byte("01234567890123456789012345678901")) // Gauges for tests. var ( @@ -24,10 +26,13 @@ var ( testLastDay = prometheus.NewGauge(prometheus.GaugeOpts{}) ) +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() + // randIPBytes is a test helper that returns a pseudorandomly generated // IP-address bytes. fam must be either [netutil.AddrFamilyIPv4] or // [netutil.AddrFamilyIPv6]. -func randIPBytes(t testing.TB, r *rand.Rand, fam netutil.AddrFamily) (ipBytes []byte) { +func randIPBytes(t testing.TB, src *rand.ChaCha8, fam netutil.AddrFamily) (ipBytes []byte) { t.Helper() switch fam { @@ -39,7 +44,7 @@ func randIPBytes(t testing.TB, r *rand.Rand, fam netutil.AddrFamily) (ipBytes [] t.Fatalf("unexpected address family %q", fam) } - n, err := r.Read(ipBytes) + n, err := src.Read(ipBytes) require.NoError(t, err) require.Equal(t, len(ipBytes), n) @@ -166,11 +171,12 @@ func TestUserCounter_Estimate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - r := rand.New(rand.NewSource(randSeed)) - c := metrics.NewUserCounter(testLastHour, testLastDay) + src := rand.NewChaCha8(randSeed) + c := metrics.NewUserCounter(testLogger, testLastHour, testLastDay) + ctx := context.Background() for _, now := range tc.nows { - c.Record(now, randIPBytes(t, r, netutil.AddrFamilyIPv6), true) + c.Record(ctx, now, randIPBytes(t, src, netutil.AddrFamilyIPv6), true) } hourly, daily := c.Estimate() @@ -183,17 +189,17 @@ func TestUserCounter_Estimate(t *testing.T) { func TestUserCounter_simple(t *testing.T) { const ipsPerMinute = 2 - src := rand.NewSource(randSeed) - r := rand.New(src) + src := rand.NewChaCha8(randSeed) - c := metrics.NewUserCounter(testLastHour, testLastDay) + c := metrics.NewUserCounter(testLogger, testLastHour, testLastDay) now := time.Unix(0, 0).UTC() for d, h := now.Day(), now.Hour(); now.Day() == d; h = now.Hour() { t.Run(strconv.Itoa(now.Hour()), func(t *testing.T) { + ctx := context.Background() for ; now.Hour() == h; now = now.Add(1 * time.Minute) { for range ipsPerMinute { - c.Record(now, randIPBytes(t, r, netutil.AddrFamilyIPv4), true) + c.Record(ctx, now, randIPBytes(t, src, netutil.AddrFamilyIPv4), true) } } @@ -212,23 +218,24 @@ var uint64Sink uint64 func BenchmarkUserCounter_Estimate(b *testing.B) { const n = 100 + ctx := context.Background() zeroTime := time.Unix(0, 0).UTC() - sparseCounter := metrics.NewUserCounter(testLastHour, testLastDay) + sparseCounter := metrics.NewUserCounter(testLogger, testLastHour, testLastDay) for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) { - r := rand.New(rand.NewSource(randSeed)) + src := rand.NewChaCha8(randSeed) for range n { - sparseCounter.Record(now, randIPBytes(b, r, netutil.AddrFamilyIPv6), true) + sparseCounter.Record(ctx, now, randIPBytes(b, src, netutil.AddrFamilyIPv6), true) } } - seqCounter := metrics.NewUserCounter(testLastHour, testLastDay) + seqCounter := metrics.NewUserCounter(testLogger, testLastHour, testLastDay) for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) { addr := netip.AddrFrom16([16]byte{}) for range n { addr = addr.Next() addrArr := addr.As16() - seqCounter.Record(now, addrArr[:], true) + seqCounter.Record(ctx, now, addrArr[:], true) } } diff --git a/internal/optlog/optlog.go b/internal/optlog/optlog.go deleted file mode 100644 index 8f03e90..0000000 --- a/internal/optlog/optlog.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package optlog contains ugly hacks to make debug logs allocate less when -// debug mode is not enabled. Add all such hacks here to make sure that we keep -// track of them. -package optlog - -import ( - "github.com/AdguardTeam/golibs/log" -) - -// Debug3 is an ugly hack to prevent [log.Debug] from allocating. -func Debug3[T1, T2, T3 any](msg string, arg1 T1, arg2 T2, arg3 T3) { - if log.GetLevel() >= log.DEBUG { - log.Debug(msg, arg1, arg2, arg3) - } -} diff --git a/internal/profiledb/internal/filecachepb/filecache.pb.go b/internal/profiledb/internal/filecachepb/filecache.pb.go index e926633..d29155d 100644 --- a/internal/profiledb/internal/filecachepb/filecache.pb.go +++ b/internal/profiledb/internal/filecachepb/filecache.pb.go @@ -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: filecache.proto package filecachepb @@ -13,6 +13,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -23,14 +24,13 @@ const ( ) type FileCache 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"` + Profiles []*Profile `protobuf:"bytes,2,rep,name=profiles,proto3" json:"profiles,omitempty"` + Devices []*Device `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices,omitempty"` + Version int32 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields - - SyncTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=sync_time,json=syncTime,proto3" json:"sync_time,omitempty"` - Profiles []*Profile `protobuf:"bytes,2,rep,name=profiles,proto3" json:"profiles,omitempty"` - Devices []*Device `protobuf:"bytes,3,rep,name=devices,proto3" json:"devices,omitempty"` - Version int32 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FileCache) Reset() { @@ -92,13 +92,10 @@ func (x *FileCache) GetVersion() int32 { } type Profile struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - FilterConfig *FilterConfig `protobuf:"bytes,1,opt,name=filter_config,json=filterConfig,proto3" json:"filter_config,omitempty"` - Access *Access `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` - // Types that are assignable to BlockingMode: + state protoimpl.MessageState `protogen:"open.v1"` + FilterConfig *FilterConfig `protobuf:"bytes,1,opt,name=filter_config,json=filterConfig,proto3" json:"filter_config,omitempty"` + Access *Access `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"` + // Types that are valid to be assigned to BlockingMode: // // *Profile_BlockingModeCustomIp // *Profile_BlockingModeNxdomain @@ -117,6 +114,8 @@ type Profile struct { FilteringEnabled bool `protobuf:"varint,16,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"` IpLogEnabled bool `protobuf:"varint,17,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"` QueryLogEnabled bool `protobuf:"varint,18,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Profile) Reset() { @@ -163,37 +162,45 @@ func (x *Profile) GetAccess() *Access { return nil } -func (m *Profile) GetBlockingMode() isProfile_BlockingMode { - if m != nil { - return m.BlockingMode +func (x *Profile) GetBlockingMode() isProfile_BlockingMode { + if x != nil { + return x.BlockingMode } return nil } func (x *Profile) GetBlockingModeCustomIp() *BlockingModeCustomIP { - if x, ok := x.GetBlockingMode().(*Profile_BlockingModeCustomIp); ok { - return x.BlockingModeCustomIp + if x != nil { + if x, ok := x.BlockingMode.(*Profile_BlockingModeCustomIp); ok { + return x.BlockingModeCustomIp + } } return nil } func (x *Profile) GetBlockingModeNxdomain() *BlockingModeNXDOMAIN { - if x, ok := x.GetBlockingMode().(*Profile_BlockingModeNxdomain); ok { - return x.BlockingModeNxdomain + if x != nil { + if x, ok := x.BlockingMode.(*Profile_BlockingModeNxdomain); ok { + return x.BlockingModeNxdomain + } } return nil } func (x *Profile) GetBlockingModeNullIp() *BlockingModeNullIP { - if x, ok := x.GetBlockingMode().(*Profile_BlockingModeNullIp); ok { - return x.BlockingModeNullIp + if x != nil { + if x, ok := x.BlockingMode.(*Profile_BlockingModeNullIp); ok { + return x.BlockingModeNullIp + } } return nil } func (x *Profile) GetBlockingModeRefused() *BlockingModeREFUSED { - if x, ok := x.GetBlockingMode().(*Profile_BlockingModeRefused); ok { - return x.BlockingModeRefused + if x != nil { + if x, ok := x.BlockingMode.(*Profile_BlockingModeRefused); ok { + return x.BlockingModeRefused + } } return nil } @@ -311,14 +318,13 @@ func (*Profile_BlockingModeNullIp) isProfile_BlockingMode() {} func (*Profile_BlockingModeRefused) isProfile_BlockingMode() {} type FilterConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Custom *FilterConfig_Custom `protobuf:"bytes,1,opt,name=custom,proto3" json:"custom,omitempty"` + Parental *FilterConfig_Parental `protobuf:"bytes,2,opt,name=parental,proto3" json:"parental,omitempty"` + RuleList *FilterConfig_RuleList `protobuf:"bytes,3,opt,name=rule_list,json=ruleList,proto3" json:"rule_list,omitempty"` + SafeBrowsing *FilterConfig_SafeBrowsing `protobuf:"bytes,4,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"` unknownFields protoimpl.UnknownFields - - Custom *FilterConfig_Custom `protobuf:"bytes,1,opt,name=custom,proto3" json:"custom,omitempty"` - Parental *FilterConfig_Parental `protobuf:"bytes,2,opt,name=parental,proto3" json:"parental,omitempty"` - RuleList *FilterConfig_RuleList `protobuf:"bytes,3,opt,name=rule_list,json=ruleList,proto3" json:"rule_list,omitempty"` - SafeBrowsing *FilterConfig_SafeBrowsing `protobuf:"bytes,4,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FilterConfig) Reset() { @@ -380,12 +386,11 @@ func (x *FilterConfig) GetSafeBrowsing() *FilterConfig_SafeBrowsing { } type DayInterval struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + End uint32 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` unknownFields protoimpl.UnknownFields - - Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` - End uint32 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DayInterval) Reset() { @@ -433,12 +438,11 @@ func (x *DayInterval) GetEnd() uint32 { } type BlockingModeCustomIP struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Ipv4 [][]byte `protobuf:"bytes,1,rep,name=ipv4,proto3" json:"ipv4,omitempty"` + Ipv6 [][]byte `protobuf:"bytes,2,rep,name=ipv6,proto3" json:"ipv6,omitempty"` unknownFields protoimpl.UnknownFields - - Ipv4 [][]byte `protobuf:"bytes,1,rep,name=ipv4,proto3" json:"ipv4,omitempty"` - Ipv6 [][]byte `protobuf:"bytes,2,rep,name=ipv6,proto3" json:"ipv6,omitempty"` + sizeCache protoimpl.SizeCache } func (x *BlockingModeCustomIP) Reset() { @@ -486,9 +490,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() { @@ -522,9 +526,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() { @@ -558,9 +562,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() { @@ -594,10 +598,7 @@ func (*BlockingModeREFUSED) Descriptor() ([]byte, []int) { } type Device struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` Authentication *AuthenticationSettings `protobuf:"bytes,6,opt,name=authentication,proto3" json:"authentication,omitempty"` DeviceId string `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` DeviceName string `protobuf:"bytes,3,opt,name=device_name,json=deviceName,proto3" json:"device_name,omitempty"` @@ -605,6 +606,8 @@ type Device struct { LinkedIp []byte `protobuf:"bytes,2,opt,name=linked_ip,json=linkedIp,proto3" json:"linked_ip,omitempty"` DedicatedIps [][]byte `protobuf:"bytes,4,rep,name=dedicated_ips,json=dedicatedIps,proto3" json:"dedicated_ips,omitempty"` FilteringEnabled bool `protobuf:"varint,5,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Device) Reset() { @@ -687,15 +690,14 @@ func (x *Device) GetFilteringEnabled() bool { } type Access struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - AllowlistAsn []uint32 `protobuf:"varint,4,rep,packed,name=allowlist_asn,json=allowlistAsn,proto3" json:"allowlist_asn,omitempty"` - AllowlistCidr []*CidrRange `protobuf:"bytes,1,rep,name=allowlist_cidr,json=allowlistCidr,proto3" json:"allowlist_cidr,omitempty"` - BlocklistAsn []uint32 `protobuf:"varint,5,rep,packed,name=blocklist_asn,json=blocklistAsn,proto3" json:"blocklist_asn,omitempty"` - BlocklistCidr []*CidrRange `protobuf:"bytes,2,rep,name=blocklist_cidr,json=blocklistCidr,proto3" json:"blocklist_cidr,omitempty"` - BlocklistDomainRules []string `protobuf:"bytes,3,rep,name=blocklist_domain_rules,json=blocklistDomainRules,proto3" json:"blocklist_domain_rules,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + AllowlistAsn []uint32 `protobuf:"varint,4,rep,packed,name=allowlist_asn,json=allowlistAsn,proto3" json:"allowlist_asn,omitempty"` + AllowlistCidr []*CidrRange `protobuf:"bytes,1,rep,name=allowlist_cidr,json=allowlistCidr,proto3" json:"allowlist_cidr,omitempty"` + BlocklistAsn []uint32 `protobuf:"varint,5,rep,packed,name=blocklist_asn,json=blocklistAsn,proto3" json:"blocklist_asn,omitempty"` + BlocklistCidr []*CidrRange `protobuf:"bytes,2,rep,name=blocklist_cidr,json=blocklistCidr,proto3" json:"blocklist_cidr,omitempty"` + BlocklistDomainRules []string `protobuf:"bytes,3,rep,name=blocklist_domain_rules,json=blocklistDomainRules,proto3" json:"blocklist_domain_rules,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Access) Reset() { @@ -764,12 +766,11 @@ func (x *Access) GetBlocklistDomainRules() []string { } 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() { @@ -817,15 +818,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() { @@ -865,16 +865,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 } @@ -890,13 +892,12 @@ type AuthenticationSettings_PasswordHashBcrypt struct { func (*AuthenticationSettings_PasswordHashBcrypt) isAuthenticationSettings_DohPasswordHash() {} type Ratelimiter struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ClientCidr []*CidrRange `protobuf:"bytes,1,rep,name=client_cidr,json=clientCidr,proto3" json:"client_cidr,omitempty"` + Rps uint32 `protobuf:"varint,2,opt,name=rps,proto3" json:"rps,omitempty"` + Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` unknownFields protoimpl.UnknownFields - - ClientCidr []*CidrRange `protobuf:"bytes,1,rep,name=client_cidr,json=clientCidr,proto3" json:"client_cidr,omitempty"` - Rps uint32 `protobuf:"varint,2,opt,name=rps,proto3" json:"rps,omitempty"` - Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Ratelimiter) Reset() { @@ -951,14 +952,11 @@ func (x *Ratelimiter) GetEnabled() bool { } type FilterConfig_Custom struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rules []string `protobuf:"bytes,3,rep,name=rules,proto3" json:"rules,omitempty"` + Enabled bool `protobuf:"varint,4,opt,name=enabled,proto3" json:"enabled,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - UpdateTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` - Rules []string `protobuf:"bytes,3,rep,name=rules,proto3" json:"rules,omitempty"` - Enabled bool `protobuf:"varint,4,opt,name=enabled,proto3" json:"enabled,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FilterConfig_Custom) Reset() { @@ -991,20 +989,6 @@ func (*FilterConfig_Custom) Descriptor() ([]byte, []int) { return file_filecache_proto_rawDescGZIP(), []int{2, 0} } -func (x *FilterConfig_Custom) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *FilterConfig_Custom) GetUpdateTime() *timestamppb.Timestamp { - if x != nil { - return x.UpdateTime - } - return nil -} - func (x *FilterConfig_Custom) GetRules() []string { if x != nil { return x.Rules @@ -1020,16 +1004,15 @@ func (x *FilterConfig_Custom) GetEnabled() bool { } type FilterConfig_Parental struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` PauseSchedule *FilterConfig_Schedule `protobuf:"bytes,1,opt,name=pause_schedule,json=pauseSchedule,proto3" json:"pause_schedule,omitempty"` BlockedServices []string `protobuf:"bytes,2,rep,name=blocked_services,json=blockedServices,proto3" json:"blocked_services,omitempty"` Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` AdultBlockingEnabled bool `protobuf:"varint,4,opt,name=adult_blocking_enabled,json=adultBlockingEnabled,proto3" json:"adult_blocking_enabled,omitempty"` SafeSearchGeneralEnabled bool `protobuf:"varint,5,opt,name=safe_search_general_enabled,json=safeSearchGeneralEnabled,proto3" json:"safe_search_general_enabled,omitempty"` SafeSearchYoutubeEnabled bool `protobuf:"varint,6,opt,name=safe_search_youtube_enabled,json=safeSearchYoutubeEnabled,proto3" json:"safe_search_youtube_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FilterConfig_Parental) Reset() { @@ -1105,12 +1088,11 @@ func (x *FilterConfig_Parental) GetSafeSearchYoutubeEnabled() bool { } type FilterConfig_Schedule struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Week *FilterConfig_WeeklySchedule `protobuf:"bytes,1,opt,name=week,proto3" json:"week,omitempty"` + TimeZone string `protobuf:"bytes,2,opt,name=time_zone,json=timeZone,proto3" json:"time_zone,omitempty"` unknownFields protoimpl.UnknownFields - - Week *FilterConfig_WeeklySchedule `protobuf:"bytes,1,opt,name=week,proto3" json:"week,omitempty"` - TimeZone string `protobuf:"bytes,2,opt,name=time_zone,json=timeZone,proto3" json:"time_zone,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FilterConfig_Schedule) Reset() { @@ -1158,17 +1140,16 @@ func (x *FilterConfig_Schedule) GetTimeZone() string { } type FilterConfig_WeeklySchedule struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Mon *DayInterval `protobuf:"bytes,1,opt,name=mon,proto3" json:"mon,omitempty"` + Tue *DayInterval `protobuf:"bytes,2,opt,name=tue,proto3" json:"tue,omitempty"` + Wed *DayInterval `protobuf:"bytes,3,opt,name=wed,proto3" json:"wed,omitempty"` + Thu *DayInterval `protobuf:"bytes,4,opt,name=thu,proto3" json:"thu,omitempty"` + Fri *DayInterval `protobuf:"bytes,5,opt,name=fri,proto3" json:"fri,omitempty"` + Sat *DayInterval `protobuf:"bytes,6,opt,name=sat,proto3" json:"sat,omitempty"` + Sun *DayInterval `protobuf:"bytes,7,opt,name=sun,proto3" json:"sun,omitempty"` unknownFields protoimpl.UnknownFields - - Mon *DayInterval `protobuf:"bytes,1,opt,name=mon,proto3" json:"mon,omitempty"` - Tue *DayInterval `protobuf:"bytes,2,opt,name=tue,proto3" json:"tue,omitempty"` - Wed *DayInterval `protobuf:"bytes,3,opt,name=wed,proto3" json:"wed,omitempty"` - Thu *DayInterval `protobuf:"bytes,4,opt,name=thu,proto3" json:"thu,omitempty"` - Fri *DayInterval `protobuf:"bytes,5,opt,name=fri,proto3" json:"fri,omitempty"` - Sat *DayInterval `protobuf:"bytes,6,opt,name=sat,proto3" json:"sat,omitempty"` - Sun *DayInterval `protobuf:"bytes,7,opt,name=sun,proto3" json:"sun,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FilterConfig_WeeklySchedule) Reset() { @@ -1251,12 +1232,11 @@ func (x *FilterConfig_WeeklySchedule) GetSun() *DayInterval { } type FilterConfig_RuleList struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` unknownFields protoimpl.UnknownFields - - Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + sizeCache protoimpl.SizeCache } func (x *FilterConfig_RuleList) Reset() { @@ -1304,13 +1284,12 @@ func (x *FilterConfig_RuleList) GetEnabled() bool { } type FilterConfig_SafeBrowsing struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - DangerousDomainsEnabled bool `protobuf:"varint,2,opt,name=dangerous_domains_enabled,json=dangerousDomainsEnabled,proto3" json:"dangerous_domains_enabled,omitempty"` - NewlyRegisteredDomainsEnabled bool `protobuf:"varint,3,opt,name=newly_registered_domains_enabled,json=newlyRegisteredDomainsEnabled,proto3" json:"newly_registered_domains_enabled,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + DangerousDomainsEnabled bool `protobuf:"varint,2,opt,name=dangerous_domains_enabled,json=dangerousDomainsEnabled,proto3" json:"dangerous_domains_enabled,omitempty"` + NewlyRegisteredDomainsEnabled bool `protobuf:"varint,3,opt,name=newly_registered_domains_enabled,json=newlyRegisteredDomainsEnabled,proto3" json:"newly_registered_domains_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FilterConfig_SafeBrowsing) Reset() { @@ -1366,7 +1345,7 @@ func (x *FilterConfig_SafeBrowsing) GetNewlyRegisteredDomainsEnabled() bool { var File_filecache_proto protoreflect.FileDescriptor -var file_filecache_proto_rawDesc = []byte{ +var file_filecache_proto_rawDesc = string([]byte{ 0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, @@ -1449,7 +1428,7 @@ var file_filecache_proto_rawDesc = []byte{ 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, - 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xeb, 0x0a, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa9, 0x0a, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, @@ -1466,154 +1445,150 @@ var file_filecache_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x0c, 0x73, 0x61, 0x66, 0x65, 0x42, - 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x1a, 0x85, 0x01, 0x0a, 0x06, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, - 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x1a, - 0xcc, 0x02, 0x0a, 0x08, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x12, 0x47, 0x0a, 0x0e, - 0x70, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, - 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x70, 0x61, 0x75, 0x73, 0x65, 0x53, 0x63, 0x68, - 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, - 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x64, - 0x75, 0x6c, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x61, 0x64, 0x75, 0x6c, - 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x3d, 0x0a, 0x1b, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x3d, 0x0a, 0x1b, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x79, - 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x59, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x1a, 0x63, - 0x0a, 0x08, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x3a, 0x0a, 0x04, 0x77, 0x65, - 0x65, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x52, 0x04, 0x77, 0x65, 0x65, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, - 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, - 0x6f, 0x6e, 0x65, 0x1a, 0xb6, 0x02, 0x0a, 0x0e, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, - 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x6d, 0x6f, 0x6e, - 0x12, 0x28, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x74, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x03, 0x77, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, - 0x03, 0x77, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x03, 0x74, 0x68, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x1a, 0x44, 0x0a, 0x06, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x1a, 0xcc, 0x02, + 0x0a, 0x08, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x61, + 0x75, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x70, 0x61, 0x75, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x64, 0x75, 0x6c, + 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x61, 0x64, 0x75, 0x6c, 0x74, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3d, + 0x0a, 0x1b, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3d, 0x0a, + 0x1b, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x79, 0x6f, 0x75, + 0x74, 0x75, 0x62, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x18, 0x73, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x59, 0x6f, + 0x75, 0x74, 0x75, 0x62, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x1a, 0x63, 0x0a, 0x08, + 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x3a, 0x0a, 0x04, 0x77, 0x65, 0x65, 0x6b, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x64, 0x62, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x04, + 0x77, 0x65, 0x65, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, + 0x65, 0x1a, 0xb6, 0x02, 0x0a, 0x0e, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x53, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, - 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x28, - 0x0a, 0x03, 0x66, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, + 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x6d, 0x6f, 0x6e, 0x12, 0x28, + 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x28, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, - 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x73, - 0x61, 0x74, 0x12, 0x28, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x76, 0x61, 0x6c, 0x52, 0x03, 0x74, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, + 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x77, + 0x65, 0x64, 0x12, 0x28, 0x0a, 0x03, 0x74, 0x68, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x73, 0x75, 0x6e, 0x1a, 0x36, 0x0a, 0x08, - 0x52, 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x1a, 0xad, 0x01, 0x0a, 0x0c, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, - 0x77, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x3a, 0x0a, 0x19, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x17, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x20, 0x6e, - 0x65, 0x77, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x6e, 0x65, 0x77, 0x6c, 0x79, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3e, 0x0a, 0x14, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, 0x4d, - 0x41, 0x49, 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, - 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, - 0x22, 0xa6, 0x02, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x61, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x68, 0x75, 0x6d, 0x61, 0x6e, 0x5f, 0x69, 0x64, - 0x5f, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x75, - 0x6d, 0x61, 0x6e, 0x49, 0x64, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, - 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, - 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, - 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x11, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x82, 0x02, 0x0a, 0x06, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73, - 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 0x12, 0x3b, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, - 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x28, 0x0a, 0x03, + 0x66, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x28, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, + 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x73, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x03, 0x73, 0x75, 0x6e, 0x1a, 0x36, 0x0a, 0x08, 0x52, 0x75, + 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x1a, 0xad, 0x01, 0x0a, 0x0c, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, + 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3a, 0x0a, + 0x19, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x17, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x20, 0x6e, 0x65, 0x77, + 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x1d, 0x6e, 0x65, 0x77, 0x6c, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x44, 0x61, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x49, + 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, 0x4d, 0x41, 0x49, + 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, + 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, 0x22, 0xa6, + 0x02, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x61, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x41, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x68, 0x75, 0x6d, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x6c, + 0x6f, 0x77, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x75, 0x6d, 0x61, + 0x6e, 0x49, 0x64, 0x4c, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, + 0x65, 0x64, 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, 0x69, 0x6e, + 0x6b, 0x65, 0x64, 0x49, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, + 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x82, 0x02, 0x0a, 0x06, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x5f, + 0x61, 0x73, 0x6e, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 0x12, 0x3b, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x43, 0x69, 0x64, 0x72, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, + 0x43, 0x69, 0x64, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 0x12, 0x3b, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x43, 0x69, - 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, - 0x73, 0x74, 0x43, 0x69, 0x64, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, - 0x69, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 0x12, 0x3b, 0x0a, 0x0e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, - 0x43, 0x69, 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x6c, 0x69, 0x73, 0x74, 0x43, 0x69, 0x64, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x75, 0x6c, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, - 0x69, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x3d, - 0x0a, 0x09, 0x43, 0x69, 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x85, 0x01, - 0x0a, 0x16, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x6f, 0x68, 0x5f, - 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0b, 0x64, 0x6f, 0x68, 0x41, 0x75, 0x74, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x32, 0x0a, 0x14, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x61, 0x73, 0x68, 0x42, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x42, 0x13, 0x0a, 0x11, 0x64, 0x6f, 0x68, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, 0x70, 0x0a, 0x0b, 0x52, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, - 0x69, 0x64, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x43, 0x69, 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x69, 0x64, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x72, - 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x72, 0x70, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x66, 0x69, 0x6c, - 0x65, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} + 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, + 0x73, 0x74, 0x43, 0x69, 0x64, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, + 0x69, 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, + 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x09, + 0x43, 0x69, 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x85, 0x01, 0x0a, 0x16, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x64, 0x6f, 0x68, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x64, + 0x6f, 0x68, 0x41, 0x75, 0x74, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x32, 0x0a, 0x14, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x48, 0x61, 0x73, 0x68, 0x42, 0x63, 0x72, 0x79, 0x70, 0x74, 0x42, 0x13, + 0x0a, 0x11, 0x64, 0x6f, 0x68, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x22, 0x70, 0x0a, 0x0b, 0x52, 0x61, 0x74, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x69, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x64, 0x62, 0x2e, 0x43, 0x69, 0x64, 0x72, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x69, 0x64, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x70, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x72, 0x70, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x66, 0x69, 0x6c, 0x65, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) var ( file_filecache_proto_rawDescOnce sync.Once - file_filecache_proto_rawDescData = file_filecache_proto_rawDesc + file_filecache_proto_rawDescData []byte ) func file_filecache_proto_rawDescGZIP() []byte { file_filecache_proto_rawDescOnce.Do(func() { - file_filecache_proto_rawDescData = protoimpl.X.CompressGZIP(file_filecache_proto_rawDescData) + file_filecache_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_filecache_proto_rawDesc), len(file_filecache_proto_rawDesc))) }) return file_filecache_proto_rawDescData } @@ -1662,21 +1637,20 @@ var file_filecache_proto_depIdxs = []int32{ 10, // 16: profiledb.Access.allowlist_cidr:type_name -> profiledb.CidrRange 10, // 17: profiledb.Access.blocklist_cidr:type_name -> profiledb.CidrRange 10, // 18: profiledb.Ratelimiter.client_cidr:type_name -> profiledb.CidrRange - 19, // 19: profiledb.FilterConfig.Custom.update_time:type_name -> google.protobuf.Timestamp - 15, // 20: profiledb.FilterConfig.Parental.pause_schedule:type_name -> profiledb.FilterConfig.Schedule - 16, // 21: profiledb.FilterConfig.Schedule.week:type_name -> profiledb.FilterConfig.WeeklySchedule - 3, // 22: profiledb.FilterConfig.WeeklySchedule.mon:type_name -> profiledb.DayInterval - 3, // 23: profiledb.FilterConfig.WeeklySchedule.tue:type_name -> profiledb.DayInterval - 3, // 24: profiledb.FilterConfig.WeeklySchedule.wed:type_name -> profiledb.DayInterval - 3, // 25: profiledb.FilterConfig.WeeklySchedule.thu:type_name -> profiledb.DayInterval - 3, // 26: profiledb.FilterConfig.WeeklySchedule.fri:type_name -> profiledb.DayInterval - 3, // 27: profiledb.FilterConfig.WeeklySchedule.sat:type_name -> profiledb.DayInterval - 3, // 28: profiledb.FilterConfig.WeeklySchedule.sun:type_name -> profiledb.DayInterval - 29, // [29:29] is the sub-list for method output_type - 29, // [29:29] is the sub-list for method input_type - 29, // [29:29] is the sub-list for extension type_name - 29, // [29:29] is the sub-list for extension extendee - 0, // [0:29] is the sub-list for field type_name + 15, // 19: profiledb.FilterConfig.Parental.pause_schedule:type_name -> profiledb.FilterConfig.Schedule + 16, // 20: profiledb.FilterConfig.Schedule.week:type_name -> profiledb.FilterConfig.WeeklySchedule + 3, // 21: profiledb.FilterConfig.WeeklySchedule.mon:type_name -> profiledb.DayInterval + 3, // 22: profiledb.FilterConfig.WeeklySchedule.tue:type_name -> profiledb.DayInterval + 3, // 23: profiledb.FilterConfig.WeeklySchedule.wed:type_name -> profiledb.DayInterval + 3, // 24: profiledb.FilterConfig.WeeklySchedule.thu:type_name -> profiledb.DayInterval + 3, // 25: profiledb.FilterConfig.WeeklySchedule.fri:type_name -> profiledb.DayInterval + 3, // 26: profiledb.FilterConfig.WeeklySchedule.sat:type_name -> profiledb.DayInterval + 3, // 27: profiledb.FilterConfig.WeeklySchedule.sun:type_name -> profiledb.DayInterval + 28, // [28:28] is the sub-list for method output_type + 28, // [28:28] is the sub-list for method input_type + 28, // [28:28] is the sub-list for extension type_name + 28, // [28:28] is the sub-list for extension extendee + 0, // [0:28] is the sub-list for field type_name } func init() { file_filecache_proto_init() } @@ -1697,7 +1671,7 @@ func file_filecache_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_filecache_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_filecache_proto_rawDesc), len(file_filecache_proto_rawDesc)), NumEnums: 0, NumMessages: 19, NumExtensions: 0, @@ -1708,7 +1682,6 @@ func file_filecache_proto_init() { MessageInfos: file_filecache_proto_msgTypes, }.Build() File_filecache_proto = out.File - file_filecache_proto_rawDesc = nil file_filecache_proto_goTypes = nil file_filecache_proto_depIdxs = nil } diff --git a/internal/profiledb/internal/filecachepb/filecache.proto b/internal/profiledb/internal/filecachepb/filecache.proto index 24586f1..80dd7db 100644 --- a/internal/profiledb/internal/filecachepb/filecache.proto +++ b/internal/profiledb/internal/filecachepb/filecache.proto @@ -43,8 +43,8 @@ message Profile { message FilterConfig { message Custom { - string id = 1; - google.protobuf.Timestamp update_time = 2; + reserved 1; + reserved 2; repeated string rules = 3; bool enabled = 4; } diff --git a/internal/profiledb/internal/filecachepb/filecachepb.go b/internal/profiledb/internal/filecachepb/filecachepb.go index 35286e2..9bd93e8 100644 --- a/internal/profiledb/internal/filecachepb/filecachepb.go +++ b/internal/profiledb/internal/filecachepb/filecachepb.go @@ -3,6 +3,7 @@ package filecachepb import ( "fmt" + "log/slog" "net/netip" "time" @@ -13,6 +14,7 @@ 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/AdGuardDNS/internal/profiledb/internal" "github.com/c2h5oh/datasize" @@ -21,8 +23,12 @@ import ( ) // toInternal converts the protobuf-encoded data into a cache structure. -func toInternal(fc *FileCache, respSzEst datasize.ByteSize) (c *internal.FileCache, err error) { - profiles, err := profilesToInternal(fc.Profiles, respSzEst) +func toInternal( + fc *FileCache, + baseCustomLogger *slog.Logger, + respSzEst datasize.ByteSize, +) (c *internal.FileCache, err error) { + profiles, err := profilesToInternal(fc.Profiles, baseCustomLogger, respSzEst) if err != nil { return nil, fmt.Errorf("converting profiles: %w", err) } @@ -53,12 +59,13 @@ func toProtobuf(c *internal.FileCache) (pbFileCache *FileCache) { // profilesToInternal converts protobuf profile structures into internal ones. func profilesToInternal( pbProfiles []*Profile, + baseCustomLogger *slog.Logger, respSzEst datasize.ByteSize, ) (profiles []*agd.Profile, err error) { profiles = make([]*agd.Profile, 0, len(pbProfiles)) for i, pbProf := range pbProfiles { var prof *agd.Profile - prof, err = pbProf.toInternal(respSzEst) + prof, err = pbProf.toInternal(baseCustomLogger, respSzEst) if err != nil { return nil, fmt.Errorf("profile at index %d: %w", i, err) } @@ -70,7 +77,10 @@ func profilesToInternal( } // toInternal converts a protobuf profile structure to an internal one. -func (x *Profile) toInternal(respSzEst datasize.ByteSize) (prof *agd.Profile, err error) { +func (x *Profile) toInternal( + baseCustomLogger *slog.Logger, + respSzEst datasize.ByteSize, +) (prof *agd.Profile, err error) { m, err := blockingModeToInternal(x.BlockingMode) if err != nil { return nil, fmt.Errorf("blocking mode: %w", err) @@ -82,12 +92,20 @@ func (x *Profile) toInternal(respSzEst datasize.ByteSize) (prof *agd.Profile, er return nil, fmt.Errorf("pause schedule: %w", err) } + // Consider the rules to have been prevalidated. + rules := unsafelyConvertStrSlice[string, filter.RuleText](pbFltConf.Custom.Rules) + + var flt filter.Custom + if len(rules) > 0 { + flt = custom.New(&custom.Config{ + Logger: baseCustomLogger.With("client_id", x.ProfileId), + Rules: rules, + }) + } + fltConf := &filter.ConfigClient{ Custom: &filter.ConfigCustom{ - ID: pbFltConf.Custom.Id, - UpdateTime: pbFltConf.Custom.UpdateTime.AsTime(), - // Consider the rules to have been prevalidated. - Rules: unsafelyConvertStrSlice[string, filter.RuleText](pbFltConf.Custom.Rules), + Filter: flt, Enabled: pbFltConf.Custom.Enabled, }, Parental: &filter.ConfigParental{ @@ -385,12 +403,15 @@ func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) { // filterConfigToProtobuf converts the filtering configration to protobuf. func filterConfigToProtobuf(c *filter.ConfigClient) (fc *FilterConfig) { + var rules []string + if c.Custom.Enabled { + rules = unsafelyConvertStrSlice[filter.RuleText, string](c.Custom.Filter.Rules()) + } + return &FilterConfig{ Custom: &FilterConfig_Custom{ - Id: string(c.Custom.ID), - UpdateTime: timestamppb.New(c.Custom.UpdateTime), - Rules: unsafelyConvertStrSlice[filter.RuleText, string](c.Custom.Rules), - Enabled: c.Custom.Enabled, + Rules: rules, + Enabled: c.Custom.Enabled, }, Parental: &FilterConfig_Parental{ PauseSchedule: scheduleToProtobuf(c.Parental.PauseSchedule), diff --git a/internal/profiledb/internal/filecachepb/filecachepb_internal_test.go b/internal/profiledb/internal/filecachepb/filecachepb_internal_test.go index 5c0f49b..cebc04a 100644 --- a/internal/profiledb/internal/filecachepb/filecachepb_internal_test.go +++ b/internal/profiledb/internal/filecachepb/filecachepb_internal_test.go @@ -9,10 +9,14 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/profiledbtest" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() + // Sinks for benchmarks var ( bytesSink []byte @@ -74,7 +78,7 @@ func BenchmarkCache(b *testing.B) { b.ReportAllocs() b.ResetTimer() for range b.N { - gotCache, errSink = toInternal(fileCacheSink, profiledbtest.RespSzEst) + gotCache, errSink = toInternal(fileCacheSink, testLogger, profiledbtest.RespSzEst) } require.NoError(b, errSink) diff --git a/internal/profiledb/internal/filecachepb/filecachepb_test.go b/internal/profiledb/internal/filecachepb/filecachepb_test.go index 9c9f31d..78442f8 100644 --- a/internal/profiledb/internal/filecachepb/filecachepb_test.go +++ b/internal/profiledb/internal/filecachepb/filecachepb_test.go @@ -1,6 +1,13 @@ package filecachepb_test -import "time" +import ( + "time" + + "github.com/AdguardTeam/golibs/logutil/slogutil" +) // testTimeout is the common timeout for tests. const testTimeout = 1 * time.Second + +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() diff --git a/internal/profiledb/internal/filecachepb/storage.go b/internal/profiledb/internal/filecachepb/storage.go index 29eb710..9f980b5 100644 --- a/internal/profiledb/internal/filecachepb/storage.go +++ b/internal/profiledb/internal/filecachepb/storage.go @@ -15,17 +15,40 @@ import ( // Storage is the file-cache storage that encodes data using protobuf. type Storage struct { - logger *slog.Logger - path string - respSzEst datasize.ByteSize + logger *slog.Logger + baseCustomLogger *slog.Logger + path string + respSzEst datasize.ByteSize } -// New returns a new protobuf-encoded file-cache storage. -func New(logger *slog.Logger, cachePath string, respSzEst datasize.ByteSize) (s *Storage) { +// Config is the configuration structure for the protobuf-encoded file-cache +// storage. +type Config struct { + // Logger is used for logging the operation of profile database. It must + // not be nil. + Logger *slog.Logger + + // BaseCustomLogger is the base logger used for the custom filters. It must + // not be nil. + BaseCustomLogger *slog.Logger + + // CacheFilePath is the path to the profile cache file. It must be set. + CacheFilePath string + + // ResponseSizeEstimate is the estimate of the size of one DNS response for + // the purposes of custom ratelimiting. Responses over this estimate are + // counted as several responses. It must be positive. + ResponseSizeEstimate datasize.ByteSize +} + +// New returns a new protobuf-encoded file-cache storage. c must not be nil and +// must be valid. +func New(c *Config) (s *Storage) { return &Storage{ - logger: logger, - path: cachePath, - respSzEst: respSzEst, + logger: c.Logger, + baseCustomLogger: c.BaseCustomLogger, + path: c.CacheFilePath, + respSzEst: c.ResponseSizeEstimate, } } @@ -63,7 +86,7 @@ func (s *Storage) Load(ctx context.Context) (c *internal.FileCache, err error) { ) } - return toInternal(fc, s.respSzEst) + return toInternal(fc, s.baseCustomLogger, s.respSzEst) } // Store implements the [internal.FileCacheStorage] interface for *Storage. diff --git a/internal/profiledb/internal/filecachepb/storage_test.go b/internal/profiledb/internal/filecachepb/storage_test.go index 4b0c7d1..f7f6421 100644 --- a/internal/profiledb/internal/filecachepb/storage_test.go +++ b/internal/profiledb/internal/filecachepb/storage_test.go @@ -9,7 +9,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/profiledbtest" - "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +17,12 @@ import ( func TestStorage(t *testing.T) { prof, dev := profiledbtest.NewProfile(t) cachePath := filepath.Join(t.TempDir(), "profiles.pb") - s := filecachepb.New(slogutil.NewDiscardLogger(), cachePath, profiledbtest.RespSzEst) + s := filecachepb.New(&filecachepb.Config{ + Logger: testLogger, + BaseCustomLogger: testLogger, + CacheFilePath: cachePath, + ResponseSizeEstimate: profiledbtest.RespSzEst, + }) require.NotNil(t, s) fc := &internal.FileCache{ @@ -42,7 +46,12 @@ func TestStorage(t *testing.T) { func TestStorage_Load_noFile(t *testing.T) { cachePath := filepath.Join(t.TempDir(), "profiles.pb") - s := filecachepb.New(slogutil.NewDiscardLogger(), cachePath, profiledbtest.RespSzEst) + s := filecachepb.New(&filecachepb.Config{ + Logger: testLogger, + BaseCustomLogger: testLogger, + CacheFilePath: cachePath, + ResponseSizeEstimate: profiledbtest.RespSzEst, + }) require.NotNil(t, s) ctx := testutil.ContextWithTimeout(t, testTimeout) diff --git a/internal/profiledb/internal/internal.go b/internal/profiledb/internal/internal.go index cd4e7f1..061c847 100644 --- a/internal/profiledb/internal/internal.go +++ b/internal/profiledb/internal/internal.go @@ -13,7 +13,7 @@ import ( // FileCacheVersion is the version of cached data structure. It must be // manually incremented on every change in [agd.Device], [agd.Profile], and any // file-cache structures. -const FileCacheVersion = 15 +const FileCacheVersion = 16 // CacheVersionError is returned from [FileCacheStorage.Load] method if the // stored cache version doesn't match current [FileCacheVersion]. diff --git a/internal/profiledb/internal/profiledbtest/profiledbtest.go b/internal/profiledb/internal/profiledbtest/profiledbtest.go index b694cfc..ffb4bf0 100644 --- a/internal/profiledb/internal/profiledbtest/profiledbtest.go +++ b/internal/profiledb/internal/profiledbtest/profiledbtest.go @@ -12,7 +12,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/c2h5oh/datasize" "github.com/stretchr/testify/require" ) @@ -77,13 +79,16 @@ func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) { Enabled: true, } + customConf := &custom.Config{ + Logger: slogutil.NewDiscardLogger(), + Rules: []filter.RuleText{"|blocked-by-custom.example^"}, + } + return &agd.Profile{ FilterConfig: &filter.ConfigClient{ Custom: &filter.ConfigCustom{ - ID: string(ProfileID), - UpdateTime: time.Now().UTC(), - Rules: []filter.RuleText{"|blocked-by-custom.example"}, - Enabled: true, + Filter: custom.New(customConf), + Enabled: true, }, Parental: parental, RuleList: &filter.ConfigRuleList{ diff --git a/internal/profiledb/profiledb.go b/internal/profiledb/profiledb.go index db57f4d..c466db5 100644 --- a/internal/profiledb/profiledb.go +++ b/internal/profiledb/profiledb.go @@ -12,14 +12,13 @@ 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/profiledb/internal" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/osutil" - "github.com/AdguardTeam/golibs/timeutil" + "github.com/AdguardTeam/golibs/service" "github.com/c2h5oh/datasize" ) @@ -124,6 +123,9 @@ type Config struct { // Logger is used for logging the operation of profile database. Logger *slog.Logger + // BaseCustomLogger is the base logger used for the custom filters. + BaseCustomLogger *slog.Logger + // Storage returns the data for this profile DB. Storage Storage @@ -239,8 +241,12 @@ func New(c *Config) (db *Default, err error) { if c.CacheFilePath == "none" { cacheStorage = internal.EmptyFileCacheStorage{} } else if ext := filepath.Ext(c.CacheFilePath); ext == ".pb" { - logger := c.Logger.With("cache_type", "pb") - cacheStorage = filecachepb.New(logger, c.CacheFilePath, c.ResponseSizeEstimate) + cacheStorage = filecachepb.New(&filecachepb.Config{ + Logger: c.Logger.With("cache_type", "pb"), + BaseCustomLogger: c.BaseCustomLogger, + CacheFilePath: c.CacheFilePath, + ResponseSizeEstimate: c.ResponseSizeEstimate, + }) } else { return nil, fmt.Errorf("file %q is not protobuf", c.CacheFilePath) } @@ -278,9 +284,9 @@ func New(c *Config) (db *Default, err error) { } // type check -var _ agdservice.Refresher = (*Default)(nil) +var _ service.Refresher = (*Default)(nil) -// Refresh implements the [agdservice.Refresher] interface for *Default. It +// Refresh implements the [service.Refresher] interface for *Default. It // updates the internal maps and the synchronization time using the data it // receives from the storage. // @@ -374,9 +380,7 @@ func (db *Default) fetchProfiles( ) (sr *StorageProfilesResponse, err error) { syncTime := db.syncTime if isFullSync { - db.logger.InfoContext(ctx, "full sync", "since_last_attempt", timeutil.Duration{ - Duration: sinceLastAttempt, - }) + db.logger.InfoContext(ctx, "full sync", "since_last_attempt", sinceLastAttempt) syncTime = time.Time{} } @@ -412,9 +416,7 @@ func (db *Default) needsFullSync(ctx context.Context) (sinceFull time.Duration, db.logger.WarnContext( ctx, "previous sync finished with error", - "since_last_successful_sync", timeutil.Duration{ - Duration: sinceFull, - }, + "since_last_successful_sync", sinceFull, "last_successful_sync_time", lastFull, ) @@ -453,9 +455,7 @@ func (db *Default) loadFileCache(ctx context.Context) (err error) { "version", c.Version, "prof_num", profNum, "dev_num", devNum, - "elapsed", timeutil.Duration{ - Duration: time.Since(start), - }, + "elapsed", time.Since(start), ) if profNum == 0 || devNum == 0 { diff --git a/internal/profiledb/profiledb_test.go b/internal/profiledb/profiledb_test.go index 5a30fe2..c23ccd2 100644 --- a/internal/profiledb/profiledb_test.go +++ b/internal/profiledb/profiledb_test.go @@ -33,6 +33,9 @@ var ( // testTimeout is the common timeout for tests. const testTimeout = 1 * time.Second +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() + // newDefaultProfileDB returns a new default profile database for tests. // devicesCh receives the devices that the storage should return in its // response. @@ -70,7 +73,8 @@ func newDefaultProfileDB(tb testing.TB, devices <-chan []*agd.Device) (db *profi } db, err := profiledb.New(&profiledb.Config{ - Logger: slogutil.NewDiscardLogger(), + Logger: testLogger, + BaseCustomLogger: testLogger, Storage: ps, ErrColl: agdtest.NewErrorCollector(), Metrics: profiledb.EmptyMetrics{}, @@ -421,8 +425,12 @@ func TestDefaultProfileDB_fileCache_success(t *testing.T) { prof, dev := profiledbtest.NewProfile(t) cacheFilePath := filepath.Join(t.TempDir(), "profiles.pb") - logger := slogutil.NewDiscardLogger() - pbCache := filecachepb.New(logger, cacheFilePath, profiledbtest.RespSzEst) + pbCache := filecachepb.New(&filecachepb.Config{ + Logger: testLogger, + BaseCustomLogger: testLogger, + CacheFilePath: cacheFilePath, + ResponseSizeEstimate: profiledbtest.RespSzEst, + }) ctx := testutil.ContextWithTimeout(t, testTimeout) err := pbCache.Store(ctx, &internal.FileCache{ @@ -434,7 +442,8 @@ func TestDefaultProfileDB_fileCache_success(t *testing.T) { require.NoError(t, err) db, err := profiledb.New(&profiledb.Config{ - Logger: logger, + Logger: testLogger, + BaseCustomLogger: testLogger, Storage: ps, ErrColl: agdtest.NewErrorCollector(), Metrics: profiledb.EmptyMetrics{}, @@ -479,8 +488,12 @@ func TestDefaultProfileDB_fileCache_badVersion(t *testing.T) { } cacheFilePath := filepath.Join(t.TempDir(), "profiles.pb") - logger := slogutil.NewDiscardLogger() - pbCache := filecachepb.New(logger, cacheFilePath, profiledbtest.RespSzEst) + pbCache := filecachepb.New(&filecachepb.Config{ + Logger: testLogger, + BaseCustomLogger: testLogger, + CacheFilePath: cacheFilePath, + ResponseSizeEstimate: profiledbtest.RespSzEst, + }) ctx := testutil.ContextWithTimeout(t, testTimeout) err := pbCache.Store(ctx, &internal.FileCache{ @@ -489,7 +502,8 @@ func TestDefaultProfileDB_fileCache_badVersion(t *testing.T) { require.NoError(t, err) db, err := profiledb.New(&profiledb.Config{ - Logger: logger, + Logger: testLogger, + BaseCustomLogger: testLogger, Storage: ps, ErrColl: agdtest.NewErrorCollector(), Metrics: profiledb.EmptyMetrics{}, @@ -542,7 +556,8 @@ func TestDefaultProfileDB_CreateAutoDevice(t *testing.T) { } db, err := profiledb.New(&profiledb.Config{ - Logger: slogutil.NewDiscardLogger(), + Logger: testLogger, + BaseCustomLogger: testLogger, Storage: ps, ErrColl: agdtest.NewErrorCollector(), Metrics: profiledb.EmptyMetrics{}, diff --git a/internal/querylog/fs.go b/internal/querylog/fs.go index c59dc8f..8943b10 100644 --- a/internal/querylog/fs.go +++ b/internal/querylog/fs.go @@ -7,50 +7,36 @@ import ( "fmt" "log/slog" "math" + "math/rand/v2" "net/netip" "os" "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/AdGuardDNS/internal/agdrand" "github.com/AdguardTeam/AdGuardDNS/internal/optslog" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/mathutil" "github.com/AdguardTeam/golibs/syncutil" - "golang.org/x/exp/rand" + "github.com/c2h5oh/datasize" ) // FileSystemConfig is the configuration of the file system query log. All // fields must not be empty. type FileSystemConfig struct { - // Logger is used for debug logging. + // Logger is used for debug logging. It must not be nil. Logger *slog.Logger - // Path is the path to the log file. + // Metrics is used for the collection of the query log statistics. It must + // not be nil. + Metrics Metrics + + // Path is the path to the log file. It must not be empty. Path string // RandSeed is used to set the "rn" property in JSON objects. - RandSeed uint64 -} - -// NewFileSystem creates a new file system query log. The log is safe for -// concurrent use. c must not be nil. -func NewFileSystem(c *FileSystemConfig) (l *FileSystem) { - rng := rand.New(&rand.LockedSource{}) - rng.Seed(c.RandSeed) - - return &FileSystem{ - logger: c.Logger, - bufferPool: syncutil.NewPool(func() (v *entryBuffer) { - return &entryBuffer{ - ent: &jsonlEntry{}, - buf: &bytes.Buffer{}, - } - }), - rng: rng, - path: c.Path, - } + RandSeed [32]byte } // entryBuffer is a struct with two fields for caching entry that is being @@ -73,10 +59,34 @@ type FileSystem struct { // resulting JSON. rng *rand.Rand + // metrics is used for the collection of the query log statistics. + metrics Metrics + // path is the path to the query log file. path string } +// NewFileSystem creates a new file system query log. The log is safe for +// concurrent use. c must not be nil. +func NewFileSystem(c *FileSystemConfig) (l *FileSystem) { + src := rand.NewChaCha8(c.RandSeed) + // #nosec G404 -- We don't need a real random, pseudorandom is enough. + rng := rand.New(agdrand.NewLockedSource(src)) + + return &FileSystem{ + logger: c.Logger, + bufferPool: syncutil.NewPool(func() (v *entryBuffer) { + return &entryBuffer{ + ent: &jsonlEntry{}, + buf: &bytes.Buffer{}, + } + }), + rng: rng, + metrics: c.Metrics, + path: c.Path, + } +} + // type check var _ Interface = (*FileSystem)(nil) @@ -95,8 +105,8 @@ func (l *FileSystem) Write(ctx context.Context, e *Entry) (err error) { startTime := time.Now() defer func() { - metrics.QueryLogWriteDuration.Observe(time.Since(startTime).Seconds()) - metrics.QueryLogItemsCount.Inc() + l.metrics.ObserveWriteDuration(ctx, time.Since(startTime)) + l.metrics.IncrementItemsCount(ctx) }() entBuf := l.bufferPool.Get() @@ -151,7 +161,9 @@ func (l *FileSystem) Write(ctx context.Context, e *Entry) (err error) { return fmt.Errorf("writing log: %w", err) } - metrics.QueryLogItemSize.Observe(float64(written)) + // #nosec G115 -- [bytes.Buffer.WriteTo] returns the number of bytes + // written, which is always a non-negative number. + l.metrics.ObserveItemSize(ctx, datasize.ByteSize(written)) return nil } diff --git a/internal/querylog/fs_test.go b/internal/querylog/fs_test.go index 7a9ad99..784cc15 100644 --- a/internal/querylog/fs_test.go +++ b/internal/querylog/fs_test.go @@ -21,8 +21,9 @@ func TestFileSystem_Write(t *testing.T) { l := querylog.NewFileSystem(&querylog.FileSystemConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: querylog.EmptyMetrics{}, Path: f.Name(), - RandSeed: 0, + RandSeed: [32]byte{}, }) ctx := context.Background() @@ -57,7 +58,7 @@ func TestFileSystem_Write(t *testing.T) { "e":5, "q":1, "r":0, - "rn":35121, + "rn":13933, "f":2, "s":1, "p":8 @@ -94,7 +95,7 @@ func TestFileSystem_Write(t *testing.T) { "e":5, "q":1, "r":3, - "rn":47387, + "rn":10182, "f":1, "s":1, "p":8 @@ -112,8 +113,9 @@ func BenchmarkFileSystem_Write_file(b *testing.B) { l := querylog.NewFileSystem(&querylog.FileSystemConfig{ Logger: slogutil.NewDiscardLogger(), + Metrics: querylog.EmptyMetrics{}, Path: f.Name(), - RandSeed: 0, + RandSeed: [32]byte{}, }) e := testEntry() diff --git a/internal/querylog/metrics.go b/internal/querylog/metrics.go new file mode 100644 index 0000000..ced2031 --- /dev/null +++ b/internal/querylog/metrics.go @@ -0,0 +1,38 @@ +package querylog + +import ( + "context" + "time" + + "github.com/c2h5oh/datasize" +) + +// Metrics is an interface that is used for the collection of the query log +// statistics. +type Metrics interface { + // IncrementItemsCount increments the total number of query log entries + // written. + IncrementItemsCount(ctx context.Context) + + // ObserveItemSize stores the size of written query log entry. + ObserveItemSize(ctx context.Context, size datasize.ByteSize) + + // ObserveWriteDuration stores the duration of the write operation. + ObserveWriteDuration(ctx context.Context, dur time.Duration) +} + +// EmptyMetrics is the implementation of the [Metrics] interface that does +// nothing. +type EmptyMetrics struct{} + +// type check +var _ Metrics = EmptyMetrics{} + +// IncrementItemsCount implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) IncrementItemsCount(_ context.Context) {} + +// ObserveItemSize implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) ObserveItemSize(_ context.Context, _ datasize.ByteSize) {} + +// ObserveWriteDuration implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) ObserveWriteDuration(_ context.Context, _ time.Duration) {} diff --git a/internal/remotekv/consulkv/consulkv.go b/internal/remotekv/consulkv/consulkv.go index d799a07..ad8c1e5 100644 --- a/internal/remotekv/consulkv/consulkv.go +++ b/internal/remotekv/consulkv/consulkv.go @@ -202,7 +202,7 @@ func (kv *KV) Set(ctx context.Context, key string, val []byte) (err error) { sessReq := &consulSessionRequest{ Name: fmt.Sprintf("ad_guard_dns_session_%d", time.Now().UnixNano()), Behavior: consulSessionBehavior, - TTL: timeutil.Duration{Duration: kv.ttl}, + TTL: timeutil.Duration(kv.ttl), } b, err := json.Marshal(sessReq) if err != nil { diff --git a/internal/remotekv/rediskv/metrics.go b/internal/remotekv/rediskv/metrics.go index fc9b3df..72e30aa 100644 --- a/internal/remotekv/rediskv/metrics.go +++ b/internal/remotekv/rediskv/metrics.go @@ -7,7 +7,7 @@ import "context" type Metrics interface { // UpdateMetrics updates the total number of active connections and // increments the total number of errors if necessary. - UpdateMetrics(ctx context.Context, val uint, isSuccess bool) + UpdateMetrics(ctx context.Context, val uint, err error) } // EmptyMetrics is the implementation of the [Metrics] interface that does @@ -18,4 +18,4 @@ type EmptyMetrics struct{} var _ Metrics = EmptyMetrics{} // UpdateMetrics implements the [Metrics] interface for EmptyMetrics. -func (EmptyMetrics) UpdateMetrics(_ context.Context, _ uint, _ bool) {} +func (EmptyMetrics) UpdateMetrics(_ context.Context, _ uint, _ error) {} diff --git a/internal/remotekv/rediskv/rediskv.go b/internal/remotekv/rediskv/rediskv.go index 2d9e196..6067f5e 100644 --- a/internal/remotekv/rediskv/rediskv.go +++ b/internal/remotekv/rediskv/rediskv.go @@ -144,7 +144,7 @@ func (kv *RedisKV) Get(ctx context.Context, key string) (val []byte, ok bool, er defer func() { // #nosec G115 -- Assume that pool.ActiveCount is always non-negative. - kv.metrics.UpdateMetrics(ctx, uint(kv.pool.ActiveCount()), err == nil) + kv.metrics.UpdateMetrics(ctx, uint(kv.pool.ActiveCount()), err) }() c, err := kv.pool.GetContext(ctx) @@ -170,7 +170,7 @@ func (kv *RedisKV) Set(ctx context.Context, key string, val []byte) (err error) defer func() { // #nosec G115 -- Assume that pool.ActiveCount is always non-negative. - kv.metrics.UpdateMetrics(ctx, uint(kv.pool.ActiveCount()), err == nil) + kv.metrics.UpdateMetrics(ctx, uint(kv.pool.ActiveCount()), err) }() c, err := kv.pool.GetContext(ctx) diff --git a/internal/rulestat/http.go b/internal/rulestat/http.go index d23e340..8f9611a 100644 --- a/internal/rulestat/http.go +++ b/internal/rulestat/http.go @@ -12,12 +12,11 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/service" ) // statFilterListLegacyID is the ID of the filtering rule list for which we @@ -36,6 +35,7 @@ type HTTP struct { url *url.URL http *agdhttp.Client errColl errcoll.Interface + metrics Metrics // mu protects stats and recordedHits. mu *sync.Mutex @@ -55,6 +55,9 @@ type HTTPConfig struct { // ErrColl is used to collect errors during refreshes. ErrColl errcoll.Interface + // Metrics is used for the collection of the filtering rule statistics. + Metrics Metrics + // URL is the URL to which the statistics is uploaded. URL *url.URL } @@ -69,6 +72,7 @@ func NewHTTP(c *HTTPConfig) (s *HTTP) { Timeout: 30 * time.Second, }), errColl: c.ErrColl, + metrics: c.Metrics, mu: &sync.Mutex{}, stats: statsSet{}, @@ -79,7 +83,7 @@ func NewHTTP(c *HTTPConfig) (s *HTTP) { var _ Interface = (*HTTP)(nil) // Collect implements the Interface interface for *HTTP. -func (s *HTTP) Collect(_ context.Context, id filter.ID, text filter.RuleText) { +func (s *HTTP) Collect(ctx context.Context, id filter.ID, text filter.RuleText) { if id != filter.IDAdGuardDNS { return } @@ -90,7 +94,7 @@ func (s *HTTP) Collect(_ context.Context, id filter.ID, text filter.RuleText) { defer s.mu.Unlock() s.recordedHits++ - metrics.RuleStatCacheSize.Set(float64(s.recordedHits)) + s.metrics.SetHitCount(ctx, s.recordedHits) texts := s.stats[id] if texts != nil { @@ -105,26 +109,24 @@ func (s *HTTP) Collect(_ context.Context, id filter.ID, text filter.RuleText) { } // type check -var _ agdservice.Refresher = (*HTTP)(nil) +var _ service.Refresher = (*HTTP)(nil) -// Refresh implements the [agdservice.Refresher] interface for *HTTP. It -// uploads the collected statistics to s.u and starts collecting a new set of +// Refresh implements the [service.Refresher] interface for *HTTP. It uploads +// the collected statistics to s.u and starts collecting a new set of // statistics. func (s *HTTP) Refresh(ctx context.Context) (err error) { s.logger.InfoContext(ctx, "refresh started") defer s.logger.InfoContext(ctx, "refresh finished") + defer func() { s.metrics.HandleUploadStatus(ctx, err) }() + err = s.refresh(ctx) if err != nil { errcoll.Collect(ctx, s.errColl, s.logger, "uploading rulestat", err) - metrics.SetStatusGauge(metrics.RuleStatUploadStatus, err) return err } - metrics.RuleStatUploadTimestamp.SetToCurrentTime() - metrics.SetStatusGauge(metrics.RuleStatUploadStatus, nil) - return nil } diff --git a/internal/rulestat/http_test.go b/internal/rulestat/http_test.go index c251ffb..ef154c0 100644 --- a/internal/rulestat/http_test.go +++ b/internal/rulestat/http_test.go @@ -42,8 +42,9 @@ func TestHTTP_Collect(t *testing.T) { rw.WriteHeader(http.StatusOK) })) conf := &rulestat.HTTPConfig{ - ErrColl: agdtest.NewErrorCollector(), Logger: slogutil.NewDiscardLogger(), + ErrColl: agdtest.NewErrorCollector(), + Metrics: rulestat.EmptyMetrics{}, URL: u, } @@ -96,6 +97,7 @@ func TestHTTP_Refresh_errors(t *testing.T) { testutil.AssertErrorMsg(t, "uploading rulestat: "+wantErrMsg, err) }, }, + Metrics: rulestat.EmptyMetrics{}, URL: &url.URL{ Scheme: "badscheme", Host: "0.0.0.0", @@ -117,7 +119,8 @@ func TestHTTP_Refresh_errors(t *testing.T) { require.NotNil(t, err) }, }, - URL: u, + Metrics: rulestat.EmptyMetrics{}, + URL: u, }) var serr *agdhttp.StatusError diff --git a/internal/rulestat/metrics.go b/internal/rulestat/metrics.go new file mode 100644 index 0000000..14de64a --- /dev/null +++ b/internal/rulestat/metrics.go @@ -0,0 +1,29 @@ +package rulestat + +import ( + "context" +) + +// Metrics is an interface that is used for the collection of the filtering rule +// statistics. +type Metrics interface { + // SetHitCount the number of rule hits that have not yet been uploaded. + SetHitCount(ctx context.Context, count int64) + + // HandleUploadStatus handles the upload status of the filtering rule + // statistics. + HandleUploadStatus(ctx context.Context, err error) +} + +// EmptyMetrics is the implementation of the [Metrics] interface that does +// nothing. +type EmptyMetrics struct{} + +// type check +var _ Metrics = EmptyMetrics{} + +// SetHitCount implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) SetHitCount(_ context.Context, _ int64) {} + +// HandleUploadStatus implements the [Metrics] interface for EmptyMetrics. +func (EmptyMetrics) HandleUploadStatus(_ context.Context, _ error) {} diff --git a/internal/tlsconfig/manager.go b/internal/tlsconfig/manager.go index 9bbeaba..e1d9fb2 100644 --- a/internal/tlsconfig/manager.go +++ b/internal/tlsconfig/manager.go @@ -10,9 +10,9 @@ import ( "path/filepath" "sync" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/service" ) // Manager stores and updates TLS configurations. @@ -203,9 +203,9 @@ func (m *DefaultManager) CloneWithMetrics( } // type check -var _ agdservice.Refresher = (*DefaultManager)(nil) +var _ service.Refresher = (*DefaultManager)(nil) -// Refresh implements the [agdservice.Refresher] interface for *DefaultManager. +// Refresh implements the [service.Refresher] interface for *DefaultManager. func (m *DefaultManager) Refresh(ctx context.Context) (err error) { m.logger.DebugContext(ctx, "refresh started") defer m.logger.DebugContext(ctx, "refresh finished") @@ -228,12 +228,13 @@ func (m *DefaultManager) Refresh(ctx context.Context) (err error) { return true } - if m.certStorage.update(cp, cert) { - m.logger.InfoContext(ctx, "refreshed certificate", "cert", cp.certPath, "key", cp.keyPath) - } else { - m.logger.WarnContext(ctx, "certificate did not refresh", "cert", cp.certPath, "key", cp.keyPath) + msg, lvl := "refreshed certificate", slog.LevelInfo + if !m.certStorage.update(cp, cert) { + msg, lvl = "certificate did not refresh", slog.LevelWarn } + m.logger.Log(ctx, lvl, msg, "cert", cp.certPath, "key", cp.keyPath) + return true }) @@ -268,8 +269,9 @@ func (m *DefaultManager) RotateTickets(ctx context.Context) (err error) { } defer func() { + m.metrics.SetSessionTicketRotationStatus(ctx, err) + if err != nil { - m.metrics.SetSessionTicketRotationStatus(ctx, false) errcoll.Collect(ctx, m.errColl, m.logger, "ticket rotation failed", err) } }() @@ -303,8 +305,6 @@ func (m *DefaultManager) RotateTickets(ctx context.Context) (err error) { "num_tickets", len(tickets), ) - m.metrics.SetSessionTicketRotationStatus(ctx, true) - return nil } diff --git a/internal/tlsconfig/metrics.go b/internal/tlsconfig/metrics.go index 0b4be1a..f0943a7 100644 --- a/internal/tlsconfig/metrics.go +++ b/internal/tlsconfig/metrics.go @@ -27,7 +27,7 @@ type Metrics interface { // SetSessionTicketRotationStatus sets the TLS session ticket rotation // status. - SetSessionTicketRotationStatus(ctx context.Context, enabled bool) + SetSessionTicketRotationStatus(ctx context.Context, err error) } // EmptyMetrics is the implementation of the [Metrics] interface that does @@ -65,4 +65,4 @@ func (EmptyMetrics) SetCertificateInfo(_ context.Context, _, _ string, _ time.Ti // SetSessionTicketRotationStatus implements the [Metrics] interface for // EmptyMetrics. -func (EmptyMetrics) SetSessionTicketRotationStatus(_ context.Context, _ bool) {} +func (EmptyMetrics) SetSessionTicketRotationStatus(_ context.Context, _ error) {} diff --git a/internal/tools/go.mod b/internal/tools/go.mod index 303103c..72733b4 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -1,6 +1,6 @@ module github.com/AdguardTeam/AdGuardDNS/internal/tools -go 1.23.4 +go 1.23.6 require ( github.com/fzipp/gocyclo v0.6.0 @@ -8,63 +8,62 @@ require ( github.com/gordonklaus/ineffassign v0.1.0 github.com/jstemmer/go-junit-report/v2 v2.1.0 github.com/kisielk/errcheck v1.8.0 - github.com/securego/gosec/v2 v2.21.4 - github.com/uudashr/gocognit v1.1.3 - golang.org/x/tools v0.28.0 - golang.org/x/vuln v1.1.3 + github.com/securego/gosec/v2 v2.22.0 + github.com/uudashr/gocognit v1.2.0 + golang.org/x/tools v0.29.0 + golang.org/x/vuln v1.1.4 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 - google.golang.org/protobuf v1.35.2 + google.golang.org/protobuf v1.36.5 honnef.co/go/tools v0.5.1 mvdan.cc/gofumpt v0.7.0 mvdan.cc/sh/v3 v3.10.0 - mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3 + mvdan.cc/unparam v0.0.0-20241226123437-447d509598f3 ) require ( - cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/ai v0.9.0 // indirect - cloud.google.com/go/auth v0.12.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - cloud.google.com/go/compute/metadata v0.5.2 // indirect - cloud.google.com/go/longrunning v0.6.3 // indirect + cloud.google.com/go v0.118.2 // indirect + cloud.google.com/go/ai v0.10.0 // indirect + cloud.google.com/go/auth v0.14.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/longrunning v0.6.4 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/generative-ai-go v0.19.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/renameio/v2 v2.0.0 // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.14.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gookit/color v1.5.4 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect - go.opentelemetry.io/otel v1.32.0 // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/otel/trace v1.32.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect - golang.org/x/exp/typeparams v0.0.0-20241204233417-43b7b7cde48d // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/api v0.210.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/grpc v1.68.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/exp/typeparams v0.0.0-20250207012021-f9890c6ad9f3 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/telemetry v0.0.0-20250206143958-557cf9c30e9f // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/api v0.220.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 // indirect + google.golang.org/grpc v1.70.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mvdan.cc/editorconfig v0.3.0 // indirect ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index ac81594..cf9a46f 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -1,31 +1,21 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/ai v0.9.0 h1:r1Ig8O8+Qr3Ia3WfoO+gokD0fxB2Rk4quppuKjmGMsY= -cloud.google.com/go/ai v0.9.0/go.mod h1:28bKM/oxmRgxmRgI1GLumFv+NSkt+DscAg/gF+54zzY= -cloud.google.com/go/auth v0.12.0 h1:ARAD8r0lkiHw2go7kEnmviF6TOYhzLM+yDGcDt9mP68= -cloud.google.com/go/auth v0.12.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= -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/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= -cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +cloud.google.com/go v0.118.2 h1:bKXO7RXMFDkniAAvvuMrAPtQ/VHrs9e7J5UT3yrGdTY= +cloud.google.com/go v0.118.2/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M= +cloud.google.com/go/ai v0.10.0 h1:hwj6CI6sMKubXodoJJGTy/c2T1RbbLGM6TL3QoAvzU8= +cloud.google.com/go/ai v0.10.0/go.mod h1:kvnt2KeHqX8+41PVeMRBETDyQAp/RFvBWGdx/aGjNMo= +cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= +cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -39,20 +29,6 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= @@ -61,30 +37,23 @@ github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaH github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= -github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= @@ -97,152 +66,108 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= -github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= -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/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM= +github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= +github.com/securego/gosec/v2 v2.22.0 h1:bV/Ii5YSQtbobXuIFBXrfr91l5N4qslEdFHE9E0I/10= +github.com/securego/gosec/v2 v2.22.0/go.mod h1:sR5n3LzZ/52rn4xxRBJk38iPe/hjiA0CkVcyiAHNCrM= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.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.1/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/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= -github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= +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/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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/exp/typeparams v0.0.0-20241204233417-43b7b7cde48d h1:WQHXGzzI2u+AAlupPRpQbdG4WVvVZ7d2dg/CkpEu1hI= -golang.org/x/exp/typeparams v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp/typeparams v0.0.0-20250207012021-f9890c6ad9f3 h1:w2c+/ogVo2eFFhGTMddgOF7WQkdOPwjh+MRS8wUnujk= +golang.org/x/exp/typeparams v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3 h1:rCLsPBq7l0E9Z451UgkWFkaWYhgt7dGmAlpD6hLjK5I= -golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3/go.mod h1:8h4Hgq+jcTvCDv2+i7NrfWwpYHcESleo2nGHxLbFLJ4= +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/telemetry v0.0.0-20250206143958-557cf9c30e9f h1:C8gBEOYcNZK84ngc8O5MU4ouNcnlgqsKinp/gLXK0+A= +golang.org/x/telemetry v0.0.0-20250206143958-557cf9c30e9f/go.mod h1:Ng+6E7PnWNge4EifZkPKeQUnm5iyAoH8qQgw3pLCiF4= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.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/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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= -golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= -golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= +golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= -google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= -google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/api v0.220.0 h1:3oMI4gdBgB72WFVwE1nerDD8W3HUOS4kypK6rRLbGns= +google.golang.org/api v0.220.0/go.mod h1:26ZAlY6aN/8WgpCzjPNy18QpYaz7Zgg1h0qe1GkZEmY= +google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 h1:fCuMM4fowGzigT89NCIsW57Pk9k2D12MMi2ODn+Nk+o= +google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= +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/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ= @@ -251,5 +176,5 @@ mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= -mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3 h1:YkmTN1n5U60NM02j7TCSWRlW3fqNiuXe/eVXf0dLFN8= -mvdan.cc/unparam v0.0.0-20240917084806-57a3b4290ba3/go.mod h1:z5yboO1sP1Q9pcfvS597TpfbNXQjphDlkCJHzt13ybc= +mvdan.cc/unparam v0.0.0-20241226123437-447d509598f3 h1:OPdLMIX29kquQXSiXmnwzHP1bc+JlH0S2l8SfVK9yWE= +mvdan.cc/unparam v0.0.0-20241226123437-447d509598f3/go.mod h1:VQc4l9ccF55E7EwPxcGqwierxEf0KG8MRR8hJ9tpngw= diff --git a/internal/websvc/blockpage.go b/internal/websvc/blockpage.go index 87859e1..2694b81 100644 --- a/internal/websvc/blockpage.go +++ b/internal/websvc/blockpage.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "context" "fmt" + "log/slog" "net/http" "os" "strings" @@ -12,35 +13,20 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/httphdr" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/netutil/httputil" + "github.com/AdguardTeam/golibs/service" "github.com/prometheus/client_golang/prometheus" ) -// blockPageName is a type alias for strings that contain a block-page name for -// logging and metrics. -type blockPageName = string - -// blockPageName values. -const ( - adultBlockingName blockPageName = "adult blocking" - generalBlockingName blockPageName = "general blocking" - safeBrowsingName blockPageName = "safe browsing" -) - -// BlockPageServerConfig is the blocking page server configuration. -type BlockPageServerConfig struct { - // ContentFilePath is the path to HTML block page content file. - ContentFilePath string - - // Bind are the addresses on which to serve the block page. - Bind []*BindData -} - // blockPageServer serves the blocking page contents. type blockPageServer struct { + // logger is used to log the refreshes. + logger *slog.Logger + // mu protects content and gzipContent. mu *sync.RWMutex @@ -53,45 +39,54 @@ type blockPageServer struct { // contentFilePath is the path to HTML block page content file. contentFilePath string - // name is the server identification used for logging and metrics. - name blockPageName + // group is the server group used for logging and metrics. + group serverGroup // bind are the addresses on which to serve the block page. bind []*BindData } -// newBlockPageServer initializes a new instance of blockPageServer. The server -// must be refreshed with [blockPageServer.Refresh] before use. -func newBlockPageServer(conf *BlockPageServerConfig, srvName blockPageName) (srv *blockPageServer) { +// newBlockPageServer initializes a new instance of blockPageServer. If conf is +// nil, srv is nil. The server must be refreshed with [blockPageServer.Refresh] +// before use. +func newBlockPageServer( + conf *BlockPageServerConfig, + baseLogger *slog.Logger, + g serverGroup, +) (srv *blockPageServer) { if conf == nil { return nil } return &blockPageServer{ + logger: baseLogger.With(loggerKeyGroup, g), mu: &sync.RWMutex{}, contentFilePath: conf.ContentFilePath, - name: srvName, + group: g, bind: conf.Bind, } } // type check -var _ agdservice.Refresher = (*blockPageServer)(nil) +var _ service.Refresher = (*blockPageServer)(nil) -// Refresh implements the [agdservice.Refresher] interface for *blockPageServer. +// Refresh implements the [service.Refresher] interface for *blockPageServer. // srv may be nil. -func (srv *blockPageServer) Refresh(_ context.Context) (err error) { +func (srv *blockPageServer) Refresh(ctx context.Context) (err error) { if srv == nil { return nil } + srv.logger.InfoContext(ctx, "refresh started") + defer srv.logger.InfoContext(ctx, "refresh finished") + // TODO(d.kolyshev): Compare with current srv content before updating. content, err := os.ReadFile(srv.contentFilePath) if err != nil { - return fmt.Errorf("block page server %q: reading block page file: %w", srv.name, err) + return fmt.Errorf("block page server %q: reading block page file: %w", srv.group, err) } - gzipContent := mustGzip(srv.name, content) + gzipContent := mustGzip(srv.group, content) srv.mu.Lock() defer srv.mu.Unlock() @@ -102,40 +97,43 @@ func (srv *blockPageServer) Refresh(_ context.Context) (err error) { return nil } -// blockPageServers is a helper function that converts a *blockPageServer into -// HTTP servers. -func blockPageServers(srv *blockPageServer, timeout time.Duration) (srvs []*http.Server) { +// newBlockPageServers is a helper function that converts a *blockPageServer +// into HTTP servers. +func newBlockPageServers( + baseLogger *slog.Logger, + srv *blockPageServer, + timeout time.Duration, +) (srvs []*server) { if srv == nil { return nil } - h := srv.blockPageHandler() + srvHdrMw := httputil.ServerHeaderMiddleware(agdhttp.UserAgent()) + handler := srv.blockPageHandler() + for _, b := range srv.bind { - addr := b.Address.String() - errLog := log.StdLog(fmt.Sprintf("websvc: %s: %s", srv.name, addr), log.DEBUG) - srvs = append(srvs, &http.Server{ - Addr: addr, - Handler: h, - TLSConfig: b.TLS, - ErrorLog: errLog, - ReadTimeout: timeout, - WriteTimeout: timeout, - IdleTimeout: timeout, - ReadHeaderTimeout: timeout, - }) + logger := baseLogger.With(loggerKeyGroup, srv.group) + h := httputil.Wrap( + handler, + srvHdrMw, + httputil.NewLogMiddleware(logger, slog.LevelDebug), + ) + + srvs = append(srvs, newServer(&serverConfig{ + BaseLogger: logger, + TLSConf: b.TLS, + Handler: h, + InitialAddress: b.Address, + Timeout: timeout, + })) } return srvs } // blockPageHandler returns an HTTP handler serving the block page content. -// name is used for logging and metrics and must be one of blockPageName values. func (srv *blockPageServer) blockPageHandler() (h http.Handler) { f := func(w http.ResponseWriter, r *http.Request) { - // Set the Server header here, so that all responses carry it. - respHdr := w.Header() - respHdr.Set(httphdr.Server, agdhttp.UserAgent()) - switch r.URL.Path { case "/favicon.ico": // Don't serve the HTML page to the favicon requests. @@ -143,12 +141,12 @@ func (srv *blockPageServer) blockPageHandler() (h http.Handler) { case "/robots.txt": // Don't serve the HTML page to the robots.txt requests. Serve the // predefined response instead. - serveRobotsDisallow(respHdr, w, srv.name) + serveRobotsDisallow(r.Context(), w.Header(), w) default: srv.mu.RLock() defer srv.mu.RUnlock() - serveBlockPage(w, r, srv.name, srv.content, srv.gzipContent) + serveBlockPage(w, r, srv.group, srv.content, srv.gzipContent) } } @@ -182,17 +180,11 @@ func mustGzip(name string, b []byte) (compressed []byte) { // serveBlockPage serves the block-page content taking compression headers into // account. -func serveBlockPage( - w http.ResponseWriter, - r *http.Request, - name string, - blockPage []byte, - gzipped []byte, -) { +func serveBlockPage(w http.ResponseWriter, r *http.Request, g serverGroup, orig, gzipped []byte) { respHdr := w.Header() respHdr.Set(httphdr.ContentType, agdhttp.HdrValTextHTML) - content := blockPage + content := orig // TODO(a.garipov): Parse the quality value. // @@ -203,31 +195,33 @@ func serveBlockPage( content = gzipped } - // Use HTTP 500 status code to signal that this is a block page. - // See AGDNS-1952. + // Use HTTP 500 status code to signal that this is a block page. See + // AGDNS-1952. w.WriteHeader(http.StatusInternalServerError) _, err := w.Write(content) if err != nil { - logErrorByType(err, "websvc: %s: writing response: %s", name, err) + ctx := r.Context() + l := slogutil.MustLoggerFromContext(ctx) + l.Log(ctx, levelForError(err), "writing block page", slogutil.KeyError, err) } - incBlockPageMetrics(name) + incBlockPageMetrics(g) } -// incBlockPageMetrics increments the metrics for the block-page view -// counts depending on the name of the block page. -func incBlockPageMetrics(name blockPageName) { +// incBlockPageMetrics increments the metrics for the block-page view counts +// depending on the server group. +func incBlockPageMetrics(g serverGroup) { var totalCtr prometheus.Counter - switch name { - case adultBlockingName: + switch g { + case srvGrpAdultBlockingPage: totalCtr = metrics.WebSvcAdultBlockingPageRequestsTotal - case generalBlockingName: + case srvGrpGeneralBlockingPage: totalCtr = metrics.WebSvcGeneralBlockingPageRequestsTotal - case safeBrowsingName: + case srvGrpSafeBrowsingPage: totalCtr = metrics.WebSvcSafeBrowsingPageRequestsTotal default: - panic(fmt.Errorf("metrics: bad websvc block-page metric name %q", name)) + panic(fmt.Errorf("metrics: block-page server group: %w: %q", errors.ErrBadEnumValue, g)) } totalCtr.Inc() diff --git a/internal/websvc/blockpage_test.go b/internal/websvc/blockpage_test.go index fa14bc2..97da744 100644 --- a/internal/websvc/blockpage_test.go +++ b/internal/websvc/blockpage_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/websvc" "github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/netutil/urlutil" @@ -36,7 +37,7 @@ func TestBlockPageServers(t *testing.T) { robotsStatus = http.StatusOK ) - // TODO(a.garipov): Do not use hardcoded ports. + // TODO(a.garipov): Do not use hardcoded ports. testCases := []struct { updateConfig func(c *websvc.Config, bps *websvc.BlockPageServerConfig) addr netip.AddrPort @@ -74,7 +75,11 @@ func TestBlockPageServers(t *testing.T) { } conf := &websvc.Config{ - Timeout: testTimeout, + Logger: testLogger, + StaticContent: http.NotFoundHandler(), + DNSCheck: http.NotFoundHandler(), + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, } tc.updateConfig(conf, bps) @@ -89,7 +94,11 @@ func TestBlockPageServers(t *testing.T) { func TestBlockPageServers_noBlockPages(t *testing.T) { conf := &websvc.Config{ - Timeout: testTimeout, + Logger: testLogger, + StaticContent: http.NotFoundHandler(), + DNSCheck: http.NotFoundHandler(), + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, } svc := websvc.New(conf) @@ -102,7 +111,7 @@ func TestBlockPageServers_noBlockPages(t *testing.T) { } func TestBlockPageServers_gzip(t *testing.T) { - // TODO(a.garipov): Do not use hardcoded ports. + // TODO(a.garipov): Do not use hardcoded ports. addr := netip.MustParseAddrPort("127.0.0.1:3001") bps := &websvc.BlockPageServerConfig{ ContentFilePath: filepath.Join("testdata", blockPageFileName), @@ -113,7 +122,12 @@ func TestBlockPageServers_gzip(t *testing.T) { } conf := &websvc.Config{ + Logger: testLogger, GeneralBlocking: bps, + StaticContent: http.NotFoundHandler(), + DNSCheck: http.NotFoundHandler(), + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, } startService(t, conf) diff --git a/internal/websvc/config.go b/internal/websvc/config.go new file mode 100644 index 0000000..72d8346 --- /dev/null +++ b/internal/websvc/config.go @@ -0,0 +1,93 @@ +package websvc + +import ( + "crypto/tls" + "log/slog" + "net/http" + "net/netip" + "net/url" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" +) + +// Config is the AdGuard DNS web service configuration structure. +type Config struct { + // Logger is used for logging the operation of the web service. It must not + // be nil. + Logger *slog.Logger + + // AdultBlocking is the optional adult-blocking block-page web server. + AdultBlocking *BlockPageServerConfig + + // GeneralBlocking is the optional general block-page web server. + GeneralBlocking *BlockPageServerConfig + + // SafeBrowsing is the optional safe-browsing block-page web server. + SafeBrowsing *BlockPageServerConfig + + // LinkedIP is the optional linked IP web server. + LinkedIP *LinkedIPServer + + // RootRedirectURL is the optional URL to which root HTTP requests are + // redirected. If not set, these requests are responded with a 404 page. + RootRedirectURL *url.URL + + // StaticContent is the content that is served statically at the given + // paths. It must not be nil; use [http.NotFoundHandler] if not needed. + StaticContent http.Handler + + // DNSCheck is the HTTP handler for DNS checks. It must not be nil. + DNSCheck http.Handler + + // ErrColl is used to collect linked IP proxy errors and other errors. It + // must not be nil. + ErrColl errcoll.Interface + + // Error404 is the optional content of the HTML page for the 404 status. If + // not set, a simple plain-text 404 response is served. + Error404 []byte + + // Error500 is the optional content of the HTML page for the 500 status. If + // not set, a simple plain-text 500 response is served. + Error500 []byte + + // NonDoHBind are the bind addresses and optional TLS configuration for the + // web service in addition to the ones in the DNS-over-HTTPS handlers. All + // items must not be nil. + NonDoHBind []*BindData + + // Timeout is the timeout for all server operations. It must be positive. + Timeout time.Duration +} + +// BlockPageServerConfig is the blocking page server configuration. +type BlockPageServerConfig struct { + // ContentFilePath is the path to HTML block page content file. It must not + // be empty. + ContentFilePath string + + // Bind are the addresses on which to serve the block page. At least one + // must be provided. All items must not be nil. + Bind []*BindData +} + +// BindData is data for binding one HTTP server to an address. +type BindData struct { + // TLS is the optional TLS configuration. + TLS *tls.Config + + // Address is the binding address. It must not be empty. + Address netip.AddrPort +} + +// LinkedIPServer is the linked IP server configuration. +type LinkedIPServer struct { + // TargetURL is the URL to which linked IP API requests are proxied. It + // must not be nil. + TargetURL *url.URL + + // Bind are the addresses on which to serve the linked IP API. At least one + // must be provided. All items must not be nil. + Bind []*BindData +} diff --git a/internal/websvc/handler.go b/internal/websvc/handler.go index 8a13ac3..daee5be 100644 --- a/internal/websvc/handler.go +++ b/internal/websvc/handler.go @@ -1,34 +1,26 @@ package websvc import ( + "context" "io" + "log/slog" "net/http" "net/http/httptest" "os" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/httphdr" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "golang.org/x/sys/unix" ) -// HTTP Handlers - // type check var _ http.Handler = (*Service)(nil) // ServeHTTP implements the http.Handler interface for *Service. func (svc *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { - respHdr := w.Header() - respHdr.Set(httphdr.Server, agdhttp.UserAgent()) - - m, p, rAddr := r.Method, r.URL.Path, r.RemoteAddr - optlog.Debug3("websvc: starting req %s %s from %s", m, p, rAddr) - defer optlog.Debug3("websvc: finished req %s %s from %s", m, p, rAddr) - if svc == nil { http.NotFound(w, r) @@ -37,26 +29,39 @@ func (svc *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { // TODO(a.garipov): Refactor the 404 and 500 handling and use // [httputil.CodeRecorderResponseWriter] instead. + ctx := r.Context() rec := httptest.NewRecorder() - svc.serveHTTP(rec, r) + svc.serveHTTP(ctx, rec, r) - action, body := svc.processRec(respHdr, rec) + action, body := svc.processRec(w.Header(), rec) w.WriteHeader(rec.Code) _, err := w.Write(body) if err != nil { - logErrorByType(err, "websvc: handler: %s: %s", action, err) + logWriteError(ctx, action, err) + } +} + +// logWriteError logs err at the appropriate level if ctx contains a logger. +// +// TODO(a.garipov): This is not a proper solution; remove once dnsserver +// starts adding a logger of its own. +func logWriteError(ctx context.Context, action string, err error) { + l, ok := slogutil.LoggerFromContext(ctx) + if ok { + l.Log(ctx, levelForError(err), "writing response", "action", action, slogutil.KeyError, err) } } // serveHTTP processes the HTTP request. -func (svc *Service) serveHTTP(rec *httptest.ResponseRecorder, r *http.Request) { +func (svc *Service) serveHTTP(ctx context.Context, rec *httptest.ResponseRecorder, r *http.Request) { + // TODO(a.garipov): Use mux routes. switch r.URL.Path { case "/dnscheck/test": svc.dnsCheck.ServeHTTP(rec, r) metrics.WebSvcDNSCheckTestRequestsTotal.Inc() case "/robots.txt": - serveRobotsDisallow(rec.Header(), rec, "handler") + serveRobotsDisallow(ctx, rec.Header(), rec) case "/": if svc.rootRedirectURL == "" { http.NotFound(rec, r) @@ -87,7 +92,7 @@ func (svc *Service) processRec( ) (action string, body []byte) { switch rec.Code { case http.StatusNotFound: - action = "writing 404" + action = "404 page" if len(svc.error404) != 0 { body = svc.error404 respHdr.Set(httphdr.ContentType, agdhttp.HdrValTextHTML) @@ -95,7 +100,7 @@ func (svc *Service) processRec( metrics.WebSvcError404RequestsTotal.Inc() case http.StatusInternalServerError: - action = "writing 500" + action = "500 page" if len(svc.error500) != 0 { body = svc.error500 respHdr.Set(httphdr.ContentType, agdhttp.HdrValTextHTML) @@ -103,7 +108,7 @@ func (svc *Service) processRec( metrics.WebSvcError500RequestsTotal.Inc() default: - action = "writing response" + action = "response" for k, v := range rec.Header() { respHdr[k] = v } @@ -117,25 +122,25 @@ func (svc *Service) processRec( } // serveRobotsDisallow writes predefined disallow-all response. -func serveRobotsDisallow(hdr http.Header, w http.ResponseWriter, name string) { +func serveRobotsDisallow(ctx context.Context, hdr http.Header, w http.ResponseWriter) { hdr.Set(httphdr.ContentType, agdhttp.HdrValTextPlain) _, err := io.WriteString(w, agdhttp.RobotsDisallowAll) if err != nil { - logErrorByType(err, "websvc: %s: writing response: %s", name, err) + logWriteError(ctx, "robots.txt", err) } metrics.WebSvcRobotsTxtRequestsTotal.Inc() } -// logErrorByType writes err to the error log, unless err is a network error or -// a timeout error, in which case it is written to the debug log. -func logErrorByType(err error, format string, args ...any) { - // TODO(d.kolyshev): Consider adding more error types. - if errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, unix.EPIPE) || +// levelForError returns a logging level depending on whether err is a network +// or a timeout error. +func levelForError(err error) (lvl slog.Level) { + if errors.Is(err, os.ErrDeadlineExceeded) || + errors.Is(err, unix.EPIPE) || errors.Is(err, unix.ECONNRESET) { - log.Debug(format, args...) - } else { - log.Error(format, args...) + return slog.LevelDebug } + + return slog.LevelError } diff --git a/internal/websvc/handler_test.go b/internal/websvc/handler_test.go index 881dee4..f8a0012 100644 --- a/internal/websvc/handler_test.go +++ b/internal/websvc/handler_test.go @@ -8,9 +8,8 @@ import ( "strings" "testing" - "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/websvc" - "github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" @@ -32,9 +31,12 @@ func TestService_ServeHTTP(t *testing.T) { } c := &websvc.Config{ + Logger: testLogger, RootRedirectURL: rootRedirectURL, StaticContent: http.NotFoundHandler(), DNSCheck: mockHandler, + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, } svc := websvc.New(c) @@ -83,23 +85,6 @@ func assertResponse( svc.ServeHTTP(rw, r) assert.Equal(t, statusCode, rw.Code) - assert.Equal(t, agdhttp.UserAgent(), rw.Header().Get(httphdr.Server)) return rw } - -// assertResponseWithHeaders is a helper function that checks status code and -// headers of HTTP response. -func assertResponseWithHeaders( - t *testing.T, - svc *websvc.Service, - path string, - statusCode int, - respHdr http.Header, -) { - t.Helper() - - rw := assertResponse(t, svc, path, statusCode) - - assert.Equal(t, respHdr, rw.Header()) -} diff --git a/internal/websvc/linkip.go b/internal/websvc/linkip.go index 12b284f..a506d46 100644 --- a/internal/websvc/linkip.go +++ b/internal/websvc/linkip.go @@ -2,6 +2,7 @@ package websvc import ( "fmt" + "log/slog" "net" "net/http" "net/http/httputil" @@ -13,30 +14,25 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/httphdr" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" ) -// Linked IP Proxy - // linkedIPProxy proxies selected requests to a remote address. type linkedIPProxy struct { httpProxy *httputil.ReverseProxy errColl errcoll.Interface - logPrefix string } -// linkedIPHandler returns a linked IP proxy handler. -func linkedIPHandler( +// newLinkedIPHandler returns a linked IP proxy handler. All arguments must be +// set. +func newLinkedIPHandler( apiURL *url.URL, errColl errcoll.Interface, - name string, + proxyLogger *slog.Logger, timeout time.Duration, ) (h http.Handler) { - logPrefix := fmt.Sprintf("websvc: linked ip proxy %s", name) - // Use a Rewrite func to make sure we send the correct Host header and don't // send anything besides the path. rewrite := func(r *httputil.ProxyRequest) { @@ -45,6 +41,10 @@ func linkedIPHandler( // Make sure that all requests are marked with our user agent. r.Out.Header.Set(httphdr.UserAgent, agdhttp.UserAgent()) + + // Set the X-Forwarded-* headers for the backend to inspect cert + // validation requests. + r.SetXForwarded() } // Use largely the same transport as http.DefaultTransport, but with a @@ -81,28 +81,20 @@ func linkedIPHandler( handlerWithError := func(_ http.ResponseWriter, r *http.Request, err error) { ctx := r.Context() reqID, _ := agd.RequestIDFromContext(ctx) - errcoll.Collectf( - ctx, - errColl, - "%s: proxying %s %s: req %s: %w", - logPrefix, - r.Method, - r.URL.Path, - reqID, - err, - ) + + l := slogutil.MustLoggerFromContext(ctx).With("req_id", reqID) + errcoll.Collect(ctx, errColl, l, "proxying", err) } return &linkedIPProxy{ httpProxy: &httputil.ReverseProxy{ Rewrite: rewrite, Transport: transport, - ErrorLog: log.StdLog(logPrefix, log.DEBUG), + ErrorLog: slog.NewLogLogger(proxyLogger.Handler(), slog.LevelDebug), ModifyResponse: modifyResponse, ErrorHandler: handlerWithError, }, - errColl: errColl, - logPrefix: logPrefix, + errColl: errColl, } } @@ -115,9 +107,9 @@ func (prx *linkedIPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { respHdr := w.Header() respHdr.Set(httphdr.Server, agdhttp.UserAgent()) + ctx := r.Context() + l := slogutil.MustLoggerFromContext(ctx) m, p, rAddr := r.Method, r.URL.Path, r.RemoteAddr - optlog.Debug3("websvc: starting req %s %s from %s", m, p, rAddr) - defer optlog.Debug3("websvc: finished req %s %s from %s", m, p, rAddr) if shouldProxy(m, p) { // TODO(a.garipov): Consider moving some or all this request @@ -134,8 +126,8 @@ func (prx *linkedIPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Set the real IP. ip, err := netutil.SplitHost(rAddr) if err != nil { - ctx := r.Context() - prx.errColl.Collect(ctx, fmt.Errorf("%s: getting ip: %w", prx.logPrefix, err)) + err = fmt.Errorf("websvc: linked ip proxy: getting ip: %w", err) + prx.errColl.Collect(ctx, err) // Send a 500 error, despite the fact that this is probably a client // error, because this is the code that the frontend expects. @@ -151,13 +143,13 @@ func (prx *linkedIPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { r = r.WithContext(agd.WithRequestID(r.Context(), reqID)) hdr.Set(httphdr.XRequestID, reqID.String()) - log.Debug("%s: proxying %s %s: req %s", prx.logPrefix, m, p, reqID) + l.DebugContext(ctx, "starting to proxy", "req_id", reqID) prx.httpProxy.ServeHTTP(w, r) metrics.WebSvcLinkedIPProxyRequestsTotal.Inc() } else if r.URL.Path == "/robots.txt" { - serveRobotsDisallow(respHdr, w, prx.logPrefix) + serveRobotsDisallow(ctx, respHdr, w) } else { http.NotFound(w, r) } @@ -170,6 +162,8 @@ func (prx *linkedIPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // - GET /linkip/{device_id}/{encrypted}/status // - POST /ddns/{device_id}/{encrypted}/{domain} // - POST /linkip/{device_id}/{encrypted} +// +// TODO(a.garipov): Use mux routes. func shouldProxy(method, urlPath string) (ok bool) { parts := strings.SplitN(strings.TrimPrefix(urlPath, "/"), "/", 5) if l := len(parts); l < 3 || l > 4 { diff --git a/internal/websvc/linkip_internal_test.go b/internal/websvc/linkip_internal_test.go index 46aad12..e091243 100644 --- a/internal/websvc/linkip_internal_test.go +++ b/internal/websvc/linkip_internal_test.go @@ -7,11 +7,11 @@ import ( "strings" "sync/atomic" "testing" - "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/golibs/httphdr" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" @@ -19,6 +19,14 @@ import ( ) func TestLinkedIPProxy_ServeHTTP(t *testing.T) { + const ( + badRemoteIP = "192.0.2.2" + + realRemoteIP = "192.0.2.1" + realRemoteAddr = realRemoteIP + ":12345" + realHost = "link-ip.example" + ) + var ( apiURL *url.URL numReq atomic.Uint64 @@ -30,19 +38,19 @@ func TestLinkedIPProxy_ServeHTTP(t *testing.T) { hdr := r.Header require.Equal(pt, agdhttp.UserAgent(), hdr.Get(httphdr.UserAgent)) - require.NotEmpty(pt, hdr.Get(httphdr.XConnectingIP)) require.NotEmpty(pt, hdr.Get(httphdr.XRequestID)) + require.Equal(pt, apiURL.Host, r.Host) + require.Equal(pt, realRemoteIP, hdr.Get(httphdr.XForwardedFor)) + require.Equal(pt, realRemoteIP, hdr.Get(httphdr.XConnectingIP)) + require.Equal(pt, realHost, hdr.Get(httphdr.XForwardedHost)) + require.Equal(pt, urlutil.SchemeHTTP, hdr.Get(httphdr.XForwardedProto)) + require.Empty(pt, hdr.Get(httphdr.CFConnectingIP)) require.Empty(pt, hdr.Get(httphdr.Forwarded)) require.Empty(pt, hdr.Get(httphdr.TrueClientIP)) - require.Empty(pt, hdr.Get(httphdr.XForwardedFor)) - require.Empty(pt, hdr.Get(httphdr.XForwardedHost)) - require.Empty(pt, hdr.Get(httphdr.XForwardedProto)) require.Empty(pt, hdr.Get(httphdr.XRealIP)) - require.Equal(pt, apiURL.Host, r.Host) - numReq.Add(1) }) @@ -52,7 +60,7 @@ func TestLinkedIPProxy_ServeHTTP(t *testing.T) { apiURL, err := url.Parse(srv.URL) require.NoError(t, err) - h := linkedIPHandler(apiURL, agdtest.NewErrorCollector(), "test", 2*time.Second) + h := newLinkedIPHandler(apiURL, agdtest.NewErrorCollector(), testLogger, testTimeout) testCases := []struct { name string @@ -107,20 +115,26 @@ func TestLinkedIPProxy_ServeHTTP(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - r := httptest.NewRequest(tc.method, (&url.URL{ + ctx := testutil.ContextWithTimeout(t, testTimeout) + ctx = slogutil.ContextWithLogger(ctx, testLogger) + + r := httptest.NewRequestWithContext(ctx, tc.method, (&url.URL{ Scheme: urlutil.SchemeHTTP, - Host: "link-ip.example", + Host: realHost, Path: tc.path, }).String(), strings.NewReader("")) - // Set some test headers. - r.Header.Set(httphdr.CFConnectingIP, "1.1.1.1") - r.Header.Set(httphdr.Forwarded, "1.1.1.1") - r.Header.Set(httphdr.TrueClientIP, "1.1.1.1") - r.Header.Set(httphdr.XForwardedFor, "1.1.1.1") - r.Header.Set(httphdr.XForwardedHost, "forward.example") - r.Header.Set(httphdr.XForwardedProto, "https") - r.Header.Set(httphdr.XRealIP, "1.1.1.1") + // Set the IP address that should be proxied. + r.RemoteAddr = realRemoteAddr + + // Set some test headers to make sure they're not proxied. + r.Header.Set(httphdr.CFConnectingIP, badRemoteIP) + r.Header.Set(httphdr.Forwarded, badRemoteIP) + r.Header.Set(httphdr.TrueClientIP, badRemoteIP) + r.Header.Set(httphdr.XForwardedFor, badRemoteIP) + r.Header.Set(httphdr.XForwardedHost, "bad.example") + r.Header.Set(httphdr.XForwardedProto, "foo") + r.Header.Set(httphdr.XRealIP, badRemoteIP) rw := httptest.NewRecorder() diff --git a/internal/websvc/server.go b/internal/websvc/server.go new file mode 100644 index 0000000..a9c43c3 --- /dev/null +++ b/internal/websvc/server.go @@ -0,0 +1,184 @@ +package websvc + +import ( + "context" + "crypto/tls" + "fmt" + "log/slog" + "net" + "net/http" + "net/netip" + "net/url" + "sync" + "time" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/netutil/urlutil" +) + +// server contains an *http.Server as well as entities and data associated with +// it. +// +// TODO(a.garipov): Join with similar structs in other projects and move to +// golibs/netutil/httputil. +// +// TODO(a.garipov): Once the above standardization is complete, consider +// merging debugsvc and websvc into a single httpsvc. +type server struct { + // mu protects http, logger, listener, and url. + mu *sync.Mutex + http *http.Server + logger *slog.Logger + listener net.Listener + url *url.URL + + initialAddr netip.AddrPort +} + +// loggerKeyServer is the key used by [server] to identify itself. +const loggerKeyServer = "server" + +// serverConfig is the configuration of a server. +type serverConfig struct { + // BaseLogger is used to create the initial logger for the server. It must + // not be nil. + BaseLogger *slog.Logger + + // TLSConf is the optional TLS configuration. + TLSConf *tls.Config + + // BaseContext is an optional function that that returns the base context + // for incoming requests on this server. See [http.Server.BaseContext]. + BaseContext func(l net.Listener) (ctx context.Context) + + // Handler is the HTTP handler for this server. It must not be nil. + Handler http.Handler + + // InitialAddress is the initial address for the server. It may have a zero + // port, in which case the real port will be set in [server.serve]. It must + // be set. + InitialAddress netip.AddrPort + + // Timeout is the optional timeout for all operations. + // + // TODO(a.garipov): Consider more fine-grained timeouts. + Timeout time.Duration +} + +// newServer returns a *server that is ready to serve HTTP queries. The TCP +// listener is not started. c must not be nil and must be valid. +func newServer(c *serverConfig) (s *server) { + u := &url.URL{ + Scheme: urlutil.SchemeHTTP, + Host: c.InitialAddress.String(), + } + + if c.TLSConf != nil { + u.Scheme = urlutil.SchemeHTTPS + } + + logger := c.BaseLogger.With(loggerKeyServer, u) + + return &server{ + mu: &sync.Mutex{}, + http: &http.Server{ + Handler: c.Handler, + TLSConfig: c.TLSConf, + ReadTimeout: c.Timeout, + ReadHeaderTimeout: c.Timeout, + WriteTimeout: c.Timeout, + IdleTimeout: c.Timeout, + ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelDebug), + BaseContext: c.BaseContext, + }, + logger: logger, + url: u, + + initialAddr: c.InitialAddress, + } +} + +// localAddr returns the local address of the server if the server has started +// listening; otherwise, it returns nil. +func (s *server) localAddr() (addr net.Addr) { + s.mu.Lock() + defer s.mu.Unlock() + + if l := s.listener; l != nil { + return l.Addr() + } + + return nil +} + +// serve starts s. baseLogger is used as a base logger for s. If s fails to +// serve with anything other than [http.ErrServerClosed], it causes an unhandled +// panic. It is intended to be used as a goroutine. +// +// TODO(a.garipov): Improve error handling. +func (s *server) serve(ctx context.Context, baseLogger *slog.Logger) { + tcpListener, err := net.ListenTCP("tcp", net.TCPAddrFromAddrPort(s.initialAddr)) + if err != nil { + s.logger.ErrorContext(ctx, "listening tcp", slogutil.KeyError, err) + + panic(fmt.Errorf("websvc: listening tcp: %w", err)) + } + + var listener net.Listener + if s.http.TLSConfig == nil { + listener = tcpListener + } else { + listener = tls.NewListener(tcpListener, s.http.TLSConfig) + } + + func() { + s.mu.Lock() + defer s.mu.Unlock() + + s.listener = listener + + // Reassign the address in case the port was zero. + s.url.Host = listener.Addr().String() + s.logger = baseLogger.With(loggerKeyServer, s.url) + s.http.ErrorLog = slog.NewLogLogger(s.logger.Handler(), slog.LevelDebug) + }() + + s.logger.InfoContext(ctx, "starting") + defer s.logger.InfoContext(ctx, "started") + + err = s.http.Serve(listener) + if err == nil || errors.Is(err, http.ErrServerClosed) { + return + } + + s.logger.ErrorContext(ctx, "serving", slogutil.KeyError, err) + + panic(fmt.Errorf("websvc: serving: %w", err)) +} + +// shutdown shuts s down. +func (s *server) shutdown(ctx context.Context) (err error) { + s.mu.Lock() + defer s.mu.Unlock() + + var errs []error + err = s.http.Shutdown(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("shutting down server %s: %w", s.url, err)) + } + + // Close the listener separately, as it might not have been closed if the + // context has been canceled. + // + // NOTE: The listener could remain uninitialized if [net.ListenTCP] failed + // in [s.serve]. + if l := s.listener; l != nil { + err = l.Close() + if err != nil && !errors.Is(err, net.ErrClosed) { + errs = append(errs, fmt.Errorf("closing listener for server %s: %w", s.url, err)) + } + } + + return errors.Join(errs...) +} diff --git a/internal/websvc/static.go b/internal/websvc/static.go index 8dc5bef..29d0cc8 100644 --- a/internal/websvc/static.go +++ b/internal/websvc/static.go @@ -3,6 +3,8 @@ package websvc import ( "maps" "net/http" + + "github.com/AdguardTeam/golibs/logutil/slogutil" ) // StaticContent serves static content with the given content type. Elements @@ -37,6 +39,8 @@ func (sc StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, err := w.Write(f.Content) if err != nil { - logErrorByType(err, "websvc: static content: writing %s: %s", p, err) + ctx := r.Context() + l := slogutil.MustLoggerFromContext(ctx) + l.Log(ctx, levelForError(err), "writing static content", slogutil.KeyError, err) } } diff --git a/internal/websvc/static_test.go b/internal/websvc/static_test.go index 1a331c2..dc16849 100644 --- a/internal/websvc/static_test.go +++ b/internal/websvc/static_test.go @@ -4,9 +4,11 @@ import ( "net/http" "testing" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/websvc" "github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,7 +23,11 @@ func TestService_ServeHTTP_static(t *testing.T) { } c := &websvc.Config{ + Logger: testLogger, StaticContent: staticContent, + DNSCheck: http.NotFoundHandler(), + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, } svc := websvc.New(c) @@ -39,7 +45,22 @@ func TestService_ServeHTTP_static(t *testing.T) { respHdr := http.Header{ httphdr.ContentType: []string{"image/x-icon"}, - httphdr.Server: []string{"AdGuardDNS/"}, } assertResponseWithHeaders(t, svc, "/favicon.ico", http.StatusOK, respHdr) } + +// assertResponseWithHeaders is a helper function that checks status code and +// headers of HTTP response. +func assertResponseWithHeaders( + t *testing.T, + svc *websvc.Service, + path string, + statusCode int, + respHdr http.Header, +) { + t.Helper() + + rw := assertResponse(t, svc, path, statusCode) + + assert.Equal(t, respHdr, rw.Header()) +} diff --git a/internal/websvc/websvc.go b/internal/websvc/websvc.go index 88dbc84..e9a6ec5 100644 --- a/internal/websvc/websvc.go +++ b/internal/websvc/websvc.go @@ -3,104 +3,39 @@ package websvc import ( "context" - "crypto/tls" "fmt" + "log/slog" "net/http" - "net/netip" - "net/url" - "time" - "github.com/AdguardTeam/AdGuardDNS/internal/agdservice" - "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" + "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/netutil/httputil" "github.com/AdguardTeam/golibs/service" ) -// Config is the AdGuard DNS web service configuration structure. -type Config struct { - // AdultBlocking is the optional adult-blocking block-page web server. - AdultBlocking *BlockPageServerConfig - - // GeneralBlocking is the optional general block-page web server. - GeneralBlocking *BlockPageServerConfig - - // SafeBrowsing is the optional safe-browsing block-page web server. - SafeBrowsing *BlockPageServerConfig - - // LinkedIP is the optional linked IP web server. - LinkedIP *LinkedIPServer - - // RootRedirectURL is the URL to which root HTTP requests are redirected. - // If not set, these requests are responded with a 404 page. - RootRedirectURL *url.URL - - // StaticContent is the content that is served statically at the given - // paths. It must not be nil; use [http.NotFoundHandler] if not needed. - StaticContent http.Handler - - // DNSCheck is the HTTP handler for DNS checks. - DNSCheck http.Handler - - // ErrColl is used to collect linked IP proxy errors and other errors. - ErrColl errcoll.Interface - - // Error404 is the content of the HTML page for the 404 status. If not set, - // a simple plain text 404 response is served. - Error404 []byte - - // Error500 is the content of the HTML page for the 500 status. If not set, - // a simple plain text 500 response is served. - Error500 []byte - - // NonDoHBind are the bind addresses and optional TLS configuration for the - // web service in addition to the ones in the DNS-over-HTTPS handlers. - NonDoHBind []*BindData - - // Timeout is the timeout for all server operations. - Timeout time.Duration -} - -// LinkedIPServer is the linked IP server configuration. -type LinkedIPServer struct { - // TargetURL is the URL to which linked IP API requests are proxied. - TargetURL *url.URL - - // Bind are the addresses on which to serve the linked IP API. - Bind []*BindData -} - -// BindData is data for binding one HTTP server to an address. -type BindData struct { - // TLS is the optional TLS configuration. - TLS *tls.Config - - // Address is the binding address. - Address netip.AddrPort -} - // Service is the AdGuard DNS web service. A nil *Service serves a simple // plain-text 404 page. type Service struct { - rootRedirectURL string - - staticContent http.Handler - - dnsCheck http.Handler - - error404 []byte - error500 []byte + logger *slog.Logger adultBlockingBPS *blockPageServer generalBlockingBPS *blockPageServer safeBrowsingBPS *blockPageServer - linkedIP []*http.Server - adultBlocking []*http.Server - generalBlocking []*http.Server - safeBrowsing []*http.Server - nonDoH []*http.Server + dnsCheck http.Handler + staticContent http.Handler + + rootRedirectURL string + + error404 []byte + error500 []byte + + adultBlocking []*server + generalBlocking []*server + linkedIP []*server + nonDoH []*server + safeBrowsing []*server } // New returns a new properly initialized *Service. If c is nil, svc is a nil @@ -111,25 +46,26 @@ func New(c *Config) (svc *Service) { return nil } - adultBlockingBPS := newBlockPageServer(c.AdultBlocking, adultBlockingName) - generalBlockingBPS := newBlockPageServer(c.GeneralBlocking, generalBlockingName) - safeBrowsingBPS := newBlockPageServer(c.SafeBrowsing, safeBrowsingName) + adultBlockingBPS := newBlockPageServer(c.AdultBlocking, c.Logger, srvGrpAdultBlockingPage) + generalBlockingBPS := newBlockPageServer(c.GeneralBlocking, c.Logger, srvGrpGeneralBlockingPage) + safeBrowsingBPS := newBlockPageServer(c.SafeBrowsing, c.Logger, srvGrpSafeBrowsingPage) svc = &Service{ - staticContent: c.StaticContent, - - dnsCheck: c.DNSCheck, - - error404: c.Error404, - error500: c.Error500, + logger: c.Logger, adultBlockingBPS: adultBlockingBPS, generalBlockingBPS: generalBlockingBPS, safeBrowsingBPS: safeBrowsingBPS, - adultBlocking: blockPageServers(adultBlockingBPS, c.Timeout), - generalBlocking: blockPageServers(generalBlockingBPS, c.Timeout), - safeBrowsing: blockPageServers(safeBrowsingBPS, c.Timeout), + dnsCheck: c.DNSCheck, + staticContent: c.StaticContent, + + error404: c.Error404, + error500: c.Error500, + + adultBlocking: newBlockPageServers(c.Logger, adultBlockingBPS, c.Timeout), + generalBlocking: newBlockPageServers(c.Logger, generalBlockingBPS, c.Timeout), + safeBrowsing: newBlockPageServers(c.Logger, safeBrowsingBPS, c.Timeout), } if c.RootRedirectURL != nil { @@ -138,42 +74,60 @@ func New(c *Config) (svc *Service) { svc.rootRedirectURL = c.RootRedirectURL.String() } - if l := c.LinkedIP; l != nil && l.TargetURL != nil { + if l := c.LinkedIP; l != nil { + logger := svc.logger.With(loggerKeyGroup, srvGrpLinkedIP) for _, b := range l.Bind { - addr := b.Address.String() - h := linkedIPHandler(l.TargetURL, c.ErrColl, addr, c.Timeout) - errLog := log.StdLog(fmt.Sprintf("websvc: linked ip: %s", addr), log.DEBUG) - svc.linkedIP = append(svc.linkedIP, &http.Server{ - Addr: addr, - Handler: h, - TLSConfig: b.TLS, - ErrorLog: errLog, - ReadTimeout: c.Timeout, - WriteTimeout: c.Timeout, - IdleTimeout: c.Timeout, - ReadHeaderTimeout: c.Timeout, - }) + proxyLogger := logger.With("proxy_addr", b.Address) + h := httputil.Wrap( + newLinkedIPHandler(l.TargetURL, c.ErrColl, proxyLogger, c.Timeout), + httputil.NewLogMiddleware(logger, slog.LevelDebug), + ) + + svc.linkedIP = append(svc.linkedIP, newServer(&serverConfig{ + BaseLogger: logger, + TLSConf: b.TLS, + Handler: h, + InitialAddress: b.Address, + Timeout: c.Timeout, + })) } } for _, b := range c.NonDoHBind { - addr := b.Address.String() - errLog := log.StdLog(fmt.Sprintf("websvc: non-doh: %s", addr), log.DEBUG) - svc.nonDoH = append(svc.nonDoH, &http.Server{ - Addr: addr, - Handler: svc, - TLSConfig: b.TLS, - ErrorLog: errLog, - ReadTimeout: c.Timeout, - WriteTimeout: c.Timeout, - IdleTimeout: c.Timeout, - ReadHeaderTimeout: c.Timeout, - }) + logger := svc.logger.With(loggerKeyGroup, srvGrpNonDoH) + h := httputil.Wrap( + svc, + httputil.ServerHeaderMiddleware(agdhttp.UserAgent()), + httputil.NewLogMiddleware(logger, slog.LevelDebug), + ) + + svc.nonDoH = append(svc.nonDoH, newServer(&serverConfig{ + BaseLogger: logger, + TLSConf: b.TLS, + Handler: h, + InitialAddress: b.Address, + Timeout: c.Timeout, + })) } return svc } +// serverGroup is a semantic alias for names of server groups. +type serverGroup = string + +// Valid server groups. +const ( + srvGrpAdultBlockingPage serverGroup = "adult_blocking_page" + srvGrpGeneralBlockingPage serverGroup = "general_blocking_page" + srvGrpLinkedIP serverGroup = "linked_ip" + srvGrpNonDoH serverGroup = "non_doh" + srvGrpSafeBrowsingPage serverGroup = "safe_browsing_page" +) + +// loggerKeyGroup is the key used by server groups +const loggerKeyGroup = "group" + // type check var _ service.Interface = (*Service)(nil) @@ -182,70 +136,42 @@ var _ service.Interface = (*Service)(nil) // may be nil. err is always nil; if any endpoint fails to start, it panics. // // TODO(a.garipov): Wait for the services to go online. -// -// TODO(a.garipov): Use the context for cancelation. -func (svc *Service) Start(_ context.Context) (err error) { +func (svc *Service) Start(ctx context.Context) (err error) { if svc == nil { return nil } - for _, srv := range svc.linkedIP { - go mustStartServer(srv) + svc.logger.InfoContext(ctx, "starting") + defer svc.logger.InfoContext(ctx, "started") - log.Info("websvc: linked ip %s: server is started", srv.Addr) + for _, srv := range svc.linkedIP { + logger := svc.logger.With(loggerKeyGroup, srvGrpLinkedIP) + go srv.serve(ctx, logger) } for _, srv := range svc.adultBlocking { - go mustStartServer(srv) - - log.Info("websvc: adult blocking %s: server is started", srv.Addr) + logger := svc.logger.With(loggerKeyGroup, srvGrpAdultBlockingPage) + go srv.serve(ctx, logger) } for _, srv := range svc.generalBlocking { - go mustStartServer(srv) - - log.Info("websvc: general blocking %s: server is started", srv.Addr) + logger := svc.logger.With(loggerKeyGroup, srvGrpGeneralBlockingPage) + go srv.serve(ctx, logger) } for _, srv := range svc.safeBrowsing { - go mustStartServer(srv) - - log.Info("websvc: safe browsing %s: server is started", srv.Addr) + logger := svc.logger.With(loggerKeyGroup, srvGrpSafeBrowsingPage) + go srv.serve(ctx, logger) } for _, srv := range svc.nonDoH { - go mustStartServer(srv) - - log.Info("websvc: non-doh %s: server is started", srv.Addr) + logger := svc.logger.With(loggerKeyGroup, srvGrpNonDoH) + go srv.serve(ctx, logger) } return nil } -// mustStartServer is a helper function that starts srv and panics if there are -// any errors. It panics if one of the servers could not start, bringing down -// the whole service. -func mustStartServer(srv *http.Server) { - if srv.TLSConfig == nil { - err := srv.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - panic(err) - } - - return - } - - l, err := tls.Listen("tcp", srv.Addr, srv.TLSConfig) - if err != nil { - panic(err) - } - - err = srv.Serve(l) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - panic(err) - } -} - // Shutdown implements the [service.Interface] interface for *Service. svc may // be nil. func (svc *Service) Shutdown(ctx context.Context) (err error) { @@ -253,20 +179,23 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) { return nil } - serverGroups := container.KeyValues[string, []*http.Server]{{ - Key: "linked ip", + svc.logger.InfoContext(ctx, "shutting down") + defer svc.logger.InfoContext(ctx, "shut down") + + serverGroups := container.KeyValues[serverGroup, []*server]{{ + Key: srvGrpLinkedIP, Value: svc.linkedIP, }, { - Key: adultBlockingName, + Key: srvGrpAdultBlockingPage, Value: svc.adultBlocking, }, { - Key: generalBlockingName, + Key: srvGrpGeneralBlockingPage, Value: svc.generalBlocking, }, { - Key: safeBrowsingName, + Key: srvGrpSafeBrowsingPage, Value: svc.safeBrowsing, }, { - Key: "non-doh", + Key: srvGrpNonDoH, Value: svc.nonDoH, }} @@ -281,33 +210,31 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) { return nil } -// shutdownServers is a helper function that shuts down srvs and logs successful -// shutdowns. -func shutdownServers(ctx context.Context, srvs []*http.Server, name string) (err error) { - for _, srv := range srvs { - err = srv.Shutdown(ctx) +// shutdownServers is a helper function that shuts down srvs. +func shutdownServers(ctx context.Context, srvs []*server, g serverGroup) (err error) { + var errs []error + for i, srv := range srvs { + err = srv.shutdown(ctx) if err != nil { - return fmt.Errorf("%s server %s shutdown: %w", name, srv.Addr, err) + errs = append(errs, fmt.Errorf("server group %s: server at index %d: %w", g, i, err)) } - - log.Info("websvc: %s %s: server is shutdown", name, srv.Addr) } - return nil + return errors.Join(errs...) } // type check -var _ agdservice.Refresher = (*Service)(nil) +var _ service.Refresher = (*Service)(nil) -// Refresh implements the [agdservice.Refresher] interface for *Service. svc -// may be nil. +// Refresh implements the [service.Refresher] interface for *Service. svc may +// be nil. func (svc *Service) Refresh(ctx context.Context) (err error) { if svc == nil { return nil } - log.Info("websvc: refresh started") - defer log.Info("websvc: refresh finished") + svc.logger.InfoContext(ctx, "refresh started") + defer svc.logger.InfoContext(ctx, "refresh finished") servers := []*blockPageServer{ svc.adultBlockingBPS, @@ -319,7 +246,7 @@ func (svc *Service) Refresh(ctx context.Context) (err error) { for _, srv := range servers { err = srv.Refresh(ctx) if err != nil { - errs = append(errs, fmt.Errorf("refreshing %s block page server: %w", srv.name, err)) + errs = append(errs, fmt.Errorf("refreshing %s block page server: %w", srv.group, err)) } } diff --git a/internal/websvc/websvc_internal_test.go b/internal/websvc/websvc_internal_test.go new file mode 100644 index 0000000..f370367 --- /dev/null +++ b/internal/websvc/websvc_internal_test.go @@ -0,0 +1,50 @@ +package websvc + +import ( + "fmt" + "net" + "time" + + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/logutil/slogutil" +) + +// testTimeout is the common timeout for tests. +// +// TODO(a.garipov): Make the test external and DRY with one in websvc_test.go +const testTimeout = 1 * time.Second + +// testLogger is the common logger for tests. +// +// TODO(a.garipov): Make the test external and DRY with one in websvc_test.go +var testLogger = slogutil.NewDiscardLogger() + +// LocalAddrs returns the local addresses of the servers in group g. Addrs may +// contain nils. +// +// TODO(a.garipov): Use in tests. +func (svc *Service) LocalAddrs(g serverGroup) (addrs []net.Addr) { + switch g { + case srvGrpAdultBlockingPage: + return serverAddrs(svc.adultBlocking) + case srvGrpGeneralBlockingPage: + return serverAddrs(svc.generalBlocking) + case srvGrpLinkedIP: + return serverAddrs(svc.linkedIP) + case srvGrpNonDoH: + return serverAddrs(svc.nonDoH) + case srvGrpSafeBrowsingPage: + return serverAddrs(svc.safeBrowsing) + default: + panic(fmt.Errorf("server group: %w: %q", errors.ErrBadEnumValue, g)) + } +} + +// serverAddrs collects the addresses of the servers. +func serverAddrs(srvs []*server) (addrs []net.Addr) { + for _, s := range srvs { + addrs = append(addrs, s.localAddr()) + } + + return addrs +} diff --git a/internal/websvc/websvc_test.go b/internal/websvc/websvc_test.go index 2a6158c..84905dc 100644 --- a/internal/websvc/websvc_test.go +++ b/internal/websvc/websvc_test.go @@ -9,22 +9,30 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/websvc" "github.com/AdguardTeam/golibs/httphdr" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil/urlutil" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// testTimeout is the common timeout for tests. const testTimeout = 1 * time.Second -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} +// testLogger is the common logger for tests. +var testLogger = slogutil.NewDiscardLogger() func TestNew(t *testing.T) { - startService(t, &websvc.Config{}) + startService(t, &websvc.Config{ + Logger: testLogger, + StaticContent: http.NotFoundHandler(), + DNSCheck: http.NotFoundHandler(), + ErrColl: agdtest.NewErrorCollector(), + Timeout: testTimeout, + }) } func TestService_NonDoH(t *testing.T) { @@ -38,6 +46,7 @@ func TestService_NonDoH(t *testing.T) { require.NoError(pt, err) }) + // TODO(a.garipov): Do not use hardcoded ports. nonDoHPort := netip.MustParseAddrPort("127.0.0.1:3003") nonDoHBind := []*websvc.BindData{{ TLS: nil, @@ -46,9 +55,11 @@ func TestService_NonDoH(t *testing.T) { notFoundContent := []byte("not found") c := &websvc.Config{ + Logger: testLogger, StaticContent: http.NotFoundHandler(), DNSCheck: mockHandler, NonDoHBind: nonDoHBind, + ErrColl: agdtest.NewErrorCollector(), Error404: notFoundContent, Timeout: testTimeout, } diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 952bdb2..9e1c6af 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a significant change is made to this script. # -# AdGuard-Project-Version: 12 +# AdGuard-Project-Version: 14 verbose="${VERBOSE:-0}" readonly verbose @@ -26,8 +26,8 @@ set -f -u # Simple analyzers -# blocklist_imports is a simple check against unwanted packages. The following -# packages are banned: +# blocklist_imports is a simple best-effort check against unwanted packages. +# The following packages are banned: # # * Package errors is replaced by our own package in the # github.com/AdguardTeam/golibs module. @@ -67,8 +67,11 @@ set -f -u # * internal/profiledb/internal/filecachepb/unsafe.go: a “safe” unsafe helper # to prevent excessive allocations. # -# TODO(a.garipov): Add golibs/log, client_golang/prometheus/promauto. +# TODO(a.garipov): Add client_golang/prometheus/promauto. blocklist_imports() { + import_or_tab="$(printf '^\\(import \\|\t\\)')" + readonly import_or_tab + find . \ -type 'f' \ '(' \ @@ -79,15 +82,16 @@ blocklist_imports() { -exec \ 'grep' \ '-H' \ - '-e' '[[:space:]]"errors"$' \ - '-e' '[[:space:]]"golang.org/x/exp/maps"$' \ - '-e' '[[:space:]]"golang.org/x/exp/slices"$' \ - '-e' '[[:space:]]"golang.org/x/net/context"$' \ - '-e' '[[:space:]]"io/ioutil"$' \ - '-e' '[[:space:]]"log"$' \ - '-e' '[[:space:]]"reflect"$' \ - '-e' '[[:space:]]"sort"$' \ - '-e' '[[:space:]]"unsafe"$' \ + '-e' "$import_or_tab"'"errors"$' \ + '-e' "$import_or_tab"'"github.com/AdguardTeam/golibs/log"$' \ + '-e' "$import_or_tab"'"golang.org/x/exp/maps"$' \ + '-e' "$import_or_tab"'"golang.org/x/exp/slices"$' \ + '-e' "$import_or_tab"'"golang.org/x/net/context"$' \ + '-e' "$import_or_tab"'"io/ioutil"$' \ + '-e' "$import_or_tab"'"log"$' \ + '-e' "$import_or_tab"'"reflect"$' \ + '-e' "$import_or_tab"'"sort"$' \ + '-e' "$import_or_tab"'"unsafe"$' \ '-n' \ '{}' \ ';' @@ -222,7 +226,7 @@ if [ "$shadow_output" != '' ]; then exit 1 fi -run_linter gosec --quiet ./... "$dnssrvmod" +run_linter gosec --exclude-generated --quiet ./... "$dnssrvmod" run_linter errcheck ./... "$dnssrvmod"