diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29b5bc7..2b806e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,11 +11,247 @@ The format is **not** based on [Keep a Changelog][kec], since the project
+## 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:
+
+ ```yaml
+ upstream:
+ # …
+ servers:
+ - 'tcp://1.1.1.1:53'
+ - '127.0.0.1:5358'
+ timeout: 2s
+ fallback:
+ servers:
+ - 8.8.4.4:53
+ timeout: 1s
+ ```
+
+ with this:
+
+ ```yaml
+ upstream:
+ # …
+ servers:
+ - address: 'tcp://1.1.1.1:53'
+ timeout: 2s
+ - address: '127.0.0.1:5358'
+ timeout: 2s
+ fallback:
+ servers:
+ - address: '8.8.4.4:53'
+ timeout: 1s
+ ```
+
+ Adjust the value and add new ones, if necessary.
+
+
+
+## AGDNS-698 / Build 701
+
+ * The object `dns` has new properties: `read_timeout`, `tcp_idle_timeout`, and
+ `write_timeout`. So replace this:
+
+ ```yaml
+ dns:
+ max_udp_response_size: 1024B
+ ```
+
+ with this:
+
+ ```yaml
+ dns:
+ read_timeout: 2s
+ tcp_idle_timeout: 30s
+ write_timeout: 2s
+ handle_timeout: 1s
+ max_udp_response_size: 1024B
+ ```
+
+ The values in the example are previous defaults.
+
+
+
+## AGDNS-1751 / Build 691
+
+ * The property `upstream.server` has been removed. Its former content is
+ moved to the newly added property `servers`, which now extended to contain
+ a list of URLs of main upstream servers. So replace this:
+
+ ```yaml
+ upstream:
+ # …
+ server: `8.8.8.8:53`
+ ```
+
+ with this:
+
+ ```yaml
+ upstream:
+ # …
+ servers:
+ - `8.8.8.8:53`
+ ```
+
+ Adjust the value and add new ones, if necessary.
+
+
+
+## AGDNS-1759 / Build 684
+
+ * The object `backend` has a new property, `full_refresh_retry_interval`. So
+ replace this:
+
+ ```yaml
+ backend:
+ # …
+ full_refresh_interval: 24h
+ ```
+
+ with this:
+
+ ```yaml
+ backend:
+ # …
+ full_refresh_interval: 24h
+ full_refresh_retry_interval: 1h
+ ```
+
+ Adjust the value, if necessary.
+
+
+
+## AGDNS-1744 / Build 681
+
+ * Metric `forward_request_total` has a new label `network`. This label
+ describes the network type (`tcp` or `udp`), over which an upstream has
+ finished processing request.
+
+
+
+## AGDNS-1738 / Build 678
+
+ * Object `dns` has a new property, describing maximum size of DNS response
+ over UDP protocol.
+
+ ```yaml
+ dns:
+ max_udp_response_size: 1024B
+ handle_timeout: 1s
+ ```
+
+
+
+## AGDNS-1735 / Build 677
+
+ * The property `upstream.fallback` has been changed. Its former content is
+ moved to the newly added property `servers`. The new property `timeout`,
+ which describes query timeout to fallback servers, was added. So replace
+ this:
+
+ ```yaml
+ upstream:
+ fallback:
+ - 1.1.1.1:53
+ - 8.8.8.8:53
+ ```
+
+ with this:
+
+ ```yaml
+ upstream:
+ fallback:
+ servers:
+ - 1.1.1.1:53
+ - 8.8.8.8:53
+ timeout: 1s
+ ```
+
+ Adjust the new values, if necessary. Note that the query timeout to fallback
+ servers was previously defined with `upstream.timeout` property, which now
+ describes the query timeout to the primary servers only.
+
+
+
+## AGDNS-1178 / Build 676
+
+ * The new object `dns` has been added:
+
+ ```yaml
+ dns:
+ handle_timeout: 1s
+ ```
+
+
+
+## AGDNS-1620 / Build 673
+
+ * Object `ratelimit` has two new properties: `quic` and `tcp`. They configure
+ QUIC and TCP connection limits. Example configuration:
+
+ ```yaml
+ ratelimit:
+ # …
+ quic:
+ enabled: true
+ max_streams_per_peer: 100
+ tcp:
+ enabled: true
+ max_pipeline_count: 100
+ ```
+
+
+
+## AGDNS-1684 / Build 661
+
+ * Profile's file cache version was incremented. The new field `access` has
+ been added.
+
+
+
+## AGDNS-1664 / Build 636
+
+ * The environment variables `BILLSTAT_URL` and `PROFILES_URL` no longer
+ support HTTP(s) endpoints. Use GRPC(S) instead.
+
+
+
+## AGDNS-1667 / Build 633
+
+* `ratelimit` configuration properties `back_off_count`, `back_off_duration`
+ and `back_off_period` have been renamed to `backoff_count`,
+ `backoff_duration` and `backoff_period`. So replace this:
+
+ ```yaml
+ ratelimit:
+ back_off_period: 10m
+ back_off_count: 1000
+ back_off_duration: 30m
+ ```
+
+ with this:
+
+ ```yaml
+ ratelimit:
+ backoff_period: 10m
+ backoff_count: 1000
+ backoff_duration: 30m
+ ```
+
+
+
## AGDNS-1607 / Build 617
-* New configuration `access` has been added, it has an a list of AdBlock rules
- to block requests, and a lists of client subnets to block access from.
- Example configuration:
+ * New configuration `access` has been added, it has an a list of AdBlock rules
+ to block requests, and a lists of client subnets to block access from.
+ Example configuration:
```yaml
access:
@@ -31,10 +267,11 @@ The format is **not** based on [Keep a Changelog][kec], since the project
## AGDNS-1619 / Build 611
-* Added a new metric `bill_stat_upload_duration` that counts the duration of
- billing statistics upload.
-* The environment variable `BILLSTAT_URL`, which describes the endpoint for
- backend billing statistics uploader API, now supports GRPC endpoints.
+ * Added a new metric `bill_stat_upload_duration` that counts the duration of
+ billing statistics upload.
+
+ * The environment variable `BILLSTAT_URL`, which describes the endpoint for
+ backend billing statistics uploader API, now supports GRPC endpoints.
@@ -57,7 +294,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project
* The optional property `bind_interfaces` of `server_groups.*.servers`
objects has been changed, property `subnet` is now an array and has been
- ranamed to `subnets`. So replace this:
+ renamed to `subnets`. So replace this:
```yaml
bind_interfaces:
@@ -98,6 +335,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project
## AGDNS-1580 / Build 562
* The environment variable `DNSDB_PATH` has been removed.
+
* New configuration `dnsdb` has been added, it has an enabled/disabled flag
and the property `max_size` which describes the maximum amount of records in
the in-memory buffer. Example configuration:
@@ -944,7 +1182,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project
identifiers, grouped by endpoint identifier and known server names. All
unknown server names are grouped in `other` label:
- ```
+ ```none
# TYPE dns_tls_handshake_total counter
dns_tls_handshake_total{cipher_suite="TLS_AES_128_GCM_SHA256",did_resume="0",negotiated_proto="",proto="tls",server_name="default_dot: other",tls_version="tls1.3"} 4
```
diff --git a/Makefile b/Makefile
index 66973fe..d5d91df 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,7 @@ VERBOSE.MACRO = $${VERBOSE:-0}
BRANCH = $$( git rev-parse --abbrev-ref HEAD )
GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
+GOTOOLCHAIN = go1.21.5
RACE = 0
REVISION = $$( git rev-parse --short HEAD )
VERSION = 0
@@ -32,6 +33,7 @@ ENV = env\
GO="$(GO.MACRO)"\
GOAMD64='$(GOAMD64)'\
GOPROXY='$(GOPROXY)'\
+ GOTOOLCHAIN='$(GOTOOLCHAIN)'\
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
RACE='$(RACE)'\
REVISION="$(REVISION)"\
@@ -51,6 +53,7 @@ test: go-test
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh
+go-fuzz: ; $(ENV) "$(SHELL)" ./scripts/make/go-fuzz.sh
go-gen: ; $(ENV) "$(SHELL)" ./scripts/make/go-gen.sh
go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh
go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh
@@ -70,4 +73,11 @@ go-os-check:
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
+# TODO(a.garipov): Consider adding to scripts/ and the common project
+# structure.
+go-upd-tools:
+ cd ./internal/tools/ &&\
+ "$(GO.MACRO)" get -u &&\
+ "$(GO.MACRO)" mod tidy
+
sync-github: ; $(ENV) "$(SHELL)" ./scripts/make/github-sync.sh
diff --git a/config.dist.yaml b/config.dist.yaml
index 72763f3..378047b 100644
--- a/config.dist.yaml
+++ b/config.dist.yaml
@@ -24,15 +24,13 @@ ratelimit:
subnet_key_len: 48
# The time during which to count the number of times a client has hit the
# rate limit for a back off.
- #
- # TODO(a.garipov): Rename to "backoff_period" along with others.
- back_off_period: 10m
+ backoff_period: 10m
# How many times a client hits the rate limit before being held in the back
# off.
- back_off_count: 1000
+ backoff_count: 1000
# How much a client that has hit the rate limit too often stays in the back
# off.
- back_off_duration: 30m
+ backoff_duration: 30m
# Configuration for the allowlist.
allowlist:
@@ -52,6 +50,19 @@ ratelimit:
stop: 1000
resume: 800
+ # Configuration of QUIC streams limiting.
+ quic:
+ enabled: true
+ # The maximum number of concurrent streams that a peer is allowed to
+ # open.
+ max_streams_per_peer: 100
+
+ # Configuration of TCP pipeline limiting.
+ tcp:
+ enabled: true
+ # The maximum number of processing TCP messages per one connection.
+ max_pipeline_count: 100
+
# Access settings.
access:
# Domains to block.
@@ -78,11 +89,17 @@ cache:
# DNS upstream configuration.
upstream:
- server: '8.8.8.8:53'
- timeout: 2s
+ servers:
+ - address: 'tcp://1.1.1.1:53'
+ timeout: 2s
+ - address: '8.8.4.4:53'
+ timeout: 2s
fallback:
- - 1.1.1.1:53
- - 8.8.8.8:53
+ servers:
+ - address: '1.1.1.1:53'
+ timeout: 1s
+ - address: '8.8.8.8:53'
+ timeout: 1s
healthcheck:
enabled: true
interval: 2s
@@ -90,6 +107,25 @@ upstream:
backoff_duration: 30s
domain_template: '${RANDOM}.neverssl.com'
+# Common DNS settings.
+#
+# TODO(a.garipov): Consider making these settings per-server-group.
+dns:
+ # The timeout for any read from a UDP connection or the first read from
+ # a TCP/TLS connection. It currently doesn't affect DNSCrypt, QUIC, or
+ # HTTPS.
+ read_timeout: 2s
+ # The timeout for consecutive reads from a TCP/TLS connection. It currently
+ # doesn't affect DNSCrypt, QUIC, or HTTPS.
+ tcp_idle_timeout: 30s
+ # The timeout for writing to a UDP or TCP/TLS connection. It currently
+ # doesn't affect DNSCrypt, QUIC, or HTTPS.
+ write_timeout: 2s
+ # The timeout for the entire handling of a single query.
+ handle_timeout: 1s
+ # UDP response size limit.
+ max_udp_response_size: 1024B
+
# DNSDB configuration.
dnsdb:
enabled: true
@@ -106,6 +142,9 @@ backend:
refresh_interval: 15s
# How often AdGuard DNS performs full synchronization.
full_refresh_interval: 24h
+ # How long to wait before attempting a new full synchronization after a
+ # failure.
+ full_refresh_retry_interval: 1h
# How often AdGuard DNS sends the billing statistics to the backend.
bill_stat_interval: 15s
diff --git a/doc/configuration.md b/doc/configuration.md
index 8ec88db..ee99059 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -15,6 +15,7 @@ configuration file with comments.
* [Cache](#cache)
* [Upstream](#upstream)
* [Healthcheck](#upstream-healthcheck)
+ * [Common DNS settings](#dns)
* [DNSDB](#dnsdb)
* [Backend](#backend)
* [Query log](#query_log)
@@ -130,13 +131,13 @@ The `ratelimit` object has the following properties:
**Example:** `1KB`.
- * `back_off_period`:
+ * `backoff_period`:
The time during which to count the number of requests that a client has sent
over the RPS.
**Example:** `10m`.
- * `back_off_duration`:
+ * `backoff_duration`:
How long a client that has hit the RPS too often stays in the backoff state.
**Example:** `30m`.
@@ -159,10 +160,10 @@ The `ratelimit` object has the following properties:
The `ipv6` configuration object has the same properties as the `ipv4` one
above.
- * `back_off_count`:
- Maximum number of requests a client can make above the RPS within
- a `back_off_period`. When a client exceeds this limit, requests aren't
- allowed from client's subnet until `back_off_duration` ends.
+ * `backoff_count`:
+ Maximum number of requests a client can make above the RPS within a
+ `backoff_period`. When a client exceeds this limit, requests aren't allowed
+ from client's subnet until `backoff_duration` ends.
**Example:** `1000`.
@@ -188,11 +189,11 @@ The `ratelimit` object has the following properties:
**Example:** `30s`.
-For example, if `back_off_period` is `1m`, `back_off_count` is `10`, and
+For example, if `backoff_period` is `1m`, `backoff_count` is `10`, and
`ipv4-rps` is `5`, a client (meaning all IP addresses within the subnet defined
by `ipv4-subnet_key_len`) that made 15 requests in one second or 6 requests
(one above `rps`) every second for 10 seconds within one minute, the client is
-blocked for `back_off_duration`.
+blocked for `backoff_duration`.
### Stream connection limit
@@ -218,6 +219,35 @@ The `connection_limit` object has the following properties:
See also [notes on these parameters](#recommended-connection_limit).
+ ### QUIC rate limiting
+
+The `quic` object has the following properties:
+
+ * `enabled`:
+ Whether or not the QUIC connections rate limiting should be enforced.
+
+ **Example:** `true`.
+
+ * `max_streams_per_peer`:
+ The maximum number of concurrent streams that a peer is allowed to open.
+
+ **Example:** `1000`.
+
+ ### TCP rate limiting
+
+The `tcp` object has the following properties:
+
+ * `enabled`:
+ Whether or not the TCP rate limiting should be enforced.
+
+ **Example:** `true`.
+
+ * `max_pipeline_count`:
+ The maximum number of simultaneously processing TCP messages per one
+ connection.
+
+ **Example:** `1000`.
+
[env-consul_allowlist_url]: environment.md#CONSUL_ALLOWLIST_URL
@@ -269,26 +299,45 @@ The `cache` object has the following properties:
The `upstream` object has the following properties:
- * `server`:
- The URL of the main upstream server, in the `[scheme://]ip:port` format.
+ * `servers`:
+ The array of the main upstream servers URLs, in the `[scheme://]ip:port`
+ format and its timeouts for main upstream DNS requests, as a human-readable
+ duration.
- **Examples:**
+ **Property example:**
- - `8.8.8.8:53`: regular DNS (over UDP with TCP fallback).
- - `tcp://1.1.1.1:53`: regular DNS (over TCP).
- - `udp://1.1.1.1:53`: regular DNS (over UDP).
-
- * `timeout`:
- Timeout for all outgoing DNS requests, as a human-readable duration.
-
- **Example:** `2s`.
+ ```yaml
+ 'servers':
+ # Regular DNS (over UDP with TCP fallback).
+ - address: '8.8.8.8:53'
+ timeout: 2s
+ # Regular DNS (over TCP).
+ - address: 'tcp://1.1.1.1:53'
+ timeout: 2s
+ # Regular DNS (over UDP).
+ - address: 'udp://1.1.1.1:53'
+ timeout: 2s
+ ```
* `fallback`:
- The array of addresses of the fallback upstream servers, in the `ip:port`
- format. These are use used in case a network error occurs while requesting
- the main upstream server.
+ Fallback servers configuration. It has the following properties:
- **Example:** `['1.1.1.1:53', '[2001:4860:4860::8888]:53']`.
+ * `servers`:
+ The array of the fallback upstream servers URLs, in the
+ `[scheme://]ip:port` format and its timeouts for upstream DNS requests,
+ as a human-readable duration. These are use used in case a network error
+ occurs while requesting the main upstream server. This property has the
+ same format as [`upstream-servers`](#upstream-servers) above.
+
+ **Property example:**
+
+ ```yaml
+ 'servers':
+ - address: '1.1.1.1:53'
+ timeout: 2s
+ - address: '[2001:4860:4860::8888]:53'
+ timeout: 2s
+ ```
* `healthcheck`: Healthcheck configuration. See
[below](#upstream-healthcheck).
@@ -341,9 +390,46 @@ connection to the main upstream as restored, and requests are routed back to it.
+## DNS
+
+The `dns` object has the following properties:
+
+ * `read_timeout`:
+ The timeout for any read from a UDP connection or the first read from
+ a TCP/TLS connection, as a human-readable duration. It currently doesn't
+ affect DNSCrypt, QUIC, or HTTPS.
+
+ **Example:** `2s`.
+
+ * `tcp_idle_timeout`:
+ The timeout for consecutive reads from a TCP/TLS connection, as a
+ human-readable duration. It currently doesn't affect DNSCrypt, QUIC, or
+ HTTPS.
+
+ **Example:** `30s`.
+
+ * `write_timeout`:
+ The timeout for writing to a UDP or TCP/TLS connection, as a human-readable
+ duration. It currently doesn't affect DNSCrypt, QUIC, or HTTPS.
+
+ **Example:** `2s`.
+
+ * `handle_timeout`:
+ The timeout for the entire handling of a single query, as a human-readable
+ duration.
+
+ **Example:** `1s`.
+
+ * `max_udp_response_size`:
+ The maximum size of DNS response over UDP protocol.
+
+ **Example:** `1024B`.
+
+
+
## DNSDB
-The `DNSDB` object has the following properties:
+The `dnsdb` object has the following properties:
* `enabled`:
If true, the DNSDB memory buffer is enabled.
@@ -382,6 +468,13 @@ The `backend` object has the following properties:
**Example:** `24h`.
+ * `full_refresh_retry_interval`:
+ How long to wait before attempting a new full profile synchronization after
+ a failure, as a human-readable duration. It is recommended to keep this
+ value greater than [`refresh_interval`](#backend-refresh_interval).
+
+ **Example:** `1h`.
+
* `bill_stat_interval`:
How often AdGuard DNS sends the billing statistics to the backend, as
a human-readable duration.
@@ -552,9 +645,9 @@ The optional `web` object has the following properties:
The optional listen addresses and optional TLS configuration for the web
service in addition to the ones in the DNS-over-HTTPS handlers. The
`certificates` array has the same format as the one in a server group's [TLS
- settings](#sg-*-tls). In the special case of `GET /robots.txt` requests, a
- special response is served; this response could be overwritten with static
- content.
+ settings](#server_groups-*-tls). In the special case of `GET /robots.txt`
+ requests, a special response is served; this response could be overwritten
+ with static content.
**Property example:**
@@ -604,7 +697,6 @@ The optional `web` object has the following properties:
**Example:** `30s`.
[http-block-pages]: http.md#block-pages
-[http-dnscheck-test]: http.md#dhscheck-test
[http-linked-ip-proxy]: http.md#linked-ip-proxy
@@ -677,7 +769,7 @@ The `filters` object has the following properties:
* `refresh_interval`:
How often AdGuard DNS refreshes the rule-list filters from the filter index,
as well as the blocked services list from the [blocked list
- index][env-blocked_services)].
+ index][env-blocked_services].
**Example:** `1h`.
diff --git a/doc/development.md b/doc/development.md
index 6b52219..5020058 100644
--- a/doc/development.md
+++ b/doc/development.md
@@ -13,7 +13,7 @@
Development is supported on Linux and macOS (aka Darwin) systems.
-1. Install Go 1.20 or later.
+1. Install Go 1.21 or later.
1. Call `make init` to set up the Git pre-commit hook.
@@ -74,11 +74,14 @@ This is not an extensive list. See `../Makefile`.
-
-
../internal/agd/country_generate.go
;
+ ../internal/geoip/country_generate.go
;
-
../internal/geoip/asntops_generate.go
;
+ -
+
../internal/ecscache/ecsblockilist_generate.go
;
+
-
../internal/profiledb/internal/filecachepb/filecache.pb.go
.
@@ -207,7 +210,7 @@ rm -f -r ./test/cache/
mkdir ./test/cache
curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoIP2-Country-Test.mmdb' -o ./test/GeoIP2-Country-Test.mmdb
curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoIP2-City-Test.mmdb' -o ./test/GeoIP2-City-Test.mmdb
-curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoLite2-ASN-Test.mmdb' -o ./test/GeoLite2-ASN-Test.mmdb
+curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoIP2-ISP-Test.mmdb' -o ./test/GeoIP2-ISP-Test.mmdb
```
@@ -228,6 +231,9 @@ You'll need to supply the following:
See the [external HTTP API documentation][externalhttp].
+You may use `go run ./scripts/backend` to start mock GRPC server for
+`BILLSTAT_URL` and `PROFILES_URL` endpoints.
+
You may need to change the listen ports in `config.yaml` which are less than
1024 to some other ports. Otherwise, `sudo` or `doas` is required to run
`AdGuardDNS`.
@@ -250,7 +256,7 @@ If you're using an OS different from Linux, you also need to make these changes:
```sh
env \
ADULT_BLOCKING_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/adult_blocking.txt' \
- BILLSTAT_URL='https://httpbin.agrd.workers.dev/post' \
+ BILLSTAT_URL='grpc://localhost:6062' \
BLOCKED_SERVICE_INDEX_URL='https://adguardteam.github.io/HostlistsRegistry/assets/services.json' \
CONSUL_ALLOWLIST_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/consul_allowlist.json' \
CONFIG_PATH='./config.yaml' \
@@ -258,10 +264,10 @@ env \
FILTER_CACHE_PATH='./test/cache' \
NEW_REG_DOMAINS_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/nrd.txt' \
PROFILES_CACHE_PATH='./test/profilecache.pb' \
- PROFILES_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/profiles' \
+ PROFILES_URL='grpc://localhost:6062' \
SAFE_BROWSING_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/safe_browsing.txt' \
GENERAL_SAFE_SEARCH_URL='https://adguardteam.github.io/HostlistsRegistry/assets/engines_safe_search.txt' \
- GEOIP_ASN_PATH='./test/GeoLite2-ASN-Test.mmdb' \
+ GEOIP_ASN_PATH='./test/GeoIP2-ISP-Test.mmdb' \
GEOIP_COUNTRY_PATH='./test/GeoIP2-City-Test.mmdb' \
QUERYLOG_PATH='./test/cache/querylog.jsonl' \
LINKED_IP_TARGET_URL='https://httpbin.agrd.workers.dev/anything' \
diff --git a/doc/environment.md b/doc/environment.md
index 421c48c..deace96 100644
--- a/doc/environment.md
+++ b/doc/environment.md
@@ -9,12 +9,12 @@ sensitive configuration. All other configuration is stored in the
* [`ADULT_BLOCKING_URL`](#ADULT_BLOCKING_URL)
* [`BILLSTAT_URL`](#BILLSTAT_URL)
* [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL)
+ * [`CONFIG_PATH`](#CONFIG_PATH)
* [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL)
* [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL)
* [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL)
- * [`CONFIG_PATH`](#CONFIG_PATH)
- * [`FILTER_INDEX_URL`](#FILTER_INDEX_URL)
* [`FILTER_CACHE_PATH`](#FILTER_CACHE_PATH)
+ * [`FILTER_INDEX_URL`](#FILTER_INDEX_URL)
* [`GENERAL_SAFE_SEARCH_URL`](#GENERAL_SAFE_SEARCH_URL)
* [`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`](#GEOIP_ASN_PATH)
* [`LINKED_IP_TARGET_URL`](#LINKED_IP_TARGET_URL)
@@ -25,8 +25,8 @@ sensitive configuration. All other configuration is stored in the
* [`PROFILES_CACHE_PATH`](#PROFILES_CACHE_PATH)
* [`PROFILES_URL`](#PROFILES_URL)
* [`QUERYLOG_PATH`](#QUERYLOG_PATH)
- * [`RESEARCH_METRICS`](#RESEARCH_METRICS)
* [`RESEARCH_LOGS`](#RESEARCH_LOGS)
+ * [`RESEARCH_METRICS`](#RESEARCH_METRICS)
* [`RULESTAT_URL`](#RULESTAT_URL)
* [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL)
* [`SENTRY_DSN`](#SENTRY_DSN)
@@ -49,9 +49,8 @@ The URL of source list of rules for adult blocking filter.
## `BILLSTAT_URL`
-The base backend URL for backend billing statistics uploader API. Supports HTTP
-and GRPC protocols. In case of HTTP the backend endpoint must reply with a 200
-status code on success. See the [external HTTP API requirements
+The base backend URL for backend billing statistics uploader API. Supports
+GRPC (`grpc://` and`grpcs://`) URLs. See the [external HTTP API requirements
section][ext-billstat].
**Default:** No default value, the variable is **required.**
@@ -239,14 +238,12 @@ The profile cache is read on start and is later updated on every
## `PROFILES_URL`
-The base backend URL for profiles API. Supports HTTP (`http://` and `https://`)
-and GRPC (`grpc://` and `grpcs://`) URLs. In case of HTTP the backend endpoint
-must reply with a 200 status code on success. See the [external API
-requirements section][ext-profiles].
+The base backend URL for profiles API. Supports GRPC (`grpc://` and`grpcs://`)
+URLs. See the [external API requirements section][ext-profiles].
**Default:** No default value, the variable is **required.**
-[ext-profiles]: externalhttp.md#profiles-backend
+[ext-profiles]: externalhttp.md#backend-profiles
diff --git a/doc/externalhttp.md b/doc/externalhttp.md
index d14837f..7d576fb 100644
--- a/doc/externalhttp.md
+++ b/doc/externalhttp.md
@@ -29,26 +29,8 @@ document should set the `Server` header in their replies.
## Backend Billing Statistics
This is the service to which the [`BILLSTAT_URL`][env-billstat_url] environment
-variable points. Supports `http(s):` and `grpc(s)` URLs. In case of GRPC
-protocol, the service must correspond to `./internal/backendpb/backend.proto`.
-In case of HTTP protocol this service must provide one endpoint:
-`POST /dns_api/v1/devices_activity`, it must respond with a `200 OK` response
-code and accept a JSON document in the following format:
-
-```json
-{
- "devices": [
- {
- "client_country": "AU",
- "device_id": "abcd1234",
- "time_ms": 1624443079309,
- "asn": 1234,
- "queries": 1000,
- "proto": 1
- }
- ]
-}
-```
+variable points. Supports `grpc(s)` URLs. The service must correspond to
+`./internal/backendpb/backend.proto`.
[env-billstat_url]: environment.md#BILLSTAT_URL
@@ -57,80 +39,8 @@ code and accept a JSON document in the following format:
## Backend Profiles Service
This is the service to which the [`PROFILES_URL`][env-profiles_url] environment
-variable points. Supports `http(s):` and `grpc(s)` URLs. In case of GRPC
-protocol, the service must correspond to `./internal/backendpb/backend.proto`.
-In case of HTTP protocol this service must provide one endpoint:
-`GET /dns_api/v1/settings`, it must respond with a `200 OK` response code and
-accept a JSON document in the following format:
-
-```json
-{
- "sync_time": 1624443079309,
- "settings": [
- {
- "dns_id": "83f3ea8f",
- "filtering_enabled": true,
- "query_log_enabled": true,
- "safe_browsing":
- {
- "enabled": true
- },
- "deleted": true,
- "block_private_relay": false,
- "devices": [
- {
- "id": "0d7724fa",
- "name": "Device 1",
- "filtering_enabled": true,
- "linked_ip": "1.2.3.4"
- }
- ],
- "parental": {
- "enabled": false,
- "block_adult": false,
- "general_safe_search": false,
- "youtube_safe_search": false,
- "blocked_services": [
- "youtube"
- ],
- "schedule": {
- "tmz": "GMT",
- "mon": [
- "0s",
- "59m"
- ],
- "tue": [
- "0s",
- "59m"
- ],
- "wed": [
- "0s",
- "59m"
- ],
- "thu": [
- "0s",
- "59m"
- ],
- "fri": [
- "0s",
- "59m"
- ]
- }
- },
- "rule_lists": {
- "enabled": true,
- "ids": [
- "1"
- ]
- },
- "filtered_response_ttl": 3600,
- "custom_rules": [
- "||example.org^"
- ]
- }
- ]
-}
-```
+variable points. Supports `grpc(s)` URLs. The service must correspond to
+`./internal/backendpb/backend.proto`.
[env-profiles_url]: environment.md#PROFILES_URL
diff --git a/doc/querylog.md b/doc/querylog.md
index 385fd57..08bef90 100644
--- a/doc/querylog.md
+++ b/doc/querylog.md
@@ -249,6 +249,13 @@ rules to remember, which property means what. The properties are:
See [this IANA list][iana-rcode] for numeric values and their meanings.
+ * `ip`:
+ The IP address of the client. This field is omitted in case the IP logging
+ is turned off for the corresponding profile. The short name `ip` stands for
+ “IP”.
+
+ **Example:** `1.2.3.4`
+
See also [file `internal/querylog/entry.go`][file-entry.go] for an explanation
of the properties, their names, and mnemonics.
diff --git a/go.mod b/go.mod
index 7e13da2..44b559a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,32 +1,32 @@
module github.com/AdguardTeam/AdGuardDNS
-go 1.20
+go 1.21.5
require (
- github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0
- github.com/AdguardTeam/golibs v0.15.0
- github.com/AdguardTeam/urlfilter v0.17.0
+ github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
+ github.com/AdguardTeam/golibs v0.18.1
+ github.com/AdguardTeam/urlfilter v0.17.2
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc
github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/caarlos0/env/v7 v7.1.0
- github.com/getsentry/sentry-go v0.21.0
+ github.com/getsentry/sentry-go v0.25.0
github.com/google/renameio/v2 v2.0.0
- github.com/miekg/dns v1.1.55
- github.com/oschwald/maxminddb-golang v1.10.0
+ github.com/miekg/dns v1.1.56
+ github.com/oschwald/maxminddb-golang v1.12.0
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
- github.com/prometheus/client_golang v1.15.1
- github.com/prometheus/client_model v0.4.0
+ github.com/prometheus/client_golang v1.17.0
+ github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.44.0
- github.com/quic-go/quic-go v0.38.0
+ github.com/quic-go/quic-go v0.39.0
github.com/stretchr/testify v1.8.4
- golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
- golang.org/x/net v0.14.0
- golang.org/x/sys v0.11.0
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+ golang.org/x/net v0.17.0
+ golang.org/x/sys v0.13.0
golang.org/x/time v0.3.0
- google.golang.org/grpc v1.56.2
- google.golang.org/protobuf v1.30.0
+ google.golang.org/grpc v1.58.3
+ google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -39,21 +39,21 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
- github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
+ github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
- github.com/onsi/ginkgo/v2 v2.11.0 // indirect
- github.com/panjf2000/ants/v2 v2.7.5 // indirect
+ github.com/onsi/ginkgo/v2 v2.13.0 // indirect
+ github.com/panjf2000/ants/v2 v2.8.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
- github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
- golang.org/x/crypto v0.12.0 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/text v0.12.0 // indirect
- golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
- google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
+ github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
+ go.uber.org/mock v0.3.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/mod v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ golang.org/x/tools v0.14.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index dfda8ac..01d06ba 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
-github.com/AdguardTeam/golibs v0.15.0 h1:yOv/fdVkJIOWKr0NlUXAE9RA0DK9GKiBbiGzq47vY7o=
-github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI=
-github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow=
-github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU=
+github.com/AdguardTeam/golibs v0.18.1 h1:6u0fvrIj2qjUsRdbIGJ9AR0g5QRSWdKIo/DYl3tp5aM=
+github.com/AdguardTeam/golibs v0.18.1/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
+github.com/AdguardTeam/urlfilter v0.17.2 h1:xTntfr1UWah8m6wwoXJmFgplFk/+kL/hDu204ptrM1U=
+github.com/AdguardTeam/urlfilter v0.17.2/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
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=
@@ -28,128 +28,124 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
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.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtVQWon4=
-github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
+github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
+github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
-github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
+github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
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/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/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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
-github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
-github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
-github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
-github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
-github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
-github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU=
-github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
+github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
+github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
+github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
+github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
+github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
+github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
+github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ=
+github.com/panjf2000/ants/v2 v2.8.2/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
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=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
-github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
-github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
-github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
-github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
-github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc=
-github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg=
+github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
+github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
+github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
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.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
+github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
-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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
-golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
-golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
-golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
-golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
-golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
-golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
-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/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
-google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
-google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
+google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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=
diff --git a/go.work b/go.work
index b85227b..f0aeb95 100644
--- a/go.work
+++ b/go.work
@@ -1,4 +1,4 @@
-go 1.20
+go 1.21.5
use (
.
diff --git a/go.work.sum b/go.work.sum
index 4fd0ee0..b1d1317 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -4,7 +4,9 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
+cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
@@ -20,7 +22,9 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AdguardTeam/golibs v0.10.7/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
+github.com/AdguardTeam/golibs v0.17.2/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
github.com/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGjKN9pinUU=
+github.com/AdguardTeam/gomitmproxy v0.2.1/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
@@ -60,7 +64,11 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
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/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
+github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
+github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
+github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
@@ -78,6 +86,8 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
+github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
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=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
@@ -88,7 +98,9 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
+github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
+github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
@@ -111,7 +123,6 @@ github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
-github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
@@ -119,8 +130,8 @@ github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
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-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=
@@ -130,6 +141,9 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
@@ -137,6 +151,7 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
@@ -150,7 +165,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@@ -163,6 +177,7 @@ github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQy
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
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/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
@@ -200,11 +215,14 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4=
github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I=
github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE=
@@ -232,10 +250,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
@@ -244,6 +263,7 @@ github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
@@ -281,9 +301,12 @@ github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgS
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI=
@@ -301,11 +324,9 @@ github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+q
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
-github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
-github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
-github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
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/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -317,7 +338,6 @@ github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58
github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -326,8 +346,6 @@ github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiy
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
-github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48 h1:vabduItPAIz9px5iryD5peyx7O3Ya8TBThapgXim98o=
@@ -392,7 +410,6 @@ github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1
github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk=
github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ=
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
-github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
@@ -451,7 +468,7 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -471,6 +488,7 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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=
@@ -479,6 +497,7 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
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=
@@ -486,7 +505,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -503,6 +521,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
@@ -512,14 +531,14 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -531,7 +550,9 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
@@ -551,20 +572,20 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=
+google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
-google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
-google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -572,8 +593,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/access/access.go b/internal/access/access.go
index c68829a..e44cc80 100644
--- a/internal/access/access.go
+++ b/internal/access/access.go
@@ -11,8 +11,10 @@ import (
"github.com/AdguardTeam/urlfilter/filterlist"
)
-// unit is a convenient alias for struct{}
-type unit = struct{}
+// blocklistFilterID ia the ID for the urlfilter rule list to use in the
+// internal access engines. As there is only one rule list in the engine it
+// could simply be 0.
+const blocklistFilterID = 0
// Interface is the access manager interface.
type Interface interface {
@@ -24,24 +26,24 @@ type Interface interface {
IsBlockedIP(ip netip.Addr) (blocked bool, rule string)
}
-// type check
-var _ Interface = (*Manager)(nil)
-
-// Manager controls IP and client blocking that takes place before all
-// other processing. An Manager is safe for concurrent use.
-type Manager struct {
- blockedIPs map[netip.Addr]unit
+// Global controls IP and client blocking that takes place before all other
+// processing. Global is safe for concurrent use.
+type Global struct {
+ blockedIPs map[netip.Addr]string
blockedHostsEng *urlfilter.DNSEngine
blockedNets []netip.Prefix
}
-// New create an Manager. The parameters assumed to be valid.
-func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) {
- am = &Manager{
- blockedIPs: map[netip.Addr]unit{},
+// NewGlobal create a new Global from provided parameters.
+func NewGlobal(blockedDomains, blockedSubnets []string) (g *Global, err error) {
+ g = &Global{
+ blockedIPs: map[netip.Addr]string{},
}
- processAccessList(blockedSubnets, am.blockedIPs, &am.blockedNets)
+ err = processAccessList(blockedSubnets, g.blockedIPs, &g.blockedNets)
+ if err != nil {
+ return nil, fmt.Errorf("adding blocked hosts: %w", err)
+ }
b := &strings.Builder{}
for _, h := range blockedDomains {
@@ -50,7 +52,7 @@ func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) {
lists := []filterlist.RuleList{
&filterlist.StringRuleList{
- ID: 0,
+ ID: blocklistFilterID,
RulesText: b.String(),
IgnoreCosmetic: true,
},
@@ -61,44 +63,53 @@ func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) {
return nil, fmt.Errorf("adding blocked hosts: %w", err)
}
- am.blockedHostsEng = urlfilter.NewDNSEngine(rulesStrg)
+ g.blockedHostsEng = urlfilter.NewDNSEngine(rulesStrg)
- return am, nil
+ return g, nil
}
// processAccessList is a helper for processing a list of strings, each of them
-// assumed be a valid IP address or a valid CIDR.
-func processAccessList(strs []string, ips map[netip.Addr]unit, nets *[]netip.Prefix) {
+// could be an IP address or a CIDR.
+func processAccessList(strs []string, ips map[netip.Addr]string, nets *[]netip.Prefix) (err error) {
for _, s := range strs {
- var err error
var ip netip.Addr
var ipnet netip.Prefix
if ip, err = netip.ParseAddr(s); err == nil {
- ips[ip] = unit{}
+ ips[ip] = ip.String()
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
*nets = append(*nets, ipnet)
+ } else {
+ return fmt.Errorf("cannot parse subnet or ip address: %q", s)
}
}
+
+ return nil
}
-// IsBlockedHost returns true if host should be blocked.
-func (am *Manager) IsBlockedHost(host string, qt uint16) (blocked bool) {
- _, blocked = am.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{
+// type check
+var _ Interface = (*Global)(nil)
+
+// IsBlockedHost implements the [Interface] interface for *Global.
+func (g *Global) IsBlockedHost(host string, qt uint16) (blocked bool) {
+ res, matched := g.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{
Hostname: host,
DNSType: qt,
})
- return blocked
-}
-
-// IsBlockedIP returns the status of the IP address blocking as well as the rule
-// that blocked it.
-func (am *Manager) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) {
- if _, ok := am.blockedIPs[ip]; ok {
- return true, ip.String()
+ if matched && res.NetworkRule != nil {
+ return !res.NetworkRule.Whitelist
}
- for _, ipnet := range am.blockedNets {
+ return matched
+}
+
+// IsBlockedIP implements the [Interface] interface for *Global.
+func (g *Global) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) {
+ if ipStr, ok := g.blockedIPs[ip]; ok {
+ return true, ipStr
+ }
+
+ for _, ipnet := range g.blockedNets {
if ipnet.Contains(ip) {
return true, ipnet.String()
}
diff --git a/internal/access/access_test.go b/internal/access/access_test.go
index fe4ccc6..b4d2ee0 100644
--- a/internal/access/access_test.go
+++ b/internal/access/access_test.go
@@ -10,11 +10,13 @@ import (
"github.com/stretchr/testify/require"
)
-func TestAccessManager_IsBlockedHost(t *testing.T) {
- am, err := access.New([]string{
+func TestGlobal_IsBlockedHost(t *testing.T) {
+ global, err := access.NewGlobal([]string{
"block.test",
"UPPERCASE.test",
"||block_aaaa.test^$dnstype=AAAA",
+ "||allowlist.test^",
+ "@@||allow.allowlist.test^",
}, []string{})
require.NoError(t, err)
@@ -53,18 +55,28 @@ func TestAccessManager_IsBlockedHost(t *testing.T) {
name: "block_qt",
host: "block_aaaa.test",
qt: dns.TypeAAAA,
+ }, {
+ want: assert.True,
+ name: "allowlist_block",
+ host: "block.allowlist.test",
+ qt: dns.TypeA,
+ }, {
+ want: assert.False,
+ name: "allowlist_test",
+ host: "allow.allowlist.test",
+ qt: dns.TypeA,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- blocked := am.IsBlockedHost(tc.host, tc.qt)
+ blocked := global.IsBlockedHost(tc.host, tc.qt)
tc.want(t, blocked)
})
}
}
-func TestAccessManager_IsBlockedIP(t *testing.T) {
- am, err := access.New([]string{}, []string{
+func TestGlobal_IsBlockedIP(t *testing.T) {
+ global, err := access.NewGlobal([]string{}, []string{
"1.1.1.1",
"2.2.2.0/8",
})
@@ -99,7 +111,7 @@ func TestAccessManager_IsBlockedIP(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- blocked, rule := am.IsBlockedIP(tc.ip)
+ blocked, rule := global.IsBlockedIP(tc.ip)
tc.want(t, blocked)
assert.Equal(t, tc.wantRule, rule)
})
diff --git a/internal/access/engine.go b/internal/access/engine.go
new file mode 100644
index 0000000..e38512a
--- /dev/null
+++ b/internal/access/engine.go
@@ -0,0 +1,77 @@
+package access
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
+ "github.com/AdguardTeam/golibs/stringutil"
+ "github.com/AdguardTeam/urlfilter"
+ "github.com/AdguardTeam/urlfilter/filterlist"
+ "github.com/miekg/dns"
+)
+
+// blockedHostEngine is a lazy blocklist rules engine.
+type blockedHostEngine struct {
+ lazyEngine *urlfilter.DNSEngine
+ initOnce *sync.Once
+ rules []string
+}
+
+// newBlockedHostEngine creates a new blockedHostEngine.
+func newBlockedHostEngine(rules []string) (e *blockedHostEngine) {
+ return &blockedHostEngine{
+ rules: rules,
+ initOnce: &sync.Once{},
+ }
+}
+
+// isBlocked returns true if the req is blocked by this engine.
+func (e *blockedHostEngine) isBlocked(req *dns.Msg) (blocked bool) {
+ e.initOnce.Do(func() {
+ start := time.Now()
+
+ e.lazyEngine = e.init()
+
+ metrics.AccessProfileInitDuration.Observe(time.Since(start).Seconds())
+ })
+
+ q := req.Question[0]
+ res, matched := e.lazyEngine.MatchRequest(&urlfilter.DNSRequest{
+ Hostname: agdnet.NormalizeQueryDomain(q.Name),
+ DNSType: q.Qtype,
+ })
+
+ if matched && res.NetworkRule != nil {
+ return !res.NetworkRule.Whitelist
+ }
+
+ return matched
+}
+
+// init returns new properly initialized dns engine.
+func (e *blockedHostEngine) init() (eng *urlfilter.DNSEngine) {
+ b := &strings.Builder{}
+ for _, h := range e.rules {
+ stringutil.WriteToBuilder(b, strings.ToLower(h), "\n")
+ }
+
+ lists := []filterlist.RuleList{
+ &filterlist.StringRuleList{
+ ID: blocklistFilterID,
+ RulesText: b.String(),
+ IgnoreCosmetic: true,
+ },
+ }
+
+ rulesStrg, err := filterlist.NewRuleStorage(lists)
+ if err != nil {
+ // Should never happen, since the storage has only one list.
+ panic(fmt.Errorf("unexpected access config error: %w", err))
+ }
+
+ return urlfilter.NewDNSEngine(rulesStrg)
+}
diff --git a/internal/access/engine_internal_test.go b/internal/access/engine_internal_test.go
new file mode 100644
index 0000000..53960f3
--- /dev/null
+++ b/internal/access/engine_internal_test.go
@@ -0,0 +1,107 @@
+package access
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
+ "github.com/miekg/dns"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBlockedHostEngine_IsBlocked(t *testing.T) {
+ t.Parallel()
+
+ rules := []string{
+ "block.test",
+ "UPPERCASE.test",
+ "||block_aaaa.test^$dnstype=AAAA",
+ "||allowlist.test^",
+ "@@||allow.allowlist.test^",
+ }
+
+ engine := newBlockedHostEngine(rules)
+
+ testCases := []struct {
+ want assert.BoolAssertionFunc
+ name string
+ host string
+ qt uint16
+ }{{
+ want: assert.False,
+ name: "pass",
+ host: "pass.test",
+ qt: dns.TypeA,
+ }, {
+ want: assert.True,
+ name: "blocked_domain_A",
+ host: "block.test",
+ qt: dns.TypeA,
+ }, {
+ want: assert.True,
+ name: "blocked_domain_HTTPS",
+ host: "block.test",
+ qt: dns.TypeHTTPS,
+ }, {
+ want: assert.True,
+ name: "uppercase_domain",
+ host: "uppercase.test",
+ qt: dns.TypeHTTPS,
+ }, {
+ want: assert.False,
+ name: "pass_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeA,
+ }, {
+ want: assert.True,
+ name: "block_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeAAAA,
+ }, {
+ want: assert.True,
+ name: "allowlist_block",
+ host: "block.allowlist.test",
+ qt: dns.TypeA,
+ }, {
+ want: assert.False,
+ name: "allowlist_test",
+ host: "allow.allowlist.test",
+ qt: dns.TypeA,
+ }}
+
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ req := dnsservertest.NewReq(tc.host, tc.qt, dns.ClassINET)
+
+ blocked := engine.isBlocked(req)
+ tc.want(t, blocked)
+ })
+ }
+}
+
+func TestBlockedHostEngine_IsBlocked_concurrent(t *testing.T) {
+ const routinesLimit = 50
+
+ rules := []string{"||block.test^"}
+ engine := newBlockedHostEngine(rules)
+
+ wg := &sync.WaitGroup{}
+ for i := 0; i < routinesLimit; i++ {
+ wg.Add(1)
+
+ host := fmt.Sprintf("%d.%s", i, "block.test")
+
+ go func() {
+ defer wg.Done()
+
+ req := dnsservertest.NewReq(host, dns.TypeA, dns.ClassINET)
+ assert.True(t, engine.isBlocked(req))
+ }()
+ }
+
+ wg.Wait()
+}
diff --git a/internal/access/profile.go b/internal/access/profile.go
new file mode 100644
index 0000000..767d7c6
--- /dev/null
+++ b/internal/access/profile.go
@@ -0,0 +1,137 @@
+package access
+
+import (
+ "net/netip"
+ "slices"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/miekg/dns"
+)
+
+// Profile is the profile access manager interface.
+type Profile interface {
+ // Config returns profile access configuration.
+ Config() (conf *ProfileConfig)
+
+ // IsBlocked returns true if the req should be blocked. req must not be
+ // nil, and req.Question must have one item.
+ IsBlocked(req *dns.Msg, rAddr netip.AddrPort, l *geoip.Location) (blocked bool)
+}
+
+// EmptyProfile is an empty profile implementation that does nothing.
+type EmptyProfile struct{}
+
+// type check
+var _ Profile = EmptyProfile{}
+
+// Config implements the [Profile] interface for EmptyProfile. It always
+// returns nil.
+func (EmptyProfile) Config() (conf *ProfileConfig) { return nil }
+
+// IsBlocked implements the [Profile] interface for EmptyProfile. It always
+// returns false.
+func (EmptyProfile) IsBlocked(_ *dns.Msg, _ netip.AddrPort, _ *geoip.Location) (blocked bool) {
+ return false
+}
+
+// ProfileConfig is a profile specific access configuration.
+//
+// NOTE: Do not change fields of this structure without incrementing
+// [internal/profiledb/internal.FileCacheVersion].
+type ProfileConfig struct {
+ // AllowedNets is slice of CIDRs to be allowed.
+ AllowedNets []netip.Prefix
+
+ // BlockedNets is slice of CIDRs to be blocked.
+ BlockedNets []netip.Prefix
+
+ // AllowedNets is slice of location ASNs to be allowed.
+ AllowedASN []geoip.ASN
+
+ // BlockedASN is slice of location ASNs to be blocked.
+ BlockedASN []geoip.ASN
+
+ // BlocklistDomainRules is slice of rules to match requests.
+ BlocklistDomainRules []string
+}
+
+// DefaultProfile controls profile specific IP and client blocking that take
+// place before all other processing. DefaultProfile is safe for concurrent
+// use.
+type DefaultProfile struct {
+ blockedHostsEng *blockedHostEngine
+
+ allowedNets []netip.Prefix
+ blockedNets []netip.Prefix
+
+ // TODO(d.kolyshev): Change to map[geoip.ASN]unit to improve performance.
+ allowedASN []geoip.ASN
+ blockedASN []geoip.ASN
+
+ blocklistDomainRules []string
+}
+
+// NewDefaultProfile creates a new *DefaultProfile. conf is assumed to be
+// valid.
+func NewDefaultProfile(conf *ProfileConfig) (p *DefaultProfile) {
+ return &DefaultProfile{
+ allowedNets: conf.AllowedNets,
+ blockedNets: conf.BlockedNets,
+ allowedASN: conf.AllowedASN,
+ blockedASN: conf.BlockedASN,
+ blocklistDomainRules: conf.BlocklistDomainRules,
+ blockedHostsEng: newBlockedHostEngine(conf.BlocklistDomainRules),
+ }
+}
+
+// type check
+var _ Profile = (*DefaultProfile)(nil)
+
+// Config implements the [Profile] interface for *DefaultProfile.
+func (p *DefaultProfile) Config() (conf *ProfileConfig) {
+ return &ProfileConfig{
+ AllowedNets: slices.Clone(p.allowedNets),
+ BlockedNets: slices.Clone(p.blockedNets),
+ AllowedASN: slices.Clone(p.allowedASN),
+ BlockedASN: slices.Clone(p.blockedASN),
+ BlocklistDomainRules: slices.Clone(p.blocklistDomainRules),
+ }
+}
+
+// IsBlocked implements the [Profile] interface for *DefaultProfile.
+func (p *DefaultProfile) IsBlocked(req *dns.Msg, rAddr netip.AddrPort, l *geoip.Location) (blocked bool) {
+ ip := rAddr.Addr()
+
+ return p.isBlockedByNets(ip, l) || p.isBlockedByHostsEng(req)
+}
+
+// isBlockedByNets returns true if ip or l is blocked by current profile.
+func (p *DefaultProfile) isBlockedByNets(ip netip.Addr, l *geoip.Location) (blocked bool) {
+ if matchASNs(p.allowedASN, l) || matchNets(p.allowedNets, ip) {
+ return false
+ }
+
+ return matchASNs(p.blockedASN, l) || matchNets(p.blockedNets, ip)
+}
+
+// matchNets returns true if ip is contained by any of the subnets.
+func matchNets(nets []netip.Prefix, ip netip.Addr) (ok bool) {
+ for _, n := range nets {
+ if n.Contains(ip) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// matchASNs returns true if l is not nil and its asn is included in asns.
+func matchASNs(asns []geoip.ASN, l *geoip.Location) (ok bool) {
+ return l != nil && slices.Contains(asns, l.ASN)
+}
+
+// isBlockedByHostsEng returns true if the req is blocked by
+// BlocklistDomainRules.
+func (p *DefaultProfile) isBlockedByHostsEng(req *dns.Msg) (blocked bool) {
+ return p.blockedHostsEng.isBlocked(req)
+}
diff --git a/internal/access/profile_test.go b/internal/access/profile_test.go
new file mode 100644
index 0000000..0d004aa
--- /dev/null
+++ b/internal/access/profile_test.go
@@ -0,0 +1,265 @@
+package access_test
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/miekg/dns"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDefaultProfile_Config(t *testing.T) {
+ conf := &access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{1, 2},
+ BlocklistDomainRules: []string{"block.test"},
+ }
+
+ a := access.NewDefaultProfile(conf)
+ got := a.Config()
+ assert.Equal(t, conf, got)
+}
+
+func TestDefaultProfile_IsBlocked(t *testing.T) {
+ passAddrPort := netip.MustParseAddrPort("3.3.3.3:3333")
+
+ conf := &access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.1/32")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{1, 2},
+ BlocklistDomainRules: []string{
+ "block.test",
+ "UPPERCASE.test",
+ "||block_aaaa.test^$dnstype=AAAA",
+ "||allowlist.test^",
+ "@@||allow.allowlist.test^",
+ },
+ }
+
+ a := access.NewDefaultProfile(conf)
+
+ testCases := []struct {
+ loc *geoip.Location
+ want assert.BoolAssertionFunc
+ rAddr netip.AddrPort
+ name string
+ host string
+ qt uint16
+ }{{
+ want: assert.False,
+ name: "pass",
+ host: "pass.test",
+ qt: dns.TypeA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "blocked_domain_A",
+ host: "block.test",
+ qt: dns.TypeA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "blocked_domain_HTTPS",
+ host: "block.test",
+ qt: dns.TypeHTTPS,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "uppercase_domain",
+ host: "uppercase.test",
+ qt: dns.TypeHTTPS,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.False,
+ name: "pass_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "block_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeAAAA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "allowlist_block",
+ host: "block.allowlist.test",
+ qt: dns.TypeA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.False,
+ name: "allowlist_test",
+ host: "allow.allowlist.test",
+ qt: dns.TypeA,
+ rAddr: passAddrPort,
+ loc: nil,
+ }, {
+ want: assert.False,
+ name: "pass_ip",
+ rAddr: netip.MustParseAddrPort("1.1.1.1:57"),
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "block_subnet",
+ rAddr: netip.MustParseAddrPort("1.1.1.2:57"),
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ want: assert.False,
+ name: "pass_subnet",
+ rAddr: netip.MustParseAddrPort("1.2.2.2:57"),
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ want: assert.True,
+ name: "block_host_pass_asn",
+ rAddr: passAddrPort,
+ host: "block.test",
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 1},
+ }, {
+ want: assert.False,
+ name: "pass_asn",
+ rAddr: passAddrPort,
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 1},
+ }, {
+ want: assert.True,
+ name: "block_asn",
+ rAddr: passAddrPort,
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 2},
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ req := dnsservertest.NewReq(tc.host, tc.qt, dns.ClassINET)
+
+ blocked := a.IsBlocked(req, tc.rAddr, tc.loc)
+ tc.want(t, blocked)
+ })
+ }
+}
+
+func TestDefaultProfile_IsBlocked_prefixAllowlist(t *testing.T) {
+ conf := &access.ProfileConfig{
+ AllowedNets: []netip.Prefix{
+ netip.MustParsePrefix("2.2.2.0/24"),
+ netip.MustParsePrefix("3.3.0.0/16"),
+ },
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")},
+ AllowedASN: nil,
+ BlockedASN: nil,
+ BlocklistDomainRules: nil,
+ }
+
+ a := access.NewDefaultProfile(conf)
+
+ testCases := []struct {
+ want assert.BoolAssertionFunc
+ rAddr netip.AddrPort
+ name string
+ }{{
+ want: assert.True,
+ name: "block_before",
+ rAddr: netip.MustParseAddrPort("1.1.1.1:2222"),
+ }, {
+ want: assert.False,
+ name: "allow_first",
+ rAddr: netip.MustParseAddrPort("2.2.2.1:2222"),
+ }, {
+ want: assert.False,
+ name: "allow_second",
+ rAddr: netip.MustParseAddrPort("3.3.1.1:2222"),
+ }, {
+ want: assert.True,
+ name: "block_second",
+ rAddr: netip.MustParseAddrPort("3.4.1.1:2222"),
+ }, {
+ want: assert.True,
+ name: "block_after",
+ rAddr: netip.MustParseAddrPort("4.4.1.1:2222"),
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ req := dnsservertest.NewReq("pass.test", dns.TypeA, dns.ClassINET)
+
+ blocked := a.IsBlocked(req, tc.rAddr, nil)
+ tc.want(t, blocked)
+ })
+ }
+}
+
+func BenchmarkDefaultProfile_IsBlocked(b *testing.B) {
+ passAddrPort := netip.MustParseAddrPort("3.3.3.3:3333")
+
+ conf := &access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.1/32")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{1, 2},
+ BlocklistDomainRules: []string{
+ "block.test",
+ "UPPERCASE.test",
+ "||block_aaaa.test^$dnstype=AAAA",
+ "||allowlist.test^",
+ "@@||allow.allowlist.test^",
+ },
+ }
+
+ a := access.NewDefaultProfile(conf)
+
+ passReq := dnsservertest.NewReq("pass.test", dns.TypeA, dns.ClassINET)
+
+ b.Run("pass", func(b *testing.B) {
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = a.IsBlocked(passReq, passAddrPort, nil)
+ }
+ })
+
+ blockReq := dnsservertest.NewReq("block.test", dns.TypeA, dns.ClassINET)
+
+ b.Run("block", func(b *testing.B) {
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ _ = a.IsBlocked(blockReq, passAddrPort, nil)
+ }
+ })
+
+ // Most recent results, on a MBP 14 with Apple M1 Pro chip:
+ //
+ // goos: darwin
+ // goarch: arm64
+ // pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
+ // BenchmarkDefaultProfile_IsBlocked
+ // BenchmarkDefaultProfile_IsBlocked/pass
+ // BenchmarkDefaultProfile_IsBlocked/pass-8 2935430 357.7 ns/op 384 B/op 4 allocs/op
+ // BenchmarkDefaultProfile_IsBlocked/block
+ // BenchmarkDefaultProfile_IsBlocked/block-8 2706435 443.7 ns/op 416 B/op 6 allocs/op
+}
diff --git a/internal/agd/agd.go b/internal/agd/agd.go
index fb618c3..974cdd4 100644
--- a/internal/agd/agd.go
+++ b/internal/agd/agd.go
@@ -7,9 +7,6 @@ import (
// Common Constants, Types, And Utilities
-// unit is a convenient alias for struct{}.
-type unit = struct{}
-
// 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.
diff --git a/internal/agd/agd_test.go b/internal/agd/agd_test.go
index b1395a5..9ba93db 100644
--- a/internal/agd/agd_test.go
+++ b/internal/agd/agd_test.go
@@ -2,16 +2,10 @@ package agd_test
import (
"testing"
- "time"
"github.com/AdguardTeam/golibs/testutil"
)
-// Common Constants And Utilities
-
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
-
-// testTimeout is the timeout for common test operations.
-const testTimeout = 1 * time.Second
diff --git a/internal/agd/context.go b/internal/agd/context.go
index 7ce6fb5..43dab2f 100644
--- a/internal/agd/context.go
+++ b/internal/agd/context.go
@@ -6,6 +6,7 @@ import (
"net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors"
)
@@ -73,7 +74,7 @@ type RequestInfo struct {
Profile *Profile
// Location is the GeoIP location data about the remote IP address, if any.
- Location *Location
+ Location *geoip.Location
// ECS contains the EDNS Client Subnet option information of the request, if
// any.
@@ -107,9 +108,10 @@ type RequestInfo struct {
QType dnsmsg.RRType
// QClass is the class of question for this request.
- //
- // TODO(a.garipov): Use more.
QClass dnsmsg.Class
+
+ // Proto is the protocol by which this request is made.
+ Proto Protocol
}
// ECS is the content of the EDNS Client Subnet option of a DNS message.
@@ -118,7 +120,7 @@ type RequestInfo struct {
type ECS struct {
// Location is the GeoIP location data about the IP address from the
// request's ECS data, if any.
- Location *Location
+ Location *geoip.Location
// Subnet is the source subnet.
Subnet netip.Prefix
diff --git a/internal/agd/error.go b/internal/agd/error.go
index 6057f70..a5b5eb5 100644
--- a/internal/agd/error.go
+++ b/internal/agd/error.go
@@ -24,27 +24,3 @@ func (err *ArgumentError) Error() (msg string) {
return fmt.Sprintf("argument %s is invalid: %s", err.Name, err.Message)
}
-
-// NotACountryError is returned from NewCountry when the string doesn't represent
-// a valid country.
-type NotACountryError struct {
- // Code is the code presented to NewCountry.
- Code string
-}
-
-// Error implements the error interface for *NotACountryError.
-func (err *NotACountryError) Error() (msg string) {
- return fmt.Sprintf("%q is not a valid iso 3166-1 alpha-2 code", err.Code)
-}
-
-// NotAContinentError is returned from NewContinent when the string doesn't
-// represent a valid continent.
-type NotAContinentError struct {
- // Code is the code presented to NewContinent.
- Code string
-}
-
-// Error implements the error interface for *NotAContinentError.
-func (err *NotAContinentError) Error() (msg string) {
- return fmt.Sprintf("%q is not a valid continent code", err.Code)
-}
diff --git a/internal/agd/errorcollector.go b/internal/agd/errorcollector.go
deleted file mode 100644
index 001098a..0000000
--- a/internal/agd/errorcollector.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package agd
-
-import (
- "context"
- "fmt"
-
- "github.com/AdguardTeam/golibs/log"
-)
-
-// Error Collector
-
-// ErrorCollector collects information about errors, possibly sending them to
-// a remote location.
-type ErrorCollector 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 the error collector.
-func Collectf(ctx context.Context, errColl ErrorCollector, format string, args ...any) {
- err := fmt.Errorf(format, args...)
- log.Error("%s", err)
- errColl.Collect(ctx, err)
-}
diff --git a/internal/agd/profile.go b/internal/agd/profile.go
index 96d5ad8..382c6aa 100644
--- a/internal/agd/profile.go
+++ b/internal/agd/profile.go
@@ -5,6 +5,7 @@ import (
"math"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors"
@@ -37,11 +38,17 @@ type Profile struct {
// [internal/profiledb/internal.FileCacheVersion].
SafeBrowsing *SafeBrowsingSettings
+ // Access is the access manager for this profile. Access is never nil.
+ //
+ // NOTE: Do not change fields of this structure without incrementing
+ // [internal/profiledb/internal.FileCacheVersion].
+ Access access.Profile
+
// BlockingMode defines the way blocked responses are constructed.
//
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
- BlockingMode dnsmsg.BlockingModeCodec
+ BlockingMode dnsmsg.BlockingMode
// ID is the unique ID of this profile.
//
@@ -126,6 +133,12 @@ type Profile struct {
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
BlockFirefoxCanary bool
+
+ // IPLogEnabled shows if client IP addresses are logged.
+ //
+ // NOTE: Do not change fields of this structure without incrementing
+ // [internal/profiledb/internal.FileCacheVersion].
+ IPLogEnabled bool
}
// ProfileID is the ID of a profile. It is an opaque string.
diff --git a/internal/agd/server.go b/internal/agd/server.go
index c010dab..df7efae 100644
--- a/internal/agd/server.go
+++ b/internal/agd/server.go
@@ -2,9 +2,13 @@ package agd
import (
"crypto/tls"
+ "fmt"
"net/netip"
+ "time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns"
@@ -84,12 +88,34 @@ type Server struct {
// TLS is the TLS configuration for this server, if any.
TLS *tls.Config
+ // QUICConf is the QUIC configuration for this server.
+ QUICConf *QUICConfig
+
+ // TCPConf is the TCP configuration for this server.
+ TCPConf *TCPConfig
+
+ // UDPConf is the UDP configuration for this server.
+ UDPConf *UDPConfig
+
// Name is the unique name of the server. Not to be confused with a TLS
// Server Name.
Name ServerName
- // BindData are the socket binding data for this server.
- BindData []*ServerBindData
+ // bindData are the socket binding data for this server.
+ bindData []*ServerBindData
+
+ // ReadTimeout defines the timeout for any read from a UDP connection or the
+ // first read from a TCP/TLS connection. It currently doesn't affect
+ // DNSCrypt, QUIC, or HTTPS.
+ //
+ // TODO(a.garipov): Make it work for DNSCrypt, QUIC, and HTTPS.
+ ReadTimeout time.Duration `yaml:"read_timeout"`
+
+ // WriteTimeout defines the timeout for writing to a UDP or TCP/TLS
+ // connection. It currently doesn't affect DNSCrypt, QUIC, or HTTPS.
+ //
+ // TODO(a.garipov): Make it work for DNSCrypt, QUIC, and HTTPS.
+ WriteTimeout time.Duration `yaml:"write_timeout"`
// Protocol is the protocol of the server.
Protocol Protocol
@@ -99,8 +125,75 @@ type Server struct {
LinkedIPEnabled bool
}
+// BindData returns the bind data of this server. The elements of the slice
+// must not be mutated.
+func (s *Server) BindData() (data []*ServerBindData) {
+ return s.bindData
+}
+
+// SetBindData sets the bind data of this server. data must have at least one
+// element and all of its elements must be of the same underlying type. The
+// elements of data must not be mutated after calling SetBindData.
+func (s *Server) SetBindData(data []*ServerBindData) {
+ switch len(data) {
+ case 0:
+ panic(errors.Error("empty bind data"))
+ case 1:
+ s.bindData = data
+ default:
+ firstIsAddrPort := data[0].PrefixAddr == nil
+ for i, bd := range data[1:] {
+ if (bd.PrefixAddr == nil) != firstIsAddrPort {
+ panic(fmt.Errorf("at index %d: inconsistent type of bind data", i+1))
+ }
+ }
+
+ s.bindData = data
+ }
+}
+
+// HasAddr returns true if addr is within the server's bind data. HasAddr does
+// not check prefix addresses unless they are single-IP subnets.
+func (s *Server) HasAddr(addr netip.AddrPort) (ok bool) {
+ for _, bd := range s.bindData {
+ prefAddr := bd.PrefixAddr
+ if prefAddr == nil {
+ if bd.AddrPort == addr {
+ return true
+ }
+
+ continue
+ }
+
+ p := prefAddr.Prefix
+ if p.IsSingleIP() && p.Addr() == addr.Addr() && prefAddr.Port == addr.Port() {
+ return true
+ }
+ }
+
+ return false
+}
+
+// HasIPv6 returns true if the bind data of this server contains an IPv6
+// address. For a server with no bind data, HasIPv6 returns false.
+func (s *Server) HasIPv6() (ok bool) {
+ for _, bd := range s.bindData {
+ if bd.AddrPort.Addr().Is6() {
+ return true
+ }
+ }
+
+ return false
+}
+
+// BindsToInterfaces returns true if server binds to interfaces. For a server
+// with no bind data, BindsToInterfaces returns false.
+func (s *Server) BindsToInterfaces() (ok bool) {
+ return len(s.bindData) > 0 && s.bindData[0].PrefixAddr != nil
+}
+
// ServerBindData are the socket binding data for a server. Either AddrPort or
-// ListenConfig with Address must be set.
+// ListenConfig with PrefixAddr must be set.
//
// TODO(a.garipov): Consider turning this into a sum type.
//
@@ -108,7 +201,7 @@ type Server struct {
// like BindConfig.
type ServerBindData struct {
ListenConfig netext.ListenConfig
- Address string
+ PrefixAddr *agdnet.PrefixNetAddr
AddrPort netip.AddrPort
}
@@ -124,3 +217,35 @@ type DNSCryptConfig struct {
// ProviderName is the name of the DNSCrypt provider.
ProviderName string
}
+
+// TCPConfig is the TCP configuration of a DNS server.
+type TCPConfig struct {
+ // IdleTimeout defines the timeout for consecutive reads from a TCP/TLS
+ // connection.
+ IdleTimeout time.Duration
+
+ // MaxPipelineCount is the maximum number of simultaneously processing TCP
+ // messages per one connection. If MaxPipelineEnabled is true, it must be
+ // greater than zero.
+ MaxPipelineCount uint
+
+ // MaxPipelineEnabled, if true, enables TCP pipeline limiting.
+ MaxPipelineEnabled bool
+}
+
+// UDPConfig is the UDP configuration of a DNS server.
+type UDPConfig struct {
+ // MaxRespSize is the maximum size in bytes of DNS response over UDP
+ // protocol.
+ MaxRespSize uint16
+}
+
+// QUICConfig is the QUIC configuration of a DNS server.
+type QUICConfig struct {
+ // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer
+ // is allowed to open.
+ MaxStreamsPerPeer int
+
+ // QUICLimitsEnabled, if true, enables QUIC limiting.
+ QUICLimitsEnabled bool
+}
diff --git a/internal/agd/server_test.go b/internal/agd/server_test.go
new file mode 100644
index 0000000..534f091
--- /dev/null
+++ b/internal/agd/server_test.go
@@ -0,0 +1,152 @@
+package agd_test
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// Common variables for tests.
+var (
+ bindDataAddrPortV4 = &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:53"),
+ }
+
+ bindDataAddrPortV6 = &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("[1234::cdef]:53"),
+ }
+
+ bindDataIface = &agd.ServerBindData{
+ ListenConfig: &agdtest.ListenConfig{},
+ PrefixAddr: &agdnet.PrefixNetAddr{
+ Prefix: netip.MustParsePrefix("1.2.3.0/24"),
+ Net: "",
+ Port: 53,
+ },
+ }
+
+ bindDataIfaceSingleIP = &agd.ServerBindData{
+ ListenConfig: &agdtest.ListenConfig{},
+ PrefixAddr: &agdnet.PrefixNetAddr{
+ Prefix: netip.MustParsePrefix("1.2.3.4/32"),
+ Net: "",
+ Port: 53,
+ },
+ }
+)
+
+func TestServer_SetBindData(t *testing.T) {
+ testCases := []struct {
+ name string
+ wantPanicMsg string
+ in []*agd.ServerBindData
+ }{{
+ name: "nil",
+ wantPanicMsg: "empty bind data",
+ in: nil,
+ }, {
+ name: "empty",
+ wantPanicMsg: "empty bind data",
+ in: []*agd.ServerBindData{},
+ }, {
+ name: "one",
+ wantPanicMsg: "",
+ in: []*agd.ServerBindData{bindDataAddrPortV4},
+ }, {
+ name: "two_same_type",
+ wantPanicMsg: "",
+ in: []*agd.ServerBindData{
+ bindDataAddrPortV4,
+ bindDataAddrPortV6,
+ },
+ }, {
+ name: "two_diff_type",
+ wantPanicMsg: "at index 1: inconsistent type of bind data",
+ in: []*agd.ServerBindData{
+ bindDataAddrPortV4,
+ bindDataIface,
+ },
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ f := func() {
+ s := &agd.Server{}
+ s.SetBindData(tc.in)
+
+ assert.NotEmpty(t, s.BindData())
+ }
+
+ if tc.wantPanicMsg == "" {
+ assert.NotPanics(t, f)
+ } else {
+ assert.PanicsWithError(t, tc.wantPanicMsg, f)
+ }
+ })
+ }
+}
+
+func TestServer_BindsToInterfaces(t *testing.T) {
+ s := &agd.Server{}
+ assert.False(t, s.BindsToInterfaces())
+
+ s.SetBindData([]*agd.ServerBindData{bindDataAddrPortV4})
+ assert.False(t, s.BindsToInterfaces())
+
+ s.SetBindData([]*agd.ServerBindData{bindDataIface})
+ assert.True(t, s.BindsToInterfaces())
+
+ s.SetBindData([]*agd.ServerBindData{bindDataIfaceSingleIP})
+ assert.True(t, s.BindsToInterfaces())
+}
+
+func TestServer_HasAddr(t *testing.T) {
+ testCases := []struct {
+ bindData *agd.ServerBindData
+ want assert.BoolAssertionFunc
+ name string
+ }{{
+ bindData: bindDataAddrPortV4,
+ want: assert.True,
+ name: "addr_has",
+ }, {
+ bindData: bindDataAddrPortV6,
+ want: assert.False,
+ name: "addr_missing",
+ }, {
+ bindData: bindDataIfaceSingleIP,
+ want: assert.True,
+ name: "prefix_has",
+ }, {
+ bindData: bindDataIface,
+ want: assert.False,
+ name: "prefix_missing",
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ s := &agd.Server{}
+ require.NotPanics(t, func() {
+ s.SetBindData([]*agd.ServerBindData{tc.bindData})
+ })
+
+ tc.want(t, s.HasAddr(bindDataAddrPortV4.AddrPort))
+ })
+ }
+}
+
+func TestServer_HasIPv6(t *testing.T) {
+ s := &agd.Server{}
+ assert.False(t, s.HasIPv6())
+
+ s.SetBindData([]*agd.ServerBindData{bindDataAddrPortV4})
+ assert.False(t, s.HasIPv6())
+
+ s.SetBindData([]*agd.ServerBindData{bindDataAddrPortV6})
+ assert.True(t, s.HasIPv6())
+}
diff --git a/internal/agd/service.go b/internal/agd/service.go
deleted file mode 100644
index 47bc23c..0000000
--- a/internal/agd/service.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package agd
-
-import "context"
-
-// Service is the interface for API servers.
-type Service interface {
- // Start starts the service. It must not block.
- Start() (err error)
-
- // Shutdown gracefully stops the service. ctx is used to determine
- // a timeout before trying to stop the service less gracefully.
- Shutdown(ctx context.Context) (err error)
-}
-
-// type check
-var _ Service = EmptyService{}
-
-// EmptyService is an agd.Service that does nothing.
-type EmptyService struct{}
-
-// Start implements the Service interface for EmptyService.
-func (EmptyService) Start() (err error) { return nil }
-
-// Shutdown implements the Service interface for EmptyService.
-func (EmptyService) Shutdown(_ context.Context) (err error) { return nil }
diff --git a/internal/agdhttp/url.go b/internal/agdhttp/url.go
index 48a2f91..c144657 100644
--- a/internal/agdhttp/url.go
+++ b/internal/agdhttp/url.go
@@ -36,15 +36,3 @@ func ParseHTTPURL(s string) (u *url.URL, err error) {
return u, nil
}
}
-
-// URL is a wrapper around *url.URL that can unmarshal itself from JSON or YAML.
-//
-// TODO(a.garipov): Move to netutil if we need it somewhere else.
-type URL struct {
- url.URL
-}
-
-// UnmarshalText implements the encoding.TextUnmarshaler interface for *URL.
-func (u *URL) UnmarshalText(b []byte) (err error) {
- return u.UnmarshalBinary(b)
-}
diff --git a/internal/agdhttp/url_test.go b/internal/agdhttp/url_test.go
index 55e3800..cbe8a92 100644
--- a/internal/agdhttp/url_test.go
+++ b/internal/agdhttp/url_test.go
@@ -61,13 +61,6 @@ func TestParseHTTPURL(t *testing.T) {
}
}
-func TestURL_UnmarshalText(t *testing.T) {
- u := &agdhttp.URL{
- URL: *testURL(),
- }
- testutil.AssertUnmarshalText(t, u.String(), u)
-}
-
func testURL() (u *url.URL) {
return &url.URL{
Scheme: "http",
diff --git a/internal/agdio/agdio.go b/internal/agdio/agdio.go
deleted file mode 100644
index bfd3300..0000000
--- a/internal/agdio/agdio.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Package agdio contains extensions and utilities for package io from the
-// standard library.
-//
-// TODO(a.garipov): Move to module golibs.
-package agdio
-
-import (
- "fmt"
- "io"
-
- "github.com/AdguardTeam/golibs/mathutil"
-)
-
-// LimitError is returned when the Limit is reached.
-type LimitError struct {
- // Limit is the limit that triggered the error.
- Limit int64
-}
-
-// Error implements the error interface for *LimitError.
-func (err *LimitError) Error() string {
- return fmt.Sprintf("cannot read more than %d bytes", err.Limit)
-}
-
-// limitedReader is a wrapper for io.Reader that has a reading limit.
-type limitedReader struct {
- r io.Reader
- limit int64
- n int64
-}
-
-// Read implements the io.Reader interface for *limitedReader.
-func (lr *limitedReader) Read(p []byte) (n int, err error) {
- if lr.n == 0 {
- return 0, &LimitError{
- Limit: lr.limit,
- }
- }
-
- l := mathutil.Min(int64(len(p)), lr.n)
- p = p[:l]
-
- n, err = lr.r.Read(p)
- lr.n -= int64(n)
-
- return n, err
-}
-
-// LimitReader returns an io.Reader that reads up to n bytes. Once that limit
-// is reached, ErrLimit is returned from limited's Read method. Method
-// Read of limited is not safe for concurrent use. n must be non-negative.
-func LimitReader(r io.Reader, n int64) (limited io.Reader) {
- return &limitedReader{
- r: r,
- limit: n,
- n: n,
- }
-}
diff --git a/internal/agdio/agdio_test.go b/internal/agdio/agdio_test.go
deleted file mode 100644
index 7af2629..0000000
--- a/internal/agdio/agdio_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package agdio_test
-
-import (
- "io"
- "strings"
- "testing"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agdio"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestLimitedReader_Read(t *testing.T) {
- testCases := []struct {
- err error
- name string
- rStr string
- limit int64
- want int
- }{{
- err: nil,
- name: "perfectly_match",
- rStr: "abc",
- limit: 3,
- want: 3,
- }, {
- err: io.EOF,
- name: "eof",
- rStr: "",
- limit: 3,
- want: 0,
- }, {
- err: &agdio.LimitError{
- Limit: 0,
- },
- name: "limit_reached",
- rStr: "abc",
- limit: 0,
- want: 0,
- }, {
- err: nil,
- name: "truncated",
- rStr: "abc",
- limit: 2,
- want: 2,
- }}
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- readCloser := io.NopCloser(strings.NewReader(tc.rStr))
- buf := make([]byte, tc.limit+1)
-
- lreader := agdio.LimitReader(readCloser, tc.limit)
- n, err := lreader.Read(buf)
- require.Equal(t, tc.err, err)
-
- assert.Equal(t, tc.want, n)
- })
- }
-}
-
-func TestLimitError_Error(t *testing.T) {
- err := &agdio.LimitError{
- Limit: 0,
- }
-
- const want = "cannot read more than 0 bytes"
- assert.Equal(t, want, err.Error())
-}
diff --git a/internal/agdnet/agdnet.go b/internal/agdnet/agdnet.go
index 6430ae0..c0e258c 100644
--- a/internal/agdnet/agdnet.go
+++ b/internal/agdnet/agdnet.go
@@ -81,3 +81,16 @@ func ParseSubnets(strs ...string) (subnets []netip.Prefix, err error) {
func NormalizeDomain(fqdn string) (host string) {
return strings.ToLower(strings.TrimSuffix(fqdn, "."))
}
+
+// NormalizeQueryDomain returns a lowercased version of the host without the
+// final dot, unless the host is ".", in which case it returns the unchanged
+// host. That is the special case to allow matching queries like:
+//
+// dig IN NS '.'
+func NormalizeQueryDomain(host string) (norm string) {
+ if host == "." {
+ return host
+ }
+
+ return NormalizeDomain(host)
+}
diff --git a/internal/agdnet/agdnet_test.go b/internal/agdnet/agdnet_test.go
new file mode 100644
index 0000000..49595b5
--- /dev/null
+++ b/internal/agdnet/agdnet_test.go
@@ -0,0 +1,9 @@
+package agdnet_test
+
+import "net/netip"
+
+// Common subnets for tests.
+var (
+ testSubnetIPv4 = netip.MustParsePrefix("1.2.3.0/24")
+ testSubnetIPv6 = netip.MustParsePrefix("1234:5678::/64")
+)
diff --git a/internal/agdnet/prefixaddr.go b/internal/agdnet/prefixaddr.go
index a7c0085..79c2bb0 100644
--- a/internal/agdnet/prefixaddr.go
+++ b/internal/agdnet/prefixaddr.go
@@ -2,18 +2,34 @@ package agdnet
import (
"fmt"
+ "net"
"net/netip"
)
-// FormatPrefixAddr returns either a simple IP:port address or one with the
-// prefix length appended after a slash, depending on whether or not subnet is a
-// single-address subnet. This is done to make using the IP:port part easier to
-// split off using functions like [strings.Cut].
-func FormatPrefixAddr(subnet netip.Prefix, port uint16) (s string) {
- addrPort := netip.AddrPortFrom(subnet.Addr(), port)
- if subnet.IsSingleIP() {
+// PrefixNetAddr is a wrapper around netip.Prefix that makes it a [net.Addr].
+type PrefixNetAddr struct {
+ Prefix netip.Prefix
+ Net string
+ Port uint16
+}
+
+// type check
+var _ net.Addr = (*PrefixNetAddr)(nil)
+
+// String implements the [net.Addr] interface for *PrefixNetAddr. It returns
+// either a simple IP:port address or one with the prefix length appended after
+// a slash, depending on whether or not subnet is a single-address subnet. This
+// is done to make using the IP:port part easier to split off using functions
+// like [strings.Cut].
+func (addr *PrefixNetAddr) String() (n string) {
+ p := addr.Prefix
+ addrPort := netip.AddrPortFrom(p.Addr(), addr.Port)
+ if p.IsSingleIP() {
return addrPort.String()
}
- return fmt.Sprintf("%s/%d", addrPort, subnet.Bits())
+ return fmt.Sprintf("%s/%d", addrPort, p.Bits())
}
+
+// Network implements the [net.Addr] interface for *PrefixNetAddr.
+func (addr *PrefixNetAddr) Network() (n string) { return addr.Net }
diff --git a/internal/agdnet/prefixaddr_example_test.go b/internal/agdnet/prefixaddr_example_test.go
index 818667a..f13d1d0 100644
--- a/internal/agdnet/prefixaddr_example_test.go
+++ b/internal/agdnet/prefixaddr_example_test.go
@@ -7,9 +7,17 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
)
-func ExampeFormatPrefixAddr() {
- fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.4/32"), 5678))
- fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.0/24"), 5678))
+func ExamplePrefixNetAddr_string() {
+ fmt.Println(&agdnet.PrefixNetAddr{
+ Prefix: netip.MustParsePrefix("1.2.3.4/32"),
+ Net: "",
+ Port: 5678,
+ })
+ fmt.Println(&agdnet.PrefixNetAddr{
+ Prefix: netip.MustParsePrefix("1.2.3.0/24"),
+ Net: "",
+ Port: 5678,
+ })
// Output:
// 1.2.3.4:5678
diff --git a/internal/bindtodevice/prefixaddr_linux_internal_test.go b/internal/agdnet/prefixaddr_test.go
similarity index 68%
rename from internal/bindtodevice/prefixaddr_linux_internal_test.go
rename to internal/agdnet/prefixaddr_test.go
index 3d8a28c..20f9167 100644
--- a/internal/bindtodevice/prefixaddr_linux_internal_test.go
+++ b/internal/agdnet/prefixaddr_test.go
@@ -1,12 +1,11 @@
-//go:build linux
-
-package bindtodevice
+package agdnet_test
import (
"fmt"
"net/netip"
"testing"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/stretchr/testify/assert"
)
@@ -19,14 +18,14 @@ func TestPrefixAddr(t *testing.T) {
fullPrefix := netip.MustParsePrefix("1.2.3.4/32")
testCases := []struct {
- in *prefixNetAddr
+ in *agdnet.PrefixNetAddr
want string
name string
}{{
- in: &prefixNetAddr{
- prefix: testSubnetIPv4,
- network: network,
- port: port,
+ in: &agdnet.PrefixNetAddr{
+ Prefix: testSubnetIPv4,
+ Net: network,
+ Port: port,
},
want: fmt.Sprintf(
"%s/%d",
@@ -34,10 +33,10 @@ func TestPrefixAddr(t *testing.T) {
),
name: "ipv4",
}, {
- in: &prefixNetAddr{
- prefix: testSubnetIPv6,
- network: network,
- port: port,
+ in: &agdnet.PrefixNetAddr{
+ Prefix: testSubnetIPv6,
+ Net: network,
+ Port: port,
},
want: fmt.Sprintf(
"%s/%d",
@@ -45,10 +44,10 @@ func TestPrefixAddr(t *testing.T) {
),
name: "ipv6",
}, {
- in: &prefixNetAddr{
- prefix: fullPrefix,
- network: network,
- port: port,
+ in: &agdnet.PrefixNetAddr{
+ Prefix: fullPrefix,
+ Net: network,
+ Port: port,
},
want: netip.AddrPortFrom(fullPrefix.Addr(), port).String(),
name: "ipv4_full",
diff --git a/internal/agdnet/resolver.go b/internal/agdnet/resolver.go
index 26fd161..40875e3 100644
--- a/internal/agdnet/resolver.go
+++ b/internal/agdnet/resolver.go
@@ -14,11 +14,11 @@ import (
"github.com/AdguardTeam/golibs/netutil"
)
-// Resolvers
-
// Resolver is the DNS resolver interface.
//
-// See go doc net.Resolver.
+// See [net.Resolver].
+//
+// TODO(a.garipov): Move to golibs and unite with the one in module dnsproxy.
type Resolver interface {
// LookupNetIP returns a slice of host's IP addresses of family specified by
// fam, which must be either [netutil.AddrFamilyIPv4] or
diff --git a/internal/agdservice/agdservice.go b/internal/agdservice/agdservice.go
new file mode 100644
index 0000000..f83ab5c
--- /dev/null
+++ b/internal/agdservice/agdservice.go
@@ -0,0 +1,38 @@
+// Package agdservice defines types and interfaces for long-running services.
+//
+// TODO(a.garipov): Move to golibs.
+package agdservice
+
+import "context"
+
+// Interface is the interface for long-running services.
+//
+// TODO(a.garipov): Define whether or not a service should finish starting or
+// shutting down before returning from these methods.
+type Interface interface {
+ // Start starts the service. ctx is used for cancelation.
+ //
+ // TODO(a.garipov): Use contexts with timeouts everywhere.
+ Start(ctx context.Context) (err error)
+
+ // Shutdown gracefully stops the service. ctx is used to determine
+ // a timeout before trying to stop the service less gracefully.
+ //
+ // TODO(a.garipov): Use contexts with timeouts everywhere.
+ Shutdown(ctx context.Context) (err error)
+}
+
+// type check
+var _ Interface = Empty{}
+
+// Empty is an [Interface] implementation that does nothing.
+type Empty struct{}
+
+// Start implements the [Interface] interface for Empty.
+func (Empty) Start(_ context.Context) (err error) { return nil }
+
+// Shutdown implements the [Interface] interface for Empty.
+func (Empty) Shutdown(_ context.Context) (err error) { return nil }
+
+// unit is a convenient alias for struct{}.
+type unit = struct{}
diff --git a/internal/backend/backend_test.go b/internal/agdservice/agdservice_test.go
similarity index 50%
rename from internal/backend/backend_test.go
rename to internal/agdservice/agdservice_test.go
index 47bc563..d1a736e 100644
--- a/internal/backend/backend_test.go
+++ b/internal/agdservice/agdservice_test.go
@@ -1,7 +1,8 @@
-package backend_test
+package agdservice_test
import (
"testing"
+ "time"
"github.com/AdguardTeam/golibs/testutil"
)
@@ -9,3 +10,6 @@ import (
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
+
+// testTimeout is the timeout for common test operations.
+const testTimeout = 1 * time.Second
diff --git a/internal/agd/refresh.go b/internal/agdservice/refresh.go
similarity index 51%
rename from internal/agd/refresh.go
rename to internal/agdservice/refresh.go
index 6b127d6..a685260 100644
--- a/internal/agd/refresh.go
+++ b/internal/agdservice/refresh.go
@@ -1,32 +1,32 @@
-package agd
+package agdservice
import (
"context"
"fmt"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/log"
+ "golang.org/x/exp/rand"
)
-// Refreshable Entities And Utilities
-
// Refresher is the interface for entities that can update themselves.
type Refresher interface {
Refresh(ctx context.Context) (err error)
}
-var _ Service = (*RefreshWorker)(nil)
-
-// RefreshWorker is a Service that updates its refreshable entity every tick of
-// the provided ticker.
+// RefreshWorker is an [Interface] implementation that updates its [Refresher]
+// every tick of the provided ticker.
type RefreshWorker struct {
- done chan unit
- context func() (ctx context.Context, cancel context.CancelFunc)
- logRoutine func(format string, args ...any)
- tick *time.Ticker
- refr Refresher
- errColl ErrorCollector
- name string
+ done chan unit
+ context func() (ctx context.Context, cancel context.CancelFunc)
+ logRoutine func(format string, args ...any)
+ tick *time.Ticker
+ rand *rand.Rand
+ refr Refresher
+ errColl errcoll.Interface
+ name string
+ maxStartSleep time.Duration
refrOnShutdown bool
}
@@ -40,13 +40,22 @@ type RefreshWorkerConfig struct {
Refresher Refresher
// ErrColl is used to collect errors during refreshes.
- ErrColl ErrorCollector
+ //
+ // TODO(a.garipov): Remove this and make all Refreshers handle their own
+ // errors.
+ ErrColl errcoll.Interface
// Name is the name of this worker. It is used for logging and error
// collecting.
+ //
+ // TODO(a.garipov): Consider accepting a slog.Logger or removing this and
+ // making all Refreshers handle their own logging.
Name string
// 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
@@ -59,6 +68,14 @@ type RefreshWorkerConfig struct {
// than on the Info one. This is useful to prevent routine logs from
// workers with a small interval from overflowing with messages.
RoutineLogsAreDebug 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
@@ -72,27 +89,39 @@ func NewRefreshWorker(c *RefreshWorkerConfig) (w *RefreshWorker) {
logRoutine = log.Info
}
+ var maxStartSleep time.Duration
+ var rng *rand.Rand
+ if c.RandomizeStart {
+ maxStartSleep = c.Interval / 10
+ rng = rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
+ }
+
return &RefreshWorker{
done: make(chan unit),
context: c.Context,
logRoutine: logRoutine,
tick: time.NewTicker(c.Interval),
+ rand: rng,
refr: c.Refresher,
errColl: c.ErrColl,
name: c.Name,
+ maxStartSleep: maxStartSleep,
refrOnShutdown: c.RefreshOnShutdown,
}
}
-// Start implements the Service interface for *RefreshWorker. err is always
+// type check
+var _ Interface = (*RefreshWorker)(nil)
+
+// Start implements the [Interface] interface for *RefreshWorker. err is always
// nil.
-func (w *RefreshWorker) Start() (err error) {
+func (w *RefreshWorker) Start(_ context.Context) (err error) {
go w.refreshInALoop()
return nil
}
-// Shutdown implements the Service interface for *RefreshWorker.
+// Shutdown implements the [Interface] interface for *RefreshWorker.
func (w *RefreshWorker) Shutdown(ctx context.Context) (err error) {
if w.refrOnShutdown {
err = w.refr.Refresh(ctx)
@@ -105,9 +134,8 @@ func (w *RefreshWorker) Shutdown(ctx context.Context) (err error) {
name := w.name
if err != nil {
err = fmt.Errorf("refresh on shutdown: %w", err)
- log.Error("%s: shut down with error: %s", name, err)
} else {
- log.Info("%s: shut down successfully", name)
+ log.Info("worker %q: shut down successfully", name)
}
return err
@@ -119,24 +147,57 @@ func (w *RefreshWorker) refreshInALoop() {
name := w.name
defer log.OnPanic(name)
- log.Info("%s: starting refresh loop", name)
+ log.Info("worker %q: starting refresh loop", name)
for {
select {
case <-w.done:
- log.Info("%s: finished refresh loop", name)
+ log.Info("worker %q: finished refresh loop", name)
return
case <-w.tick.C:
- w.refresh()
+ if w.sleepRandom() {
+ 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() (shouldRefresh bool) {
+ if w.maxStartSleep == 0 {
+ return true
+ }
+
+ sleepDur := time.Duration(w.rand.Int63n(int64(w.maxStartSleep)))
+ w.logRoutine("worker %q: sleeping for %s before refresh", w.name, 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() {
name := w.name
- w.logRoutine("%s: refreshing", name)
+ w.logRoutine("worker %q: refreshing", name)
// TODO(a.garipov): Consider adding a helper for enriching errors with
// context deadline data without duplication. See an example in method
@@ -144,15 +205,15 @@ func (w *RefreshWorker) refresh() {
ctx, cancel := w.context()
defer cancel()
- log.Debug("%s: starting refresh", name)
+ log.Debug("worker %q: starting refresh", name)
err := w.refr.Refresh(ctx)
- log.Debug("%s: finished refresh", name)
+ log.Debug("worker %q: finished refresh", name)
if err != nil {
- Collectf(ctx, w.errColl, "%s: %w", name, err)
+ errcoll.Collectf(ctx, w.errColl, "%s: %w", name, err)
return
}
- w.logRoutine("%s: refreshed successfully", name)
+ w.logRoutine("worker %q: refreshed successfully", name)
}
diff --git a/internal/agd/refresh_test.go b/internal/agdservice/refresh_test.go
similarity index 72%
rename from internal/agd/refresh_test.go
rename to internal/agdservice/refresh_test.go
index c3e1d8d..b1ebba7 100644
--- a/internal/agd/refresh_test.go
+++ b/internal/agdservice/refresh_test.go
@@ -1,11 +1,11 @@
-package agd_test
+package agdservice_test
import (
"context"
"testing"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
@@ -45,11 +45,11 @@ func newTestRefresher(t *testing.T, respErr error) (refr *agdtest.Refresher, syn
// newRefrConf returns worker configuration.
func newRefrConf(
t *testing.T,
- refr agd.Refresher,
+ refr agdservice.Refresher,
ivl time.Duration,
refrOnShutDown bool,
errCh chan sig,
-) (conf *agd.RefreshWorkerConfig) {
+) (conf *agdservice.RefreshWorkerConfig) {
t.Helper()
pt := testutil.PanicT{}
@@ -60,7 +60,7 @@ func newRefrConf(
},
}
- return &agd.RefreshWorkerConfig{
+ return &agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), testTimeout)
},
@@ -70,6 +70,7 @@ func newRefrConf(
Interval: ivl,
RefreshOnShutdown: refrOnShutDown,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
}
}
@@ -78,18 +79,15 @@ func TestRefreshWorker(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil)
errCh := make(chan sig, 1)
- w := agd.NewRefreshWorker(newRefrConf(t, refr, testIvl, false, errCh))
+ w := agdservice.NewRefreshWorker(newRefrConf(t, refr, testIvl, false, errCh))
- err := w.Start()
+ err := w.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
require.Empty(t, errCh)
- shutdown, cancel := context.WithTimeout(context.Background(), testTimeout)
- defer cancel()
-
- err = w.Shutdown(shutdown)
+ err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
})
@@ -97,15 +95,12 @@ func TestRefreshWorker(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil)
errCh := make(chan sig, 1)
- w := agd.NewRefreshWorker(newRefrConf(t, refr, testIvlLong, true, errCh))
+ w := agdservice.NewRefreshWorker(newRefrConf(t, refr, testIvlLong, true, errCh))
- err := w.Start()
+ err := w.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
- shutdown, cancel := context.WithTimeout(context.Background(), testTimeout)
- defer cancel()
-
- err = w.Shutdown(shutdown)
+ err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
@@ -116,18 +111,15 @@ func TestRefreshWorker(t *testing.T) {
errRefr, syncCh := newTestRefresher(t, testError)
errCh := make(chan sig, 1)
- w := agd.NewRefreshWorker(newRefrConf(t, errRefr, testIvl, false, errCh))
+ w := agdservice.NewRefreshWorker(newRefrConf(t, errRefr, testIvl, false, errCh))
- err := w.Start()
+ err := w.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
testutil.RequireReceive(t, errCh, testTimeout)
- shutdown, cancel := context.WithTimeout(context.Background(), testTimeout)
- defer cancel()
-
- err = w.Shutdown(shutdown)
+ err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
})
@@ -135,15 +127,12 @@ func TestRefreshWorker(t *testing.T) {
errRefr, syncCh := newTestRefresher(t, testError)
errCh := make(chan sig, 1)
- w := agd.NewRefreshWorker(newRefrConf(t, errRefr, testIvlLong, true, errCh))
+ w := agdservice.NewRefreshWorker(newRefrConf(t, errRefr, testIvlLong, true, errCh))
- err := w.Start()
+ err := w.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
- shutdown, cancel := context.WithTimeout(context.Background(), testTimeout)
- defer cancel()
-
- err = w.Shutdown(shutdown)
+ err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
assert.ErrorIs(t, err, testError)
testutil.RequireReceive(t, syncCh, testTimeout)
diff --git a/internal/agdsync/agdsync.go b/internal/agdsync/agdsync.go
deleted file mode 100644
index 938b3d3..0000000
--- a/internal/agdsync/agdsync.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Package agdsync contains extensions and utilities for package sync from the
-// standard library.
-//
-// TODO(a.garipov): Move to module golibs.
-package agdsync
-
-import "sync"
-
-// TypedPool is the strongly typed version of [sync.Pool] that manages pointers
-// to T.
-type TypedPool[T any] struct {
- pool *sync.Pool
-}
-
-// NewTypedPool returns a new strongly typed pool. newFunc must not be nil.
-func NewTypedPool[T any](newFunc func() (v *T)) (p *TypedPool[T]) {
- return &TypedPool[T]{
- pool: &sync.Pool{
- New: func() (v any) { return newFunc() },
- },
- }
-}
-
-// Get selects an arbitrary item from the pool, removes it from the pool, and
-// returns it to the caller.
-//
-// See [sync.Pool.Get].
-func (p *TypedPool[T]) Get() (v *T) {
- return p.pool.Get().(*T)
-}
-
-// Put adds v to the pool.
-//
-// See [sync.Pool.Put].
-func (p *TypedPool[T]) Put(v *T) {
- p.pool.Put(v)
-}
diff --git a/internal/agdtest/agdtest.go b/internal/agdtest/agdtest.go
index 672745c..60ac18d 100644
--- a/internal/agdtest/agdtest.go
+++ b/internal/agdtest/agdtest.go
@@ -3,6 +3,8 @@
package agdtest
import (
+ "context"
+ "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@@ -18,5 +20,23 @@ const FilteredResponseTTLSec = 10
// NewConstructor returns a standard dnsmsg.Constructor for tests.
func NewConstructor() (c *dnsmsg.Constructor) {
- return dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL)
+ return dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL)
+}
+
+// NewCloner returns a standard dnsmsg.Cloner for tests.
+func NewCloner() (c *dnsmsg.Cloner) {
+ return dnsmsg.NewCloner(dnsmsg.EmptyClonerStat{})
+}
+
+// ContextWithTimeout is a helper that creates a new context with timeout and
+// registers ctx's cleanup with t.Cleanup.
+//
+// TODO(a.garipov): Move to golibs.
+func ContextWithTimeout(tb testing.TB, timeout time.Duration) (ctx context.Context) {
+ tb.Helper()
+
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ tb.Cleanup(cancel)
+
+ return ctx
}
diff --git a/internal/agdtest/interface.go b/internal/agdtest/interface.go
index df69850..716522c 100644
--- a/internal/agdtest/interface.go
+++ b/internal/agdtest/interface.go
@@ -9,11 +9,13 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
@@ -29,34 +31,6 @@ import (
// Module AdGuardDNS
-// type check
-var _ agd.ErrorCollector = (*ErrorCollector)(nil)
-
-// ErrorCollector is an agd.ErrorCollector for tests.
-//
-// TODO(a.garipov): Actually test the error collection where this is used.
-type ErrorCollector struct {
- OnCollect func(ctx context.Context, err error)
-}
-
-// Collect implements the agd.ErrorCollector interface for *ErrorCollector.
-func (c *ErrorCollector) Collect(ctx context.Context, err error) {
- c.OnCollect(ctx, err)
-}
-
-// type check
-var _ agd.Refresher = (*Refresher)(nil)
-
-// Refresher is an agd.Refresher for tests.
-type Refresher struct {
- OnRefresh func(ctx context.Context) (err error)
-}
-
-// Refresh implements the agd.Refresher interface for *Refresher.
-func (r *Refresher) Refresh(ctx context.Context) (err error) {
- return r.OnRefresh(ctx)
-}
-
// Package access
// type check
@@ -83,7 +57,7 @@ func (a *AccessManager) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) {
// type check
var _ agdnet.Resolver = (*Resolver)(nil)
-// Resolver is an agd.Resolver for tests.
+// Resolver is an [agdnet.Resolver] for tests.
type Resolver struct {
OnLookupNetIP func(
ctx context.Context,
@@ -92,7 +66,7 @@ type Resolver struct {
) (ips []netip.Addr, err error)
}
-// LookupNetIP implements the [agd.Resolver] interface for *Resolver.
+// LookupNetIP implements the [agdnet.Resolver] interface for *Resolver.
func (r *Resolver) LookupNetIP(
ctx context.Context,
fam netutil.AddrFamily,
@@ -101,6 +75,21 @@ func (r *Resolver) LookupNetIP(
return r.OnLookupNetIP(ctx, fam, host)
}
+// 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 billstat
// type check
@@ -111,8 +100,8 @@ type BillStatRecorder struct {
OnRecord func(
ctx context.Context,
id agd.DeviceID,
- ctry agd.Country,
- asn agd.ASN,
+ ctry geoip.Country,
+ asn geoip.ASN,
start time.Time,
proto agd.Protocol,
)
@@ -122,8 +111,8 @@ type BillStatRecorder struct {
func (r *BillStatRecorder) Record(
ctx context.Context,
id agd.DeviceID,
- ctry agd.Country,
- asn agd.ASN,
+ ctry geoip.Country,
+ asn geoip.ASN,
start time.Time,
proto agd.Protocol,
) {
@@ -177,6 +166,23 @@ func (db *DNSDB) Record(ctx context.Context, resp *dns.Msg, ri *agd.RequestInfo)
db.OnRecord(ctx, resp, ri)
}
+// Package errcoll
+
+// type check
+var _ errcoll.Interface = (*ErrorCollector)(nil)
+
+// ErrorCollector is an [errcoll.Interface] for tests.
+//
+// TODO(a.garipov): Actually test the error collection where this is used.
+type ErrorCollector struct {
+ OnCollect func(ctx context.Context, err error)
+}
+
+// Collect implements the [errcoll.Interface] interface for *ErrorCollector.
+func (c *ErrorCollector) Collect(ctx context.Context, err error) {
+ c.OnCollect(ctx, err)
+}
+
// Package filter
// type check
@@ -263,25 +269,18 @@ var _ geoip.Interface = (*GeoIP)(nil)
// GeoIP is a geoip.Interface for tests.
type GeoIP struct {
- OnSubnetByLocation func(
- c agd.Country,
- a agd.ASN,
- fam netutil.AddrFamily,
- ) (n netip.Prefix, err error)
- OnData func(host string, ip netip.Addr) (l *agd.Location, err error)
+ OnSubnetByLocation func(l *geoip.Location, fam netutil.AddrFamily) (n netip.Prefix, err error)
+ OnData func(host string, ip netip.Addr) (l *geoip.Location, err error)
}
// SubnetByLocation implements the geoip.Interface interface for *GeoIP.
-func (g *GeoIP) SubnetByLocation(
- c agd.Country,
- a agd.ASN,
- fam netutil.AddrFamily,
+func (g *GeoIP) SubnetByLocation(l *geoip.Location, fam netutil.AddrFamily,
) (n netip.Prefix, err error) {
- return g.OnSubnetByLocation(c, a, fam)
+ return g.OnSubnetByLocation(l, fam)
}
// Data implements the geoip.Interface interface for *GeoIP.
-func (g *GeoIP) Data(host string, ip netip.Addr) (l *agd.Location, err error) {
+func (g *GeoIP) Data(host string, ip netip.Addr) (l *geoip.Location, err error) {
return g.OnData(host, ip)
}
diff --git a/internal/backend/backend.go b/internal/backend/backend.go
deleted file mode 100644
index 901686a..0000000
--- a/internal/backend/backend.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// Package backend contains implementations of several interfaces that send or
-// receive information to or from the business logic backend service.
-package backend
-
-// Common Constants, Types, And Utilities
-
-// Path constants.
-const (
- PathDNSAPIV1DevicesActivity = "/dns_api/v1/devices_activity"
- PathDNSAPIV1Settings = "/dns_api/v1/settings"
-)
diff --git a/internal/backend/billstat.go b/internal/backend/billstat.go
deleted file mode 100644
index 45a7625..0000000
--- a/internal/backend/billstat.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package backend
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
- "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
- "github.com/AdguardTeam/golibs/errors"
- "github.com/AdguardTeam/golibs/mapsutil"
-)
-
-// Billing Statistics Uploader
-
-// BillStatConfig is the configuration structure for the business logic backend
-// billing statistics uploader.
-type BillStatConfig struct {
- // BaseEndpoint is the base URL to which API paths are appended.
- BaseEndpoint *url.URL
-}
-
-// NewBillStat creates a new billing statistics uploader. c must not be nil.
-func NewBillStat(c *BillStatConfig) (b *BillStat) {
- return &BillStat{
- apiURL: c.BaseEndpoint.JoinPath(PathDNSAPIV1DevicesActivity),
- // Assume that the timeouts are handled by the context in Upload.
- http: agdhttp.NewClient(&agdhttp.ClientConfig{}),
- }
-}
-
-// BillStat is the implementation of the [billstat.Uploader] interface that
-// uploads the billing statistics to the business logic backend. It is safe for
-// concurrent use.
-//
-// TODO(a.garipov): Consider uniting with [ProfileStorage] into a single
-// backend.Client.
-type BillStat struct {
- apiURL *url.URL
- http *agdhttp.Client
-}
-
-// type check
-var _ billstat.Uploader = (*BillStat)(nil)
-
-// Upload implements the [billstat.Uploader] interface for *BillStat.
-func (b *BillStat) Upload(ctx context.Context, records billstat.Records) (err error) {
- if len(records) == 0 {
- return nil
- }
-
- req := &v1DevicesActivityReq{
- Devices: billStatRecsToReq(records),
- }
- data, err := json.Marshal(req)
- if err != nil {
- return fmt.Errorf("encoding billstat req: %w", err)
- }
-
- reqURL := b.apiURL.Redacted()
- resp, err := b.http.Post(ctx, b.apiURL, agdhttp.HdrValApplicationJSON, bytes.NewReader(data))
- if err != nil {
- return fmt.Errorf("sending to %s: %w", reqURL, err)
- }
- defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
-
- err = agdhttp.CheckStatus(resp, http.StatusOK)
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return err
- }
-
- return nil
-}
-
-// v1DevicesActivityReq is a request to the devices activity HTTP API.
-type v1DevicesActivityReq struct {
- Devices []*v1DevicesActivityReqDevice `json:"devices"`
-}
-
-// v1DevicesActivityReqDevice is a single device within a request to the devices
-// activity HTTP API.
-type v1DevicesActivityReqDevice struct {
- // ClientCountry is the detected country of the client's IP address, if any.
- ClientCountry agd.Country `json:"client_country"`
-
- // DeviceID is the ID of the device.
- DeviceID agd.DeviceID `json:"device_id"`
-
- // Time is the time of the most recent query from the device, in Unix time
- // in milliseconds.
- Time int64 `json:"time_ms"`
-
- // ASN is the detected ASN of the client's IP address, if any.
- ASN agd.ASN `json:"asn"`
-
- // Queries is the total number of Queries the device has performed since the
- // most recent sync. This value is an int32 to be in sync with the business
- // logic backend which uses this type. Change it if it is changed there.
- Queries int32 `json:"queries"`
-
- // Proto is the numeric value of the DNS protocol of the most recent query
- // from the device. It is a uint8 and not an agd.Protocol to make sure that
- // it always remains numeric even if we implement json.Marshal on
- // agd.Protocol in the future.
- Proto uint8 `json:"proto"`
-}
-
-// billStatRecsToReq converts billing statistics records into devices for the
-// devices activity HTTP API.
-func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityReqDevice) {
- devices = make([]*v1DevicesActivityReqDevice, 0, len(records))
- mapsutil.OrderedRange(records, func(id agd.DeviceID, rec *billstat.Record) (cont bool) {
- devices = append(devices, &v1DevicesActivityReqDevice{
- ClientCountry: rec.Country,
- DeviceID: id,
- Time: rec.Time.UnixMilli(),
- ASN: rec.ASN,
- Queries: rec.Queries,
- Proto: uint8(rec.Proto),
- })
-
- return true
- })
-
- return devices
-}
diff --git a/internal/backend/billstat_test.go b/internal/backend/billstat_test.go
deleted file mode 100644
index b992a8c..0000000
--- a/internal/backend/billstat_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package backend_test
-
-import (
- "context"
- "encoding/json"
- "io"
- "net/http"
- "net/http/httptest"
- "net/url"
- "testing"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/backend"
- "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
- "github.com/AdguardTeam/golibs/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestBillStat_Upload(t *testing.T) {
- var reqURLStr string
- var body []byte
- h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- pt := testutil.PanicT{}
-
- reqURLStr = r.URL.String()
-
- var err error
- body, err = io.ReadAll(r.Body)
- require.NoError(pt, err)
-
- w.WriteHeader(http.StatusOK)
- })
-
- // TODO(a.garipov): Don't listen on actual sockets and use piped conns
- // instead. Perhaps, add these to a new network test utility package in the
- // golibs module.
- srv := httptest.NewServer(h)
- t.Cleanup(srv.Close)
-
- u, err := url.Parse(srv.URL)
- require.NoError(t, err)
-
- c := &backend.BillStatConfig{
- BaseEndpoint: u,
- }
-
- b := backend.NewBillStat(c)
- require.NotNil(t, b)
-
- reqTime := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
- records := billstat.Records{
- "dev1234": &billstat.Record{
- Time: reqTime,
- Country: agd.CountryAD,
- Queries: 123,
- ASN: 1230,
- Proto: agd.ProtoDoH,
- },
- "dev5678": &billstat.Record{
- Time: reqTime.Add(1 * time.Second),
- Country: agd.CountryAE,
- Queries: 42,
- ASN: 420,
- Proto: agd.ProtoDoQ,
- },
- }
-
- ctx := context.Background()
- err = b.Upload(ctx, records)
- require.NoError(t, err)
-
- // Compare against a relative URL since the URL inside an HTTP handler
- // seems to always be relative.
- assert.Equal(t, backend.PathDNSAPIV1DevicesActivity, reqURLStr)
-
- type jobj = map[string]any
- type jarr = []any
-
- wantData := jobj{
- "devices": jarr{jobj{
- "client_country": "AD",
- "device_id": "dev1234",
- "time_ms": 1640995200000,
- "queries": 123,
- "asn": 1230,
- "proto": 3,
- }, jobj{
- "client_country": "AE",
- "device_id": "dev5678",
- "time_ms": 1640995201000,
- "queries": 42,
- "asn": 420,
- "proto": 4,
- }},
- }
-
- wantBody, err := json.Marshal(wantData)
- require.NoError(t, err)
-
- assert.JSONEq(t, string(wantBody), string(body))
-}
diff --git a/internal/backend/profiledb.go b/internal/backend/profiledb.go
deleted file mode 100644
index e7f84d3..0000000
--- a/internal/backend/profiledb.go
+++ /dev/null
@@ -1,561 +0,0 @@
-package backend
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "net/netip"
- "net/url"
- "strconv"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
- "github.com/AdguardTeam/golibs/errors"
- "github.com/AdguardTeam/golibs/netutil"
- "github.com/AdguardTeam/golibs/timeutil"
- "golang.org/x/exp/slices"
-)
-
-// Profile Storage
-
-// ProfileStorageConfig is the configuration for the business logic backend
-// profile storage.
-type ProfileStorageConfig struct {
- // BaseEntpoint is the base URL to which API paths are appended.
- BaseEndpoint *url.URL
-
- // Now is returns the current time, typically time.Now. It is used to set
- // UpdateTime on profiles.
- Now func() (t time.Time)
-
- // ErrColl is the error collector that is used to collect critical and
- // non-critical errors.
- ErrColl agd.ErrorCollector
-}
-
-// NewProfileStorage returns a new [ProfileStorage] that retrieves information
-// from the business logic backend.
-func NewProfileStorage(c *ProfileStorageConfig) (s *ProfileStorage) {
- return &ProfileStorage{
- apiURL: c.BaseEndpoint.JoinPath(PathDNSAPIV1Settings),
- // Assume that the timeouts are handled by the context in Profiles.
- http: agdhttp.NewClient(&agdhttp.ClientConfig{}),
- now: c.Now,
- errColl: c.ErrColl,
- }
-}
-
-// ProfileStorage is the implementation of the [profiledb.Storage] interface
-// that retrieves the profile and device information from the business logic
-// backend. It is safe for concurrent use.
-//
-// TODO(a.garipov): Consider uniting with [BillStat] into a single
-// backend.Client.
-type ProfileStorage struct {
- apiURL *url.URL
- http *agdhttp.Client
- now func() (t time.Time)
- errColl agd.ErrorCollector
-}
-
-// type check
-var _ profiledb.Storage = (*ProfileStorage)(nil)
-
-// Profiles implements the [profiledb.Storage] interface for *ProfileStorage.
-func (s *ProfileStorage) Profiles(
- ctx context.Context,
- req *profiledb.StorageRequest,
-) (resp *profiledb.StorageResponse, err error) {
- q := url.Values{}
- if !req.SyncTime.IsZero() {
- syncTimeStr := strconv.FormatInt(req.SyncTime.UnixMilli(), 10)
- q.Add("sync_time", syncTimeStr)
- }
-
- u := netutil.CloneURL(s.apiURL)
- u.RawQuery = q.Encode()
- redURL := u.Redacted()
-
- settResp, err := s.loadSettingsResponse(ctx, u)
- if err != nil {
- return nil, fmt.Errorf("loading from url %s: %w", redURL, err)
- }
-
- return settResp.toInternal(ctx, s.now(), s.errColl), nil
-}
-
-// loadSettingsResponse fetches, decodes, and returns the settings response.
-func (s *ProfileStorage) loadSettingsResponse(
- ctx context.Context,
- u *url.URL,
-) (resp *v1SettingsResp, err error) {
- httpResp, err := s.http.Get(ctx, u)
- if err != nil {
- return nil, fmt.Errorf("calling backend: %w", err)
- }
- defer func() { err = errors.WithDeferred(err, httpResp.Body.Close()) }()
-
- err = agdhttp.CheckStatus(httpResp, http.StatusOK)
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return nil, err
- }
-
- resp = &v1SettingsResp{}
- err = json.NewDecoder(httpResp.Body).Decode(resp)
- if err != nil {
- return nil, agdhttp.WrapServerError(
- fmt.Errorf("decoding response: %w", err),
- httpResp,
- )
- }
-
- return resp, nil
-}
-
-// v1SettingsRespSchedule is the structure for decoding the
-// settings.*.parental.schedule property of the response from the backend.
-type v1SettingsRespSchedule struct {
- // TODO(a.garipov): Consider making a custom type detecting an absence of
- // value to remove these pointers.
- Monday *[2]timeutil.Duration `json:"mon"`
- Tuesday *[2]timeutil.Duration `json:"tue"`
- Wednesday *[2]timeutil.Duration `json:"wed"`
- Thursday *[2]timeutil.Duration `json:"thu"`
- Friday *[2]timeutil.Duration `json:"fri"`
- Saturday *[2]timeutil.Duration `json:"sat"`
- Sunday *[2]timeutil.Duration `json:"sun"`
-
- // TimeZone is the tzdata name of the time zone.
- //
- // NOTE: Do not use *agdtime.Location here so that lookup failures are
- // properly mitigated in [v1SettingsRespParental.toInternal].
- TimeZone string `json:"tmz"`
-}
-
-// v1SettingsRespParental is the structure for decoding the settings.*.parental
-// property of the response from the backend.
-type v1SettingsRespParental struct {
- Schedule *v1SettingsRespSchedule `json:"schedule"`
-
- BlockedServices []string `json:"blocked_services"`
-
- Enabled bool `json:"enabled"`
- BlockAdult bool `json:"block_adult"`
- GeneralSafeSearch bool `json:"general_safe_search"`
- YoutubeSafeSearch bool `json:"youtube_safe_search"`
-}
-
-// v1SettingsRespDevice is the structure for decoding the settings.devices
-// property of the response from the backend.
-type v1SettingsRespDevice struct {
- LinkedIP netip.Addr `json:"linked_ip"`
- ID string `json:"id"`
- Name string `json:"name"`
- DedicatedIPs []netip.Addr `json:"dedicated_ips"`
- FilteringEnabled bool `json:"filtering_enabled"`
-}
-
-// v1SettingsRespSettings is the structure for decoding the settings property of
-// the response from the backend.
-type v1SettingsRespSettings struct {
- DNSID string `json:"dns_id"`
- Parental *v1SettingsRespParental `json:"parental"`
- RuleLists *v1SettingsRespRuleLists `json:"rule_lists"`
- SafeBrowsing *v1SettingsRespSafeBrowsing `json:"safe_browsing"`
- BlockingMode dnsmsg.BlockingModeCodec `json:"blocking_mode"`
- Devices []*v1SettingsRespDevice `json:"devices"`
- CustomRules []string `json:"custom_rules"`
- FilteredResponseTTL uint32 `json:"filtered_response_ttl"`
- QueryLogEnabled bool `json:"query_log_enabled"`
- FilteringEnabled bool `json:"filtering_enabled"`
- Deleted bool `json:"deleted"`
- BlockPrivateRelay bool `json:"block_private_relay"`
- BlockFirefoxCanary bool `json:"block_firefox_canary"`
-}
-
-// type check
-var _ json.Unmarshaler = (*v1SettingsRespSettings)(nil)
-
-// UnmarshalJSON implements the [json.Unmarshaler] interface for
-// *v1SettingsRespSettings. It puts default value into BlockFirefoxCanary
-// field while it is not implemented on the backend side.
-//
-// TODO(a.garipov): Remove once the backend starts to always send it.
-func (rs *v1SettingsRespSettings) UnmarshalJSON(b []byte) (err error) {
- type defaultDec v1SettingsRespSettings
-
- s := defaultDec{
- BlockingMode: dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- },
- BlockFirefoxCanary: true,
- }
-
- if err = json.Unmarshal(b, &s); err != nil {
- return err
- }
-
- *rs = v1SettingsRespSettings(s)
-
- return nil
-}
-
-// v1SettingsRespRuleLists is the structure for decoding filtering rule lists
-// based filtering settings from the backend.
-type v1SettingsRespRuleLists struct {
- IDs []string `json:"ids"`
- Enabled bool `json:"enabled"`
-}
-
-// v1SettingsRespSafeBrowsing is the structure for decoding the general safe
-// browsing filtering settings from the backend.
-type v1SettingsRespSafeBrowsing struct {
- BlockDangerousDomains *bool `json:"block_dangerous_domains"`
- BlockNewlyRegisteredDomains bool `json:"block_nrd"`
- Enabled bool `json:"enabled"`
-}
-
-// toInternal converts s to an [agd.SafeBrowsingSettings] instance.
-func (s *v1SettingsRespSafeBrowsing) toInternal() (res *agd.SafeBrowsingSettings) {
- if s == nil {
- return nil
- }
-
- // TODO(d.kolyshev): Don't make this migration after AGDNS-1537.
- blockDangerDomains := s.Enabled
- if s.BlockDangerousDomains != nil {
- blockDangerDomains = *s.BlockDangerousDomains
- }
-
- return &agd.SafeBrowsingSettings{
- Enabled: s.Enabled,
- BlockDangerousDomains: blockDangerDomains,
- BlockNewlyRegisteredDomains: s.BlockNewlyRegisteredDomains,
- }
-}
-
-// v1SettingsResp is the structure for decoding the response from the backend.
-type v1SettingsResp struct {
- Settings []*v1SettingsRespSettings `json:"settings"`
-
- SyncTime int64 `json:"sync_time"`
-}
-
-// toInternal converts p to an [agd.ParentalProtectionSettings] instance.
-func (p *v1SettingsRespParental) toInternal(
- ctx context.Context,
- errColl agd.ErrorCollector,
- settIdx int,
-) (res *agd.ParentalProtectionSettings, err error) {
- if p == nil {
- return nil, nil
- }
-
- var sch *agd.ParentalProtectionSchedule
- if psch := p.Schedule; psch != nil {
- sch = &agd.ParentalProtectionSchedule{}
-
- // TODO(a.garipov): Cache location lookup results.
- sch.TimeZone, err = agdtime.LoadLocation(psch.TimeZone)
- if err != nil {
- // Report the error and assume UTC.
- reportf(ctx, errColl, "settings at index %d: schedule: time zone: %w", settIdx, err)
-
- sch.TimeZone = agdtime.UTC()
- }
-
- sch.Week = &agd.WeeklySchedule{}
- days := []*[2]timeutil.Duration{
- psch.Sunday,
- psch.Monday,
- psch.Tuesday,
- psch.Wednesday,
- psch.Thursday,
- psch.Friday,
- psch.Saturday,
- }
- for i, d := range days {
- if d == nil {
- sch.Week[i] = agd.ZeroLengthDayRange()
-
- continue
- }
-
- sch.Week[i] = agd.DayRange{
- Start: uint16(d[0].Minutes()),
- End: uint16(d[1].Minutes()),
- }
- }
-
- for i, r := range sch.Week {
- err = r.Validate()
- if err != nil {
- return nil, fmt.Errorf("weekday %s: %w", time.Weekday(i), err)
- }
- }
- }
-
- blockedSvcs := blockedSvcsToInternal(ctx, errColl, settIdx, p.BlockedServices)
-
- return &agd.ParentalProtectionSettings{
- Schedule: sch,
-
- BlockedServices: blockedSvcs,
-
- Enabled: p.Enabled,
- BlockAdult: p.BlockAdult,
- GeneralSafeSearch: p.GeneralSafeSearch,
- YoutubeSafeSearch: p.YoutubeSafeSearch,
- }, nil
-}
-
-// blockedSvcsToInternal is a helper that converts the blocked service IDs from
-// the backend response to AdGuard DNS blocked service IDs.
-func blockedSvcsToInternal(
- ctx context.Context,
- errColl agd.ErrorCollector,
- settIdx int,
- respSvcs []string,
-) (svcs []agd.BlockedServiceID) {
- l := len(respSvcs)
- if l == 0 {
- return nil
- }
-
- svcs = make([]agd.BlockedServiceID, 0, l)
- for i, s := range respSvcs {
- id, err := agd.NewBlockedServiceID(s)
- if err != nil {
- reportf(
- ctx,
- errColl,
- "settings at index %d: blocked service at index %d: %w",
- settIdx,
- i,
- err,
- )
-
- continue
- }
-
- svcs = append(svcs, id)
- }
-
- return svcs
-}
-
-// settsRespPrefix is the logging prefix for logs by v1SettingsResp.
-const settsRespPrefix = "backend.v1SettingsResp"
-
-// devicesToInternal is a helper that converts the devices from the backend
-// response to AdGuard DNS devices.
-func devicesToInternal(
- ctx context.Context,
- errColl agd.ErrorCollector,
- settIdx int,
- respDevices []*v1SettingsRespDevice,
-) (devices []*agd.Device, ids []agd.DeviceID) {
- l := len(respDevices)
- if l == 0 {
- return nil, nil
- }
-
- devices = make([]*agd.Device, 0, l)
- for i, d := range respDevices {
- if d == nil {
- reportf(ctx, errColl, "settings at index %d: device at index %d: is nil", settIdx, i)
-
- continue
- }
-
- // TODO(a.garipov): Consider validating uniqueness of linked and
- // dedicated IPs.
- dev := &agd.Device{
- LinkedIP: d.LinkedIP,
- DedicatedIPs: slices.Clone(d.DedicatedIPs),
- FilteringEnabled: d.FilteringEnabled,
- }
-
- // Use the same error message format string from now on, since all
- // constructors and validators return informative errors.
- const msgFmt = "settings at index %d: device at index %d: %w"
-
- var err error
- dev.ID, err = agd.NewDeviceID(d.ID)
- if err != nil {
- reportf(ctx, errColl, msgFmt, settIdx, i, err)
-
- continue
- }
-
- dev.Name, err = agd.NewDeviceName(d.Name)
- if err != nil {
- reportf(ctx, errColl, msgFmt, settIdx, i, err)
-
- continue
- }
-
- ids = append(ids, dev.ID)
- devices = append(devices, dev)
- }
-
- return devices, ids
-}
-
-// filterListsToInternal is a helper that converts the filter lists from the
-// backend response to AdGuard DNS devices.
-func filterListsToInternal(
- ctx context.Context,
- errColl agd.ErrorCollector,
- settIdx int,
- respFilters *v1SettingsRespRuleLists,
-) (enabled bool, filterLists []agd.FilterListID) {
- if respFilters == nil {
- return false, nil
- }
-
- lists := respFilters.IDs
- l := len(lists)
- if l == 0 {
- return respFilters.Enabled, nil
- }
-
- filterLists = make([]agd.FilterListID, 0, l)
- for i, f := range lists {
- id, err := agd.NewFilterListID(f)
- if err != nil {
- reportf(ctx, errColl, "settings at index %d: filter at index %d: %w", settIdx, i, err)
-
- continue
- }
-
- filterLists = append(filterLists, id)
- }
-
- return respFilters.Enabled, filterLists
-}
-
-// rulesToInternal is a helper that converts the filter rules from the backend
-// response to AdGuard DNS filtering rules.
-func rulesToInternal(
- ctx context.Context,
- errColl agd.ErrorCollector,
- settIdx int,
- respRules []string,
-) (rules []agd.FilterRuleText) {
- l := len(respRules)
- if l == 0 {
- return nil
- }
-
- rules = make([]agd.FilterRuleText, 0, l)
- for i, r := range respRules {
- text, err := agd.NewFilterRuleText(r)
- if err != nil {
- reportf(ctx, errColl, "settings at index %d: rule at index %d: %w", settIdx, i, err)
-
- continue
- }
-
- rules = append(rules, text)
- }
-
- return rules
-}
-
-// maxFltRespTTL is the maximum allowed filtered response TTL.
-const maxFltRespTTL = 1 * time.Hour
-
-// fltRespTTLToInternal converts respTTL to the filtered response TTL. If
-// respTTL is invalid, it returns an error describing the validation error as
-// well as the closest valid value to use.
-func fltRespTTLToInternal(respTTL uint32) (ttl time.Duration, err error) {
- ttl = time.Duration(respTTL) * time.Second
- if ttl > maxFltRespTTL {
- ttl = maxFltRespTTL
- err = fmt.Errorf("too high: got %s, max %s", ttl, maxFltRespTTL)
- }
-
- return ttl, err
-}
-
-// toInternal converts r to an [agd.DSProfilesResponse] instance.
-func (r *v1SettingsResp) toInternal(
- ctx context.Context,
- updTime time.Time,
- // TODO(a.garipov): Here and in other functions, consider just adding the
- // error collector to the context.
- errColl agd.ErrorCollector,
-) (pr *profiledb.StorageResponse) {
- if r == nil {
- return nil
- }
-
- pr = &profiledb.StorageResponse{
- SyncTime: time.Unix(0, r.SyncTime*1_000_000),
- Profiles: make([]*agd.Profile, 0, len(r.Settings)),
- }
-
- for i, s := range r.Settings {
- parental, err := s.Parental.toInternal(ctx, errColl, i)
- if err != nil {
- reportf(ctx, errColl, "settings at index %d: parental: %w", i, err)
-
- continue
- }
-
- devices, deviceIDs := devicesToInternal(ctx, errColl, i, s.Devices)
- rlEnabled, ruleLists := filterListsToInternal(ctx, errColl, i, s.RuleLists)
- rules := rulesToInternal(ctx, errColl, i, s.CustomRules)
-
- id, err := agd.NewProfileID(s.DNSID)
- if err != nil {
- reportf(ctx, errColl, "settings at index %d: profile id: %w", i, err)
-
- continue
- }
-
- fltRespTTL, err := fltRespTTLToInternal(s.FilteredResponseTTL)
- if err != nil {
- reportf(ctx, errColl, "settings at index %d: filtered resp ttl: %w", i, err)
-
- // Go on and use the fixed value.
- //
- // TODO(ameshkov, a.garipov): Consider continuing, like with all
- // other validation errors.
- }
-
- pr.Devices = append(pr.Devices, devices...)
-
- pr.Profiles = append(pr.Profiles, &agd.Profile{
- Parental: parental,
- BlockingMode: s.BlockingMode,
- ID: id,
- UpdateTime: updTime,
- DeviceIDs: deviceIDs,
- RuleListIDs: ruleLists,
- CustomRules: rules,
- FilteredResponseTTL: fltRespTTL,
- SafeBrowsing: s.SafeBrowsing.toInternal(),
- RuleListsEnabled: rlEnabled,
- FilteringEnabled: s.FilteringEnabled,
- QueryLogEnabled: s.QueryLogEnabled,
- Deleted: s.Deleted,
- BlockPrivateRelay: s.BlockPrivateRelay,
- BlockFirefoxCanary: s.BlockFirefoxCanary,
- })
- }
-
- return pr
-}
-
-// reportf is a helper method for reporting non-critical errors.
-func reportf(ctx context.Context, errColl agd.ErrorCollector, format string, args ...any) {
- agd.Collectf(ctx, errColl, settsRespPrefix+": "+format, args...)
-}
diff --git a/internal/backend/profiledb_test.go b/internal/backend/profiledb_test.go
deleted file mode 100644
index e4a5017..0000000
--- a/internal/backend/profiledb_test.go
+++ /dev/null
@@ -1,209 +0,0 @@
-package backend_test
-
-import (
- "context"
- "fmt"
- "net/http"
- "net/http/httptest"
- "net/netip"
- "net/url"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
- "github.com/AdguardTeam/AdGuardDNS/internal/backend"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
- "github.com/AdguardTeam/golibs/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-// Common test constants.
-var (
- syncTime = time.Unix(0, 1_624_443_079_309_000_000)
- updTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
-)
-
-func TestProfileStorage_Profiles(t *testing.T) {
- reqURLStr := ""
- h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- pt := testutil.PanicT{}
-
- // NOTE: Keep testdata/profiles.json formatted using jq. Example of a
- // formatting command using sponge(1) from the current directory:
- //
- // jq '.' ./testdata/profiles.json | sponge ./testdata/profiles.json
- reqURLStr = r.URL.String()
- b, err := os.ReadFile(filepath.Join("testdata", "profiles.json"))
- require.NoError(pt, err)
-
- _, err = w.Write(b)
- require.NoError(pt, err)
- })
-
- // TODO(a.garipov): Don't listen on actual sockets and use piped conns
- // instead. Perhaps, add these to a new network test utility package in the
- // golibs module.
- srv := httptest.NewServer(h)
- t.Cleanup(srv.Close)
-
- u, err := url.Parse(srv.URL)
- require.NoError(t, err)
-
- c := &backend.ProfileStorageConfig{
- BaseEndpoint: u,
- Now: func() (t time.Time) { return updTime },
- }
-
- ds := backend.NewProfileStorage(c)
- require.NotNil(t, ds)
-
- ctx := context.Background()
- req := &profiledb.StorageRequest{
- SyncTime: syncTime,
- }
-
- resp, err := ds.Profiles(ctx, req)
- require.NoError(t, err)
- require.NotNil(t, resp)
-
- // Compare against a relative URL since the URL inside an HTTP handler
- // seems to always be relative.
- wantURLStr := fmt.Sprintf("%s?sync_time=%d", backend.PathDNSAPIV1Settings, syncTime.UnixMilli())
- assert.Equal(t, wantURLStr, reqURLStr)
-
- want := testProfileResp(t)
- assert.Equal(t, want, resp)
-}
-
-// testProfileResp returns profile resp corresponding with testdata.
-//
-// Keep in sync with the testdata one.
-func testProfileResp(t *testing.T) (resp *profiledb.StorageResponse) {
- t.Helper()
-
- wantLoc, err := agdtime.LoadLocation("GMT")
- require.NoError(t, err)
-
- dayRange := agd.DayRange{
- Start: 0,
- End: 59,
- }
-
- wantParental := &agd.ParentalProtectionSettings{
- Schedule: &agd.ParentalProtectionSchedule{
- Week: &agd.WeeklySchedule{
- agd.ZeroLengthDayRange(),
- dayRange,
- dayRange,
- dayRange,
- dayRange,
- dayRange,
- agd.ZeroLengthDayRange(),
- },
- TimeZone: wantLoc,
- },
- BlockedServices: []agd.BlockedServiceID{"youtube"},
- Enabled: false,
- BlockAdult: false,
- GeneralSafeSearch: false,
- YoutubeSafeSearch: false,
- }
-
- wantSafeBrowsing := &agd.SafeBrowsingSettings{
- Enabled: true,
- BlockDangerousDomains: true,
- BlockNewlyRegisteredDomains: false,
- }
-
- wantLinkedIP := netip.AddrFrom4([4]byte{1, 2, 3, 4})
-
- wantBlockingMode := dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeCustomIP{
- IPv4: netip.MustParseAddr("1.2.3.4"),
- IPv6: netip.MustParseAddr("1234::cdef"),
- },
- }
-
- want := &profiledb.StorageResponse{
- SyncTime: syncTime,
- Profiles: []*agd.Profile{{
- Parental: nil,
- BlockingMode: dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- },
- ID: "37f97ee9",
- UpdateTime: updTime,
- DeviceIDs: []agd.DeviceID{
- "118ffe93",
- "b9e1a762",
- },
- RuleListIDs: []agd.FilterListID{"1"},
- CustomRules: nil,
- FilteredResponseTTL: 10 * time.Second,
- SafeBrowsing: wantSafeBrowsing,
- RuleListsEnabled: true,
- FilteringEnabled: true,
- QueryLogEnabled: true,
- Deleted: false,
- BlockPrivateRelay: true,
- BlockFirefoxCanary: true,
- }, {
- Parental: wantParental,
- BlockingMode: wantBlockingMode,
- ID: "83f3ea8f",
- UpdateTime: updTime,
- DeviceIDs: []agd.DeviceID{
- "0d7724fa",
- "6d2ac775",
- "94d4c481",
- "ada436e3",
- },
- RuleListIDs: []agd.FilterListID{"1"},
- CustomRules: []agd.FilterRuleText{"||example.org^"},
- FilteredResponseTTL: 3600 * time.Second,
- SafeBrowsing: wantSafeBrowsing,
- RuleListsEnabled: true,
- FilteringEnabled: true,
- QueryLogEnabled: true,
- Deleted: true,
- BlockPrivateRelay: false,
- BlockFirefoxCanary: false,
- }},
- Devices: []*agd.Device{{
- ID: "118ffe93",
- Name: "Device 1",
- FilteringEnabled: true,
- }, {
- ID: "b9e1a762",
- Name: "Device 2",
- FilteringEnabled: true,
- }, {
- ID: "0d7724fa",
- Name: "Device 1",
- FilteringEnabled: true,
- }, {
- ID: "6d2ac775",
- Name: "Device 2",
- FilteringEnabled: true,
- }, {
- ID: "94d4c481",
- Name: "Device 3",
- DedicatedIPs: []netip.Addr{
- netip.MustParseAddr("1.2.3.4"),
- },
- FilteringEnabled: true,
- }, {
- ID: "ada436e3",
- LinkedIP: wantLinkedIP,
- Name: "Device 4",
- FilteringEnabled: true,
- }},
- }
-
- return want
-}
diff --git a/internal/backend/testdata/profiles.json b/internal/backend/testdata/profiles.json
deleted file mode 100644
index 26f3dee..0000000
--- a/internal/backend/testdata/profiles.json
+++ /dev/null
@@ -1,121 +0,0 @@
-{
- "sync_time": 1624443079309,
- "settings": [
- {
- "dns_id": "37f97ee9",
- "filtering_enabled": true,
- "query_log_enabled": true,
- "safe_browsing": {
- "enabled": true
- },
- "deleted": false,
- "block_private_relay": true,
- "devices": [
- {
- "id": "118ffe93",
- "name": "Device 1",
- "filtering_enabled": true
- },
- {
- "id": "b9e1a762",
- "name": "Device 2",
- "filtering_enabled": true
- }
- ],
- "rule_lists": {
- "enabled": true,
- "ids": [
- "1"
- ]
- },
- "filtered_response_ttl": 10,
- "custom_rules": []
- },
- {
- "dns_id": "83f3ea8f",
- "filtering_enabled": true,
- "query_log_enabled": true,
- "safe_browsing": {
- "enabled": true
- },
- "deleted": true,
- "block_private_relay": false,
- "block_firefox_canary": false,
- "devices": [
- {
- "id": "0d7724fa",
- "name": "Device 1",
- "filtering_enabled": true
- },
- {
- "id": "6d2ac775",
- "name": "Device 2",
- "linked_ip": null,
- "filtering_enabled": true
- },
- {
- "id": "94d4c481",
- "name": "Device 3",
- "linked_ip": "",
- "dedicated_ips": [
- "1.2.3.4"
- ],
- "filtering_enabled": true
- },
- {
- "id": "ada436e3",
- "name": "Device 4",
- "linked_ip": "1.2.3.4",
- "filtering_enabled": true
- }
- ],
- "parental": {
- "enabled": false,
- "block_adult": false,
- "general_safe_search": false,
- "youtube_safe_search": false,
- "blocked_services": [
- "youtube"
- ],
- "schedule": {
- "tmz": "GMT",
- "mon": [
- "0s",
- "59m"
- ],
- "tue": [
- "0s",
- "59m"
- ],
- "wed": [
- "0s",
- "59m"
- ],
- "thu": [
- "0s",
- "59m"
- ],
- "fri": [
- "0s",
- "59m"
- ]
- }
- },
- "rule_lists": {
- "enabled": true,
- "ids": [
- "1"
- ]
- },
- "filtered_response_ttl": 3600,
- "blocking_mode": {
- "type": "custom_ip",
- "ipv4": "1.2.3.4",
- "ipv6": "1234::cdef"
- },
- "custom_rules": [
- "||example.org^"
- ]
- }
- ]
-}
diff --git a/internal/backendpb/backend.pb.go b/internal/backendpb/backend.pb.go
index 33aaf0a..c1cdc32 100644
--- a/internal/backendpb/backend.pb.go
+++ b/internal/backendpb/backend.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
-// protoc v4.23.4
+// protoc v4.25.1
// source: backend.proto
package backendpb
@@ -94,6 +94,8 @@ type DNSProfile struct {
// *DNSProfile_BlockingModeNullIp
// *DNSProfile_BlockingModeRefused
BlockingMode isDNSProfile_BlockingMode `protobuf_oneof:"blocking_mode"`
+ IpLogEnabled bool `protobuf:"varint,17,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"`
+ Access *AccessSettings `protobuf:"bytes,18,opt,name=access,proto3" json:"access,omitempty"`
}
func (x *DNSProfile) Reset() {
@@ -247,6 +249,20 @@ func (x *DNSProfile) GetBlockingModeRefused() *BlockingModeREFUSED {
return nil
}
+func (x *DNSProfile) GetIpLogEnabled() bool {
+ if x != nil {
+ return x.IpLogEnabled
+ }
+ return false
+}
+
+func (x *DNSProfile) GetAccess() *AccessSettings {
+ if x != nil {
+ return x.Access
+ }
+ return nil
+}
+
type isDNSProfile_BlockingMode interface {
isDNSProfile_BlockingMode()
}
@@ -1021,6 +1037,148 @@ func (x *DeviceBillingStat) GetQueries() uint32 {
return 0
}
+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"`
+}
+
+func (x *AccessSettings) Reset() {
+ *x = AccessSettings{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_backend_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AccessSettings) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AccessSettings) ProtoMessage() {}
+
+func (x *AccessSettings) ProtoReflect() protoreflect.Message {
+ mi := &file_backend_proto_msgTypes[14]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AccessSettings.ProtoReflect.Descriptor instead.
+func (*AccessSettings) Descriptor() ([]byte, []int) {
+ return file_backend_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *AccessSettings) GetAllowlistCidr() []*CidrRange {
+ if x != nil {
+ return x.AllowlistCidr
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistCidr() []*CidrRange {
+ if x != nil {
+ return x.BlocklistCidr
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetAllowlistAsn() []uint32 {
+ if x != nil {
+ return x.AllowlistAsn
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistAsn() []uint32 {
+ if x != nil {
+ return x.BlocklistAsn
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistDomainRules() []string {
+ if x != nil {
+ return x.BlocklistDomainRules
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetEnabled() bool {
+ if x != nil {
+ return x.Enabled
+ }
+ return false
+}
+
+type CidrRange struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ 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"`
+}
+
+func (x *CidrRange) Reset() {
+ *x = CidrRange{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_backend_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CidrRange) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CidrRange) ProtoMessage() {}
+
+func (x *CidrRange) ProtoReflect() protoreflect.Message {
+ mi := &file_backend_proto_msgTypes[15]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CidrRange.ProtoReflect.Descriptor instead.
+func (*CidrRange) Descriptor() ([]byte, []int) {
+ return file_backend_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *CidrRange) GetAddress() []byte {
+ if x != nil {
+ return x.Address
+ }
+ return nil
+}
+
+func (x *CidrRange) GetPrefix() uint32 {
+ if x != nil {
+ return x.Prefix
+ }
+ return 0
+}
+
var File_backend_proto protoreflect.FileDescriptor
var file_backend_proto_rawDesc = []byte{
@@ -1035,7 +1193,7 @@ var file_backend_proto_rawDesc = []byte{
0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x18, 0x01, 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, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xf9, 0x06, 0x0a,
+ 0x6d, 0x70, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xc8, 0x07, 0x0a,
0x0a, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x64,
0x6e, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x6e, 0x73,
0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f,
@@ -1090,110 +1248,135 @@ var file_backend_proto_rawDesc = []byte{
0x75, 0x73, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44,
0x48, 0x00, 0x52, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65,
- 0x52, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
- 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66,
- 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
- 0x73, 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, 0x36, 0x0a, 0x17, 0x62,
- 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64,
- 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c,
- 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61,
- 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x72, 0x64,
- 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x72, 0x64,
- 0x22, 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69,
- 0x6e, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65,
- 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01,
- 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61,
- 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69,
- 0x70, 0x18, 0x04, 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, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61,
- 0x74, 0x65, 0x64, 0x49, 0x70, 0x73, 0x22, 0x87, 0x02, 0x0a, 0x10, 0x50, 0x61, 0x72, 0x65, 0x6e,
- 0x74, 0x61, 0x6c, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61,
- 0x64, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63,
- 0x6b, 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
- 0x6c, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, 0x20,
- 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65,
- 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62,
- 0x65, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x04, 0x20,
- 0x01, 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65,
- 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65,
- 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09,
- 0x52, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
- 0x73, 0x12, 0x2d, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x06, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65,
- 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
- 0x22, 0x54, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74,
- 0x69, 0x6e, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6d, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x03, 0x74, 0x6d, 0x7a, 0x12, 0x2e, 0x0a, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79,
- 0x52, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x57, 0x65,
- 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c,
- 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x0b, 0x57, 0x65, 0x65, 0x6b, 0x6c,
- 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03,
- 0x6d, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
- 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x75, 0x65,
- 0x12, 0x1b, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e,
- 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x77, 0x65, 0x64, 0x12, 0x1b, 0x0a,
- 0x03, 0x74, 0x68, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79,
- 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x1b, 0x0a, 0x03, 0x66, 0x72,
- 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e,
- 0x67, 0x65, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x06,
- 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52,
- 0x03, 0x73, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28,
- 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75,
- 0x6e, 0x22, 0x68, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2f, 0x0a,
- 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67,
- 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44,
- 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2b,
- 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,
- 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,
- 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3f, 0x0a, 0x11, 0x52,
- 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
+ 0x52, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x70, 0x5f, 0x6c, 0x6f,
+ 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x0c, 0x69, 0x70, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a,
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
+ 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x06,
+ 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69,
+ 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, 0x65,
+ 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
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, 0x10, 0x0a, 0x03, 0x69, 0x64,
- 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 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, 0x01,
- 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36,
- 0x18, 0x02, 0x20, 0x01, 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, 0xe3, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x69, 0x6c, 0x6c,
- 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f,
- 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 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,
- 0x10, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x69, 0x6d,
- 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25,
- 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79,
- 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
- 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x04,
- 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x61,
- 0x73, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x61, 0x73, 0x6e, 0x12, 0x18, 0x0a,
- 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
- 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x32, 0x8a, 0x01, 0x0a, 0x0a, 0x44, 0x4e, 0x53, 0x53,
- 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x67, 0x65, 0x74, 0x44, 0x4e, 0x53,
- 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x13, 0x2e, 0x44, 0x4e, 0x53, 0x50, 0x72,
- 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e,
- 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16,
- 0x73, 0x61, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x42, 0x69, 0x6c, 0x6c, 0x69,
- 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42,
- 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
- 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
- 0x74, 0x79, 0x28, 0x01, 0x42, 0x4a, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x67, 0x75,
- 0x61, 0x72, 0x64, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 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, 0x5a, 0x0b, 0x2e,
- 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x4e, 0x53,
- 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x62, 0x6c,
+ 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f,
+ 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, 0x6f,
+ 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69,
+ 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x72, 0x64, 0x18,
+ 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x72, 0x64, 0x22,
+ 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
+ 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
+ 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
+ 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62,
+ 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x70,
+ 0x18, 0x04, 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, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74,
+ 0x65, 0x64, 0x49, 0x70, 0x73, 0x22, 0x87, 0x02, 0x0a, 0x10, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74,
+ 0x61, 0x6c, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61, 0x64,
+ 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
+ 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
+ 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65, 0x53,
+ 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65,
+ 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01,
+ 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65, 0x53,
+ 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
+ 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52,
+ 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
+ 0x12, 0x2d, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74,
+ 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22,
+ 0x54, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69,
+ 0x6e, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6d, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x03, 0x74, 0x6d, 0x7a, 0x12, 0x2e, 0x0a, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52,
+ 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x57, 0x65, 0x65,
+ 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79,
+ 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x0b, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79,
+ 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x6d,
+ 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x75, 0x65, 0x12,
+ 0x1b, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44,
+ 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x77, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x03,
+ 0x74, 0x68, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52,
+ 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x1b, 0x0a, 0x03, 0x66, 0x72, 0x69,
+ 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67,
+ 0x65, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03,
+ 0x73, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75, 0x6e,
+ 0x22, 0x68, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x05,
+ 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,
+ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,
+ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2b, 0x0a,
+ 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f,
+ 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3f, 0x0a, 0x11, 0x52, 0x75,
+ 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73,
+ 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 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, 0x01, 0x28,
+ 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18,
+ 0x02, 0x20, 0x01, 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, 0xe3, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69,
+ 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x61,
+ 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 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, 0x10,
+ 0x6c, 0x61, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x69, 0x6d, 0x65,
+ 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a,
+ 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18,
+ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x75,
+ 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x04, 0x20,
+ 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x73,
+ 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x61, 0x73, 0x6e, 0x12, 0x18, 0x0a, 0x07,
+ 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x71,
+ 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0x90, 0x02, 0x0a, 0x0e, 0x41, 0x63, 0x63, 0x65, 0x73,
+ 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x31, 0x0a, 0x0e, 0x61, 0x6c, 0x6c,
+ 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x0a, 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, 0x31, 0x0a, 0x0e,
+ 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02,
+ 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 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,
+ 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x6e,
+ 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73,
+ 0x74, 0x41, 0x73, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73,
+ 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f,
+ 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 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, 0x05, 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, 0x12,
+ 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08,
+ 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 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, 0x32, 0x8a, 0x01, 0x0a, 0x0a, 0x44, 0x4e, 0x53,
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x67, 0x65, 0x74, 0x44, 0x4e,
+ 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x13, 0x2e, 0x44, 0x4e, 0x53, 0x50,
+ 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b,
+ 0x2e, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x30, 0x01, 0x12, 0x46, 0x0a,
+ 0x16, 0x73, 0x61, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x42, 0x69, 0x6c, 0x6c,
+ 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
+ 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
+ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
+ 0x70, 0x74, 0x79, 0x28, 0x01, 0x42, 0x3d, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x67,
+ 0x75, 0x61, 0x72, 0x64, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 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 (
@@ -1208,7 +1391,7 @@ func file_backend_proto_rawDescGZIP() []byte {
return file_backend_proto_rawDescData
}
-var file_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
+var file_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_backend_proto_goTypes = []interface{}{
(*DNSProfilesRequest)(nil), // 0: DNSProfilesRequest
(*DNSProfile)(nil), // 1: DNSProfile
@@ -1224,42 +1407,47 @@ var file_backend_proto_goTypes = []interface{}{
(*BlockingModeNullIP)(nil), // 11: BlockingModeNullIP
(*BlockingModeREFUSED)(nil), // 12: BlockingModeREFUSED
(*DeviceBillingStat)(nil), // 13: DeviceBillingStat
- (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp
- (*durationpb.Duration)(nil), // 15: google.protobuf.Duration
- (*emptypb.Empty)(nil), // 16: google.protobuf.Empty
+ (*AccessSettings)(nil), // 14: AccessSettings
+ (*CidrRange)(nil), // 15: CidrRange
+ (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp
+ (*durationpb.Duration)(nil), // 17: google.protobuf.Duration
+ (*emptypb.Empty)(nil), // 18: google.protobuf.Empty
}
var file_backend_proto_depIdxs = []int32{
- 14, // 0: DNSProfilesRequest.sync_time:type_name -> google.protobuf.Timestamp
+ 16, // 0: DNSProfilesRequest.sync_time:type_name -> google.protobuf.Timestamp
2, // 1: DNSProfile.safe_browsing:type_name -> SafeBrowsingSettings
4, // 2: DNSProfile.parental:type_name -> ParentalSettings
8, // 3: DNSProfile.rule_lists:type_name -> RuleListsSettings
3, // 4: DNSProfile.devices:type_name -> DeviceSettings
- 15, // 5: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
+ 17, // 5: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
9, // 6: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
10, // 7: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
11, // 8: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP
12, // 9: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED
- 5, // 10: ParentalSettings.schedule:type_name -> ScheduleSettings
- 6, // 11: ScheduleSettings.weeklyRange:type_name -> WeeklyRange
- 7, // 12: WeeklyRange.mon:type_name -> DayRange
- 7, // 13: WeeklyRange.tue:type_name -> DayRange
- 7, // 14: WeeklyRange.wed:type_name -> DayRange
- 7, // 15: WeeklyRange.thu:type_name -> DayRange
- 7, // 16: WeeklyRange.fri:type_name -> DayRange
- 7, // 17: WeeklyRange.sat:type_name -> DayRange
- 7, // 18: WeeklyRange.sun:type_name -> DayRange
- 15, // 19: DayRange.start:type_name -> google.protobuf.Duration
- 15, // 20: DayRange.end:type_name -> google.protobuf.Duration
- 14, // 21: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
- 0, // 22: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
- 13, // 23: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
- 1, // 24: DNSService.getDNSProfiles:output_type -> DNSProfile
- 16, // 25: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
- 24, // [24:26] is the sub-list for method output_type
- 22, // [22:24] is the sub-list for method input_type
- 22, // [22:22] is the sub-list for extension type_name
- 22, // [22:22] is the sub-list for extension extendee
- 0, // [0:22] is the sub-list for field type_name
+ 14, // 10: DNSProfile.access:type_name -> AccessSettings
+ 5, // 11: ParentalSettings.schedule:type_name -> ScheduleSettings
+ 6, // 12: ScheduleSettings.weeklyRange:type_name -> WeeklyRange
+ 7, // 13: WeeklyRange.mon:type_name -> DayRange
+ 7, // 14: WeeklyRange.tue:type_name -> DayRange
+ 7, // 15: WeeklyRange.wed:type_name -> DayRange
+ 7, // 16: WeeklyRange.thu:type_name -> DayRange
+ 7, // 17: WeeklyRange.fri:type_name -> DayRange
+ 7, // 18: WeeklyRange.sat:type_name -> DayRange
+ 7, // 19: WeeklyRange.sun:type_name -> DayRange
+ 17, // 20: DayRange.start:type_name -> google.protobuf.Duration
+ 17, // 21: DayRange.end:type_name -> google.protobuf.Duration
+ 16, // 22: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
+ 15, // 23: AccessSettings.allowlist_cidr:type_name -> CidrRange
+ 15, // 24: AccessSettings.blocklist_cidr:type_name -> CidrRange
+ 0, // 25: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
+ 13, // 26: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
+ 1, // 27: DNSService.getDNSProfiles:output_type -> DNSProfile
+ 18, // 28: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
+ 27, // [27:29] is the sub-list for method output_type
+ 25, // [25:27] is the sub-list for method input_type
+ 25, // [25:25] is the sub-list for extension type_name
+ 25, // [25:25] is the sub-list for extension extendee
+ 0, // [0:25] is the sub-list for field type_name
}
func init() { file_backend_proto_init() }
@@ -1436,6 +1624,30 @@ func file_backend_proto_init() {
return nil
}
}
+ file_backend_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AccessSettings); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_backend_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CidrRange); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
file_backend_proto_msgTypes[1].OneofWrappers = []interface{}{
(*DNSProfile_BlockingModeCustomIp)(nil),
@@ -1449,7 +1661,7 @@ func file_backend_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_backend_proto_rawDesc,
NumEnums: 0,
- NumMessages: 14,
+ NumMessages: 16,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/internal/backendpb/backend.proto b/internal/backendpb/backend.proto
index 9194342..c759b06 100644
--- a/internal/backendpb/backend.proto
+++ b/internal/backendpb/backend.proto
@@ -1,7 +1,5 @@
syntax = "proto3";
-option go_package = "./backendpb";
-
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
@@ -25,7 +23,7 @@ service DNSService {
/*
Stores devices activity.
- */
+ */
rpc saveDevicesBillingStat(stream DeviceBillingStat) returns (google.protobuf.Empty);
}
@@ -52,6 +50,8 @@ message DNSProfile {
BlockingModeNullIP blocking_mode_null_ip = 15;
BlockingModeREFUSED blocking_mode_refused = 16;
}
+ bool ip_log_enabled = 17;
+ AccessSettings access = 18;
}
message SafeBrowsingSettings {
@@ -122,3 +122,17 @@ message DeviceBillingStat {
uint32 asn = 5;
uint32 queries = 6;
}
+
+message AccessSettings {
+ repeated CidrRange allowlist_cidr = 1;
+ repeated CidrRange blocklist_cidr = 2;
+ repeated uint32 allowlist_asn = 3;
+ repeated uint32 blocklist_asn = 4;
+ repeated string blocklist_domain_rules = 5;
+ bool enabled = 6;
+}
+
+message CidrRange {
+ bytes address = 1;
+ uint32 prefix = 2;
+}
diff --git a/internal/backendpb/backend_grpc.pb.go b/internal/backendpb/backend_grpc.pb.go
index 88853ae..0a249aa 100644
--- a/internal/backendpb/backend_grpc.pb.go
+++ b/internal/backendpb/backend_grpc.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
-// - protoc v4.23.4
+// - protoc v4.25.1
// source: backend.proto
package backendpb
diff --git a/internal/backendpb/backendpb.go b/internal/backendpb/backendpb.go
index 3bda61f..9c98cea 100644
--- a/internal/backendpb/backendpb.go
+++ b/internal/backendpb/backendpb.go
@@ -6,7 +6,7 @@ import (
"fmt"
"net/url"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -38,6 +38,6 @@ func newClient(apiURL *url.URL) (client DNSServiceClient, err error) {
}
// reportf is a helper method for reporting non-critical errors.
-func reportf(ctx context.Context, errColl agd.ErrorCollector, format string, args ...any) {
- agd.Collectf(ctx, errColl, "backendpb: "+format, args...)
+func reportf(ctx context.Context, errColl errcoll.Interface, format string, args ...any) {
+ errcoll.Collectf(ctx, errColl, "backendpb: "+format, args...)
}
diff --git a/internal/backendpb/billstat.go b/internal/backendpb/billstat.go
index afe7060..88722fb 100644
--- a/internal/backendpb/billstat.go
+++ b/internal/backendpb/billstat.go
@@ -8,6 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -17,7 +18,7 @@ import (
type BillStatConfig struct {
// ErrColl is the error collector that is used to collect critical and
// non-critical errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs".
@@ -46,7 +47,7 @@ func NewBillStat(c *BillStatConfig) (b *BillStat, err error) {
// TODO(a.garipov): Consider uniting with [ProfileStorage] into a single
// backendpb.Client.
type BillStat struct {
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
// client is the current GRPC client.
client DNSServiceClient
diff --git a/internal/backendpb/billstat_test.go b/internal/backendpb/billstat_test.go
index a4576da..db0988e 100644
--- a/internal/backendpb/billstat_test.go
+++ b/internal/backendpb/billstat_test.go
@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
@@ -30,7 +31,7 @@ func TestBillStat_Upload(t *testing.T) {
wantRecord := &billstat.Record{
Time: time.Time{},
- Country: agd.CountryCY,
+ Country: geoip.CountryCY,
ASN: 1221,
Queries: 1122,
Proto: agd.ProtoDNS,
diff --git a/internal/backendpb/profiledb.go b/internal/backendpb/profiledb.go
index ea157d8..8318997 100644
--- a/internal/backendpb/profiledb.go
+++ b/internal/backendpb/profiledb.go
@@ -9,13 +9,18 @@ import (
"strconv"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors"
+ "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -24,7 +29,7 @@ import (
type ProfileStorageConfig struct {
// ErrColl is the error collector that is used to collect critical and
// non-critical errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs".
@@ -35,7 +40,7 @@ type ProfileStorageConfig struct {
// that retrieves the profile and device information from the business logic
// backend. It is safe for concurrent use.
type ProfileStorage struct {
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
// client is the current GRPC client.
client DNSServiceClient
@@ -66,7 +71,7 @@ func (s *ProfileStorage) Profiles(
) (resp *profiledb.StorageResponse, err error) {
stream, err := s.client.GetDNSProfiles(ctx, toProtobuf(req))
if err != nil {
- return nil, fmt.Errorf("loading profiles: %w", err)
+ return nil, fmt.Errorf("loading profiles: %w", fixGRPCError(err))
}
defer func() { err = errors.WithDeferred(err, stream.CloseSend()) }()
@@ -76,7 +81,7 @@ func (s *ProfileStorage) Profiles(
}
stats := &profilesCallStats{
- isFullSync: req.SyncTime == time.Time{},
+ isFullSync: req.SyncTime.IsZero(),
}
for {
@@ -87,7 +92,7 @@ func (s *ProfileStorage) Profiles(
break
}
- return nil, fmt.Errorf("receiving profile: %w", profErr)
+ return nil, fmt.Errorf("receiving profile: %w", fixGRPCError(profErr))
}
stats.endRecv()
@@ -115,11 +120,27 @@ func (s *ProfileStorage) Profiles(
return resp, nil
}
+// fixGRPCError wraps GRPC error if needed. As the GRPC deadline error is not
+// correctly wrapped, this helper detects it by the status code and replaces it
+// with a simple DeadlineExceeded error.
+//
+// See https://github.com/grpc/grpc-go/issues/4822.
+//
+// TODO(d.kolyshev): Remove after the grpc-go issue is fixed.
+func fixGRPCError(err error) (wErr error) {
+ st, ok := status.FromError(err)
+ if ok && st.Code() == codes.DeadlineExceeded {
+ err = fmt.Errorf("grpc: %w", context.DeadlineExceeded)
+ }
+
+ return err
+}
+
// toInternal converts the protobuf-encoded data into a profile structure.
func (x *DNSProfile) toInternal(
ctx context.Context,
updTime time.Time,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (profile *agd.Profile, devices []*agd.Device, err error) {
if x == nil {
return nil, nil, fmt.Errorf("profile is nil")
@@ -159,11 +180,13 @@ func (x *DNSProfile) toInternal(
FilteredResponseTTL: fltRespTTL,
FilteringEnabled: x.FilteringEnabled,
SafeBrowsing: x.SafeBrowsing.toInternal(),
+ Access: x.Access.toInternal(ctx, errColl),
RuleListsEnabled: listsEnabled,
QueryLogEnabled: x.QueryLogEnabled,
Deleted: x.Deleted,
BlockPrivateRelay: x.BlockPrivateRelay,
BlockFirefoxCanary: x.BlockFirefoxCanary,
+ IPLogEnabled: x.IpLogEnabled,
}, devices, nil
}
@@ -171,7 +194,7 @@ func (x *DNSProfile) toInternal(
// one. If x is nil, toInternal returns nil.
func (x *ParentalSettings) toInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (s *agd.ParentalProtectionSettings, err error) {
if x == nil {
return nil, nil
@@ -206,11 +229,61 @@ func (x *SafeBrowsingSettings) toInternal() (sb *agd.SafeBrowsingSettings) {
}
}
+// toInternal converts protobuf access settings to an internal structure. If x
+// is nil, toInternal returns [access.EmptyProfile].
+func (x *AccessSettings) toInternal(
+ ctx context.Context,
+ errColl errcoll.Interface,
+) (a access.Profile) {
+ if x == nil || !x.Enabled {
+ return access.EmptyProfile{}
+ }
+
+ return access.NewDefaultProfile(&access.ProfileConfig{
+ AllowedNets: cidrRangeToInternal(ctx, errColl, x.AllowlistCidr),
+ BlockedNets: cidrRangeToInternal(ctx, errColl, x.BlocklistCidr),
+ AllowedASN: asnToInternal(x.AllowlistAsn),
+ BlockedASN: asnToInternal(x.BlocklistAsn),
+ BlocklistDomainRules: x.BlocklistDomainRules,
+ })
+}
+
+// cidrRangeToInternal is a helper that converts a slice of CidrRange to the
+// slice of [netip.Prefix].
+func cidrRangeToInternal(
+ ctx context.Context,
+ errColl errcoll.Interface,
+ cidrs []*CidrRange,
+) (out []netip.Prefix) {
+ for i, c := range cidrs {
+ addr, ok := netip.AddrFromSlice(c.Address)
+ if !ok {
+ reportf(ctx, errColl, "invalid cidr at index %d: %w", i)
+
+ continue
+ }
+
+ out = append(out, netip.PrefixFrom(addr, int(c.Prefix)))
+ }
+
+ return out
+}
+
+// asnToInternal is a helper that converts a slice of ASNs to the slice of
+// [geoip.ASN].
+func asnToInternal(asns []uint32) (out []geoip.ASN) {
+ for _, asn := range asns {
+ out = append(out, geoip.ASN(asn))
+ }
+
+ return out
+}
+
// blockedSvcsToInternal is a helper that converts the blocked service IDs from
// the backend response to AdGuard DNS blocked service IDs.
func blockedSvcsToInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
respSvcs []string,
) (svcs []agd.BlockedServiceID) {
l := len(respSvcs)
@@ -277,35 +350,33 @@ func (x *ScheduleSettings) toInternal() (sch *agd.ParentalProtectionSchedule, er
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
// internal one. If pbm is nil, blockingModeToInternal returns a null-IP
// blocking mode.
-func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingModeCodec, err error) {
+func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
switch pbm := pbm.(type) {
case nil:
- m.Mode = &dnsmsg.BlockingModeNullIP{}
+ return &dnsmsg.BlockingModeNullIP{}, nil
case *DNSProfile_BlockingModeCustomIp:
custom := &dnsmsg.BlockingModeCustomIP{}
err = custom.IPv4.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv4)
if err != nil {
- return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv4: %w", err)
+ return nil, fmt.Errorf("bad custom ipv4: %w", err)
}
err = custom.IPv6.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv6)
if err != nil {
- return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv6: %w", err)
+ return nil, fmt.Errorf("bad custom ipv6: %w", err)
}
- m.Mode = custom
+ return custom, nil
case *DNSProfile_BlockingModeNxdomain:
- m.Mode = &dnsmsg.BlockingModeNXDOMAIN{}
+ return &dnsmsg.BlockingModeNXDOMAIN{}, nil
case *DNSProfile_BlockingModeNullIp:
- m.Mode = &dnsmsg.BlockingModeNullIP{}
+ return &dnsmsg.BlockingModeNullIP{}, nil
case *DNSProfile_BlockingModeRefused:
- m.Mode = &dnsmsg.BlockingModeREFUSED{}
+ return &dnsmsg.BlockingModeREFUSED{}, nil
default:
// Consider unhandled type-switch cases programmer errors.
- panic(fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm))
+ return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
}
-
- return m, nil
}
// devicesToInternal is a helper that converts the devices from protobuf to
@@ -313,7 +384,7 @@ func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingMod
func devicesToInternal(
ctx context.Context,
ds []*DeviceSettings,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (out []*agd.Device, ids []agd.DeviceID) {
l := len(ds)
if l == 0 {
@@ -379,7 +450,7 @@ func (ds *DeviceSettings) toInternal() (dev *agd.Device, err error) {
func rulesToInternal(
ctx context.Context,
respRules []string,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (rules []agd.FilterRuleText) {
l := len(respRules)
if l == 0 {
@@ -406,7 +477,7 @@ func rulesToInternal(
// false and nil.
func (x *RuleListsSettings) toInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (enabled bool, filterLists []agd.FilterListID) {
if x == nil {
return false, nil
diff --git a/internal/backendpb/profiledb_internal_test.go b/internal/backendpb/profiledb_internal_test.go
index d1e74b1..f9e1cd7 100644
--- a/internal/backendpb/profiledb_internal_test.go
+++ b/internal/backendpb/profiledb_internal_test.go
@@ -7,10 +7,12 @@ import (
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -60,13 +62,7 @@ func TestDNSProfile_ToInternal(t *testing.T) {
require.NoError(t, err)
require.Error(t, errCollErr)
- // See the TODO in [blockingModeToInternal].
- wantProf := newProfile(t)
- wantProf.BlockingMode = dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- }
-
- assert.Equal(t, wantProf, got)
+ assert.Equal(t, newProfile(t), got)
assert.Equal(t, newDevices(t), gotDevices)
})
@@ -127,31 +123,55 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(ctx, TestUpdTime, errColl)
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv6: unexpected slice size", err)
})
+
+ t.Run("nil_blocking_mode", func(t *testing.T) {
+ dp := NewTestDNSProfile(t)
+ dp.BlockingMode = nil
+
+ got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, errColl)
+ require.NoError(t, err)
+ require.NotNil(t, got)
+
+ wantProf := newProfile(t)
+ wantProf.BlockingMode = &dnsmsg.BlockingModeNullIP{}
+
+ assert.Equal(t, wantProf, got)
+ assert.Equal(t, newDevices(t), gotDevices)
+ })
+
+ t.Run("nil_access", func(t *testing.T) {
+ dp := NewTestDNSProfile(t)
+ dp.Access = nil
+
+ got, _, err := dp.toInternal(ctx, TestUpdTime, errColl)
+ require.NoError(t, err)
+ require.NotNil(t, got)
+
+ assert.Equal(t, got.ID, testProfileID)
+ assert.IsType(t, access.EmptyProfile{}, got.Access)
+ })
+
+ t.Run("access_disabled", func(t *testing.T) {
+ dp := NewTestDNSProfile(t)
+ dp.Access = &AccessSettings{
+ Enabled: false,
+ }
+
+ got, _, err := dp.toInternal(ctx, TestUpdTime, errColl)
+ require.NoError(t, err)
+ require.NotNil(t, got)
+
+ assert.Equal(t, got.ID, testProfileID)
+ assert.IsType(t, access.EmptyProfile{}, got.Access)
+ })
}
-// newDNSProfileWithBadData returns a new instance of *DNSProfile with bad data
-// for tests.
+// newDNSProfileWithBadData returns a new instance of *DNSProfile with bad
+// devices data for tests.
func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
tb.Helper()
- dayRange := &DayRange{
- Start: durationpb.New(0),
- End: durationpb.New(59 * time.Minute),
- }
-
- devices := []*DeviceSettings{{
- Id: "118ffe93",
- Name: "118ffe93-name",
- FilteringEnabled: false,
- LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
- DedicatedIps: [][]byte{ipToBytes(tb, netip.MustParseAddr("1.1.1.2"))},
- }, {
- Id: "b9e1a762",
- Name: "b9e1a762-name",
- FilteringEnabled: true,
- LinkedIp: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")),
- DedicatedIps: nil,
- }, {
+ invalidDevices := []*DeviceSettings{{
Id: "invalid-too-long-device-id",
Name: "device_name",
FilteringEnabled: true,
@@ -179,45 +199,10 @@ func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
DedicatedIps: [][]byte{[]byte("1")},
}}
- return &DNSProfile{
- DnsId: string(testProfileID),
- FilteringEnabled: true,
- QueryLogEnabled: true,
- Deleted: false,
- SafeBrowsing: &SafeBrowsingSettings{
- Enabled: true,
- BlockDangerousDomains: true,
- BlockNrd: false,
- },
- Parental: &ParentalSettings{
- Enabled: false,
- BlockAdult: false,
- GeneralSafeSearch: false,
- YoutubeSafeSearch: false,
- BlockedServices: []string{"youtube", "inv_blocked_svc\r"},
- Schedule: &ScheduleSettings{
- Tmz: "GMT",
- WeeklyRange: &WeeklyRange{
- Sun: nil,
- Mon: dayRange,
- Tue: dayRange,
- Wed: dayRange,
- Thu: dayRange,
- Fri: dayRange,
- Sat: nil,
- },
- },
- },
- RuleLists: &RuleListsSettings{
- Enabled: true,
- Ids: []string{"1", "inv_filter_id\r"},
- },
- Devices: devices,
- CustomRules: []string{"||example.org^"},
- FilteredResponseTtl: durationpb.New(10 * time.Second),
- BlockPrivateRelay: true,
- BlockFirefoxCanary: true,
- }
+ dp = NewTestDNSProfile(tb)
+ dp.Devices = append(dp.Devices, invalidDevices...)
+
+ return dp
}
// NewTestDNSProfile returns a new instance of *DNSProfile for tests.
@@ -281,12 +266,27 @@ func NewTestDNSProfile(tb testing.TB) (dp *DNSProfile) {
FilteredResponseTtl: durationpb.New(10 * time.Second),
BlockPrivateRelay: true,
BlockFirefoxCanary: true,
+ IpLogEnabled: true,
BlockingMode: &DNSProfile_BlockingModeCustomIp{
BlockingModeCustomIp: &BlockingModeCustomIP{
Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")),
Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")),
},
},
+ Access: &AccessSettings{
+ AllowlistCidr: []*CidrRange{{
+ Address: netip.MustParseAddr("1.1.1.0").AsSlice(),
+ Prefix: 24,
+ }},
+ BlocklistCidr: []*CidrRange{{
+ Address: netip.MustParseAddr("2.2.2.0").AsSlice(),
+ Prefix: 24,
+ }},
+ AllowlistAsn: []uint32{1},
+ BlocklistAsn: []uint32{2},
+ BlocklistDomainRules: []string{"block.test"},
+ Enabled: true,
+ },
}
}
@@ -328,13 +328,19 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
BlockNewlyRegisteredDomains: false,
}
- wantBlockingMode := dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeCustomIP{
- IPv4: netip.MustParseAddr("1.2.3.4"),
- IPv6: netip.MustParseAddr("1234::cdef"),
- },
+ wantBlockingMode := &dnsmsg.BlockingModeCustomIP{
+ IPv4: netip.MustParseAddr("1.2.3.4"),
+ IPv6: netip.MustParseAddr("1234::cdef"),
}
+ wantAccess := access.NewDefaultProfile(&access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{2},
+ BlocklistDomainRules: []string{"block.test"},
+ })
+
return &agd.Profile{
Parental: wantParental,
BlockingMode: wantBlockingMode,
@@ -348,12 +354,14 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
CustomRules: []agd.FilterRuleText{"||example.org^"},
FilteredResponseTTL: 10 * time.Second,
SafeBrowsing: wantSafeBrowsing,
+ Access: wantAccess,
RuleListsEnabled: true,
FilteringEnabled: true,
QueryLogEnabled: true,
Deleted: false,
BlockPrivateRelay: true,
BlockFirefoxCanary: true,
+ IPLogEnabled: true,
}
}
@@ -390,30 +398,30 @@ func TestSyncTimeFromTrailer(t *testing.T) {
milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10)
testCases := []struct {
+ in metadata.MD
wantError string
want time.Time
name string
- in metadata.MD
}{{
+ in: metadata.MD{},
wantError: "empty value",
want: time.Time{},
name: "no_key",
- in: metadata.MD{},
}, {
+ in: metadata.MD{"sync_time": []string{}},
wantError: "empty value",
want: time.Time{},
name: "empty_key",
- in: metadata.MD{"sync_time": []string{}},
}, {
+ in: metadata.MD{"sync_time": []string{""}},
wantError: `invalid value: strconv.ParseInt: parsing "": invalid syntax`,
want: time.Time{},
name: "empty_value",
- in: metadata.MD{"sync_time": []string{""}},
}, {
+ in: metadata.MD{"sync_time": []string{milliseconds}},
wantError: "",
want: TestUpdTime,
name: "success",
- in: metadata.MD{"sync_time": []string{milliseconds}},
}}
for _, tc := range testCases {
diff --git a/internal/billstat/billstat.go b/internal/billstat/billstat.go
index b2d6c98..ebf4355 100644
--- a/internal/billstat/billstat.go
+++ b/internal/billstat/billstat.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
)
// Common Constants, Types, And Utilities
@@ -15,8 +16,8 @@ type Recorder interface {
Record(
ctx context.Context,
id agd.DeviceID,
- ctry agd.Country,
- asn agd.ASN,
+ ctry geoip.Country,
+ asn geoip.ASN,
start time.Time,
proto agd.Protocol,
)
@@ -32,8 +33,8 @@ type EmptyRecorder struct{}
func (EmptyRecorder) Record(
_ context.Context,
_ agd.DeviceID,
- _ agd.Country,
- _ agd.ASN,
+ _ geoip.Country,
+ _ geoip.ASN,
_ time.Time,
_ agd.Protocol,
) {
@@ -51,10 +52,10 @@ type Record struct {
Time time.Time
// Country is the detected country of the client's IP address, if any.
- Country agd.Country
+ Country geoip.Country
// ASN is the detected ASN of the client's IP address, if any.
- ASN agd.ASN
+ ASN geoip.ASN
// Queries is the total number of Queries the device has performed since the
// most recent sync. This value is an int32 to be in sync with the business
diff --git a/internal/billstat/runtime.go b/internal/billstat/runtime.go
index a10349c..4901a28 100644
--- a/internal/billstat/runtime.go
+++ b/internal/billstat/runtime.go
@@ -7,6 +7,8 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/log"
)
@@ -51,8 +53,8 @@ var _ Recorder = (*RuntimeRecorder)(nil)
func (r *RuntimeRecorder) Record(
ctx context.Context,
id agd.DeviceID,
- ctry agd.Country,
- asn agd.ASN,
+ ctry geoip.Country,
+ asn geoip.ASN,
start time.Time,
proto agd.Protocol,
) {
@@ -80,10 +82,10 @@ func (r *RuntimeRecorder) Record(
}
// type check
-var _ agd.Refresher = (*RuntimeRecorder)(nil)
+var _ agdservice.Refresher = (*RuntimeRecorder)(nil)
-// Refresh implements the agd.Refresher interface for *RuntimeRecorder. It
-// uploads the currently available data and resets it.
+// Refresh implements the [agdserivce.Refresher] interface for *RuntimeRecorder.
+// It uploads the currently available data and resets it.
func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) {
records := r.resetRecords()
diff --git a/internal/billstat/runtime_test.go b/internal/billstat/runtime_test.go
index 3c90fc5..ea8b8d0 100644
--- a/internal/billstat/runtime_test.go
+++ b/internal/billstat/runtime_test.go
@@ -8,6 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
@@ -20,10 +21,10 @@ type sig = struct{}
// Common constants for tests.
const (
- devID = "dev1234"
- proto = agd.ProtoDoH
- clientCtry = agd.CountryAD
- clientASN agd.ASN = 42
+ devID = "dev1234"
+ proto = agd.ProtoDoH
+ clientCtry = geoip.CountryAD
+ clientASN geoip.ASN = 42
)
func TestRuntimeRecorder_success(t *testing.T) {
diff --git a/internal/bindtodevice/bindtodevice_internal_test.go b/internal/bindtodevice/bindtodevice_internal_test.go
index d79c0b3..2afd413 100644
--- a/internal/bindtodevice/bindtodevice_internal_test.go
+++ b/internal/bindtodevice/bindtodevice_internal_test.go
@@ -6,7 +6,7 @@ import (
"time"
)
-// Common timeout for tests
+// testTimeout is a common timeout for tests.
const testTimeout = 1 * time.Second
// Common addresses for tests.
@@ -24,5 +24,4 @@ var (
// Common subnets for tests.
var (
testSubnetIPv4 = netip.MustParsePrefix("1.2.3.0/24")
- testSubnetIPv6 = netip.MustParsePrefix("1234:5678::/64")
)
diff --git a/internal/bindtodevice/bindtodevice_test.go b/internal/bindtodevice/bindtodevice_test.go
index f138b03..b2479e5 100644
--- a/internal/bindtodevice/bindtodevice_test.go
+++ b/internal/bindtodevice/bindtodevice_test.go
@@ -3,6 +3,7 @@ package bindtodevice_test
import (
"net/netip"
"testing"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/golibs/testutil"
@@ -12,6 +13,9 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
+// testTimeout is a common timeout for tests.
+const testTimeout = 1 * time.Second
+
// Common interface listener IDs for tests
const (
testID1 bindtodevice.ID = "id1"
diff --git a/internal/bindtodevice/connindex_linux.go b/internal/bindtodevice/connindex_linux.go
index 68ee8f3..70614a3 100644
--- a/internal/bindtodevice/connindex_linux.go
+++ b/internal/bindtodevice/connindex_linux.go
@@ -5,8 +5,7 @@ package bindtodevice
import (
"fmt"
"net/netip"
-
- "golang.org/x/exp/slices"
+ "slices"
)
// connIndex is the data structure that contains the channel listeners and
diff --git a/internal/bindtodevice/connindex_linux_internal_test.go b/internal/bindtodevice/connindex_linux_internal_test.go
index 2d9245b..35c101e 100644
--- a/internal/bindtodevice/connindex_linux_internal_test.go
+++ b/internal/bindtodevice/connindex_linux_internal_test.go
@@ -4,10 +4,10 @@ package bindtodevice
import (
"net/netip"
+ "slices"
"testing"
"github.com/stretchr/testify/assert"
- "golang.org/x/exp/slices"
)
func TestSubnetCompare(t *testing.T) {
diff --git a/internal/bindtodevice/interfacelistener_linux.go b/internal/bindtodevice/interfacelistener_linux.go
index d75eda0..ad57993 100644
--- a/internal/bindtodevice/interfacelistener_linux.go
+++ b/internal/bindtodevice/interfacelistener_linux.go
@@ -8,24 +8,24 @@ import (
"net"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optlog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/syncutil"
)
// interfaceListener contains information about a single interface listener.
type interfaceListener struct {
conns *connIndex
listenConf *net.ListenConfig
- bodyPool *agdsync.TypedPool[[]byte]
- oobPool *agdsync.TypedPool[[]byte]
+ bodyPool *syncutil.Pool[[]byte]
+ oobPool *syncutil.Pool[[]byte]
writeRequests chan *packetConnWriteReq
done chan unit
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
ifaceName string
port uint16
}
@@ -37,7 +37,7 @@ func (l *interfaceListener) listenTCP(errCh chan<- error) {
defer log.OnPanic("interfaceListener.listenTCP")
ctx := context.Background()
- addrStr := netutil.JoinHostPort("0.0.0.0", int(l.port))
+ addrStr := netutil.JoinHostPort("0.0.0.0", l.port)
tcpListener, err := l.listenConf.Listen(ctx, "tcp", addrStr)
errCh <- err
@@ -62,7 +62,7 @@ func (l *interfaceListener) listenTCP(errCh chan<- error) {
var conn net.Conn
conn, err = tcpListener.Accept()
if err != nil {
- agd.Collectf(ctx, l.errColl, "%s: accepting: %w", logPrefix, err)
+ errcoll.Collectf(ctx, l.errColl, "%s: accepting: %w", logPrefix, err)
continue
}
@@ -78,7 +78,7 @@ func (l *interfaceListener) processConn(conn net.Conn, logPrefix string) {
raddr := conn.RemoteAddr()
if lsnr := l.conns.listener(laddr.Addr()); lsnr != nil {
if !lsnr.send(conn) {
- log.Info("%s: from raddr %s: channel for laddr %s is closed", logPrefix, raddr, laddr)
+ optlog.Debug3("%s: from raddr %s: channel for laddr %s is closed", logPrefix, raddr, laddr)
}
return
@@ -90,7 +90,7 @@ func (l *interfaceListener) processConn(conn net.Conn, logPrefix string) {
err := conn.Close()
if err != nil {
- log.Debug("%s: from raddr %s: closing: %s", logPrefix, raddr, err)
+ optlog.Debug3("%s: from raddr %s: closing: %s", logPrefix, raddr, err)
}
}
@@ -101,7 +101,7 @@ func (l *interfaceListener) listenUDP(errCh chan<- error) {
defer log.OnPanic("interfaceListener.listenUDP")
ctx := context.Background()
- addrStr := netutil.JoinHostPort("0.0.0.0", int(l.port))
+ addrStr := netutil.JoinHostPort("0.0.0.0", l.port)
packetConn, err := l.listenConf.ListenPacket(ctx, "udp", addrStr)
if err != nil {
errCh <- err
@@ -131,7 +131,7 @@ func (l *interfaceListener) listenUDP(errCh chan<- error) {
err = l.readUDP(udpConn, logPrefix)
if err != nil {
- agd.Collectf(ctx, l.errColl, "%s: reading session: %w", logPrefix, err)
+ errcoll.Collectf(ctx, l.errColl, "%s: reading session: %w", logPrefix, err)
}
}
}
@@ -179,7 +179,7 @@ func (l *interfaceListener) readUDP(c *net.UDPConn, logPrefix string) (err error
}
if !chanPacketConn.send(sess) {
- log.Info("%s: channel for laddr %s is closed", logPrefix, laddr)
+ optlog.Debug2("%s: channel for laddr %s is closed", logPrefix, laddr)
}
return nil
@@ -194,7 +194,7 @@ func (l *interfaceListener) writeUDP(c *net.UDPConn) {
var req *packetConnWriteReq
select {
case <-l.done:
- log.Info("%s: done", logPrefix)
+ optlog.Debug1("%s: done", logPrefix)
return
case req = <-l.writeRequests:
diff --git a/internal/bindtodevice/listenconfig_linux.go b/internal/bindtodevice/listenconfig_linux.go
index 7e8e4db..6ee517a 100644
--- a/internal/bindtodevice/listenconfig_linux.go
+++ b/internal/bindtodevice/listenconfig_linux.go
@@ -6,6 +6,7 @@ import (
"context"
"net"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
)
@@ -19,7 +20,7 @@ import (
type ListenConfig struct {
packetConn *chanPacketConn
listener *chanListener
- addr string
+ addr *agdnet.PrefixNetAddr
}
// type check
@@ -44,8 +45,7 @@ func (lc *ListenConfig) ListenPacket(
return lc.packetConn, nil
}
-// Addr returns the address on which lc accepts connections. See
-// [agdnet.FormatPrefixAddr] for the format.
-func (lc *ListenConfig) Addr() (addr string) {
+// Addr returns the address on which lc accepts connections. addr.Net is empty.
+func (lc *ListenConfig) Addr() (addr *agdnet.PrefixNetAddr) {
return lc.addr
}
diff --git a/internal/bindtodevice/listenconfig_linux_internal_test.go b/internal/bindtodevice/listenconfig_linux_internal_test.go
index 2022f35..5a33bbe 100644
--- a/internal/bindtodevice/listenconfig_linux_internal_test.go
+++ b/internal/bindtodevice/listenconfig_linux_internal_test.go
@@ -14,7 +14,11 @@ import (
func TestListenConfig(t *testing.T) {
pc := newChanPacketConn(nil, testSubnetIPv4, nil, testLAddr)
lsnr := newChanListener(nil, testSubnetIPv4, testLAddr)
- addr := agdnet.FormatPrefixAddr(testSubnetIPv4, 1234)
+ addr := &agdnet.PrefixNetAddr{
+ Prefix: testSubnetIPv4,
+ Net: "",
+ Port: 1234,
+ }
c := &ListenConfig{
packetConn: pc,
listener: lsnr,
diff --git a/internal/bindtodevice/listenconfig_others.go b/internal/bindtodevice/listenconfig_others.go
index 99286eb..28140e5 100644
--- a/internal/bindtodevice/listenconfig_others.go
+++ b/internal/bindtodevice/listenconfig_others.go
@@ -4,9 +4,12 @@ package bindtodevice
import (
"context"
+ "fmt"
"net"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/errors"
)
// ListenConfig is a [netext.ListenConfig] implementation that uses the
@@ -31,7 +34,10 @@ func (lc *ListenConfig) Listen(
network string,
address string,
) (l net.Listener, err error) {
- return nil, errUnsupported
+ return nil, fmt.Errorf(
+ "bindtodevice: listen: %w; only supported on linux",
+ errors.ErrUnsupported,
+ )
}
// ListenPacket implements the [netext.ListenConfig] interface for
@@ -43,13 +49,15 @@ func (lc *ListenConfig) ListenPacket(
network string,
address string,
) (c net.PacketConn, err error) {
- return nil, errUnsupported
+ return nil, fmt.Errorf(
+ "bindtodevice: listenpacket: %w; only supported on linux",
+ errors.ErrUnsupported,
+ )
}
-// Addr returns the address on which lc accepts connections. See
-// [agdnet.FormatPrefixAddr] for the format.
+// Addr returns the address on which lc accepts connections.
//
// It is only supported on Linux.
-func (lc *ListenConfig) Addr() (addr string) {
- return ""
+func (lc *ListenConfig) Addr() (addr *agdnet.PrefixNetAddr) {
+ return nil
}
diff --git a/internal/bindtodevice/manager.go b/internal/bindtodevice/manager.go
index 598ab39..f09edaf 100644
--- a/internal/bindtodevice/manager.go
+++ b/internal/bindtodevice/manager.go
@@ -1,6 +1,6 @@
package bindtodevice
-import "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+import "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
// ManagerConfig is the configuration structure for [NewManager]. All fields
// must be set.
@@ -11,7 +11,7 @@ type ManagerConfig struct {
// ErrColl is the error collector that is used to collect non-critical
// errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// ChannelBufferSize is the size of the buffers of the channels used to
// dispatch TCP connections and UDP sessions.
diff --git a/internal/bindtodevice/manager_linux.go b/internal/bindtodevice/manager_linux.go
index 7a99db9..8abac5d 100644
--- a/internal/bindtodevice/manager_linux.go
+++ b/internal/bindtodevice/manager_linux.go
@@ -9,13 +9,14 @@ import (
"net/netip"
"sync"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mapsutil"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
@@ -24,7 +25,7 @@ type Manager struct {
interfaces InterfaceStorage
closeOnce *sync.Once
ifaceListeners map[ID]*interfaceListener
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
done chan unit
chanBufSize int
}
@@ -108,18 +109,10 @@ func (m *Manager) newInterfaceListener(
port uint16,
) (l *interfaceListener) {
return &interfaceListener{
- conns: &connIndex{},
- listenConf: newListenConfig(ifaceName, ctrlConf),
- bodyPool: agdsync.NewTypedPool(func() (v *[]byte) {
- b := make([]byte, bodySize)
-
- return &b
- }),
- oobPool: agdsync.NewTypedPool(func() (v *[]byte) {
- b := make([]byte, netext.IPDstOOBSize)
-
- return &b
- }),
+ conns: &connIndex{},
+ listenConf: newListenConfig(ifaceName, ctrlConf),
+ bodyPool: syncutil.NewSlicePool[byte](bodySize),
+ oobPool: syncutil.NewSlicePool[byte](netext.IPDstOOBSize),
writeRequests: make(chan *packetConnWriteReq, m.chanBufSize),
done: m.done,
errColl: m.errColl,
@@ -154,10 +147,10 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
}
lsnrCh := make(chan net.Conn, m.chanBufSize)
- lsnr := newChanListener(lsnrCh, subnet, &prefixNetAddr{
- prefix: subnet,
- network: "tcp",
- port: l.port,
+ lsnr := newChanListener(lsnrCh, subnet, &agdnet.PrefixNetAddr{
+ Prefix: subnet,
+ Net: "tcp",
+ Port: l.port,
})
err = l.conns.addListener(lsnr)
@@ -166,10 +159,10 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
}
sessCh := make(chan *packetSession, m.chanBufSize)
- pConn := newChanPacketConn(sessCh, subnet, l.writeRequests, &prefixNetAddr{
- prefix: subnet,
- network: "udp",
- port: l.port,
+ pConn := newChanPacketConn(sessCh, subnet, l.writeRequests, &agdnet.PrefixNetAddr{
+ Prefix: subnet,
+ Net: "udp",
+ Port: l.port,
})
err = l.conns.addPacketConn(pConn)
@@ -182,7 +175,11 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
return &ListenConfig{
packetConn: pConn,
listener: lsnr,
- addr: agdnet.FormatPrefixAddr(subnet, l.port),
+ addr: &agdnet.PrefixNetAddr{
+ Prefix: subnet,
+ Net: "",
+ Port: l.port,
+ },
}, nil
}
@@ -214,13 +211,15 @@ func (m *Manager) validateIfaceSubnet(ifaceName string, subnet netip.Prefix) (er
}
// type check
-var _ agd.Service = (*Manager)(nil)
+var _ agdservice.Interface = (*Manager)(nil)
-// Start implements the [agd.Service] interface for *Manager. If m is nil,
-// Start returns nil, since this feature is optional.
+// Start implements the [agdservice.Interface] interface for *Manager. If m is
+// nil, Start returns nil, since this feature is optional.
//
// TODO(a.garipov): Consider an interface solution.
-func (m *Manager) Start() (err error) {
+//
+// TODO(a.garipov): Use the context for cancelation.
+func (m *Manager) Start(_ context.Context) (err error) {
if m == nil {
return nil
}
@@ -250,8 +249,8 @@ func (m *Manager) Start() (err error) {
return nil
}
-// Shutdown implements the [agd.Service] interface for *Manager. If m is nil,
-// Shutdown returns nil, since this feature is optional.
+// Shutdown implements the [agdservice.Interface] interface for *Manager. If m
+// is nil, Shutdown returns nil, since this feature is optional.
//
// TODO(a.garipov): Consider an interface solution.
//
diff --git a/internal/bindtodevice/manager_linux_test.go b/internal/bindtodevice/manager_linux_test.go
index d102eeb..49a24ce 100644
--- a/internal/bindtodevice/manager_linux_test.go
+++ b/internal/bindtodevice/manager_linux_test.go
@@ -233,10 +233,10 @@ func TestManager(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, lc)
- err = m.Start()
+ err = m.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
- return m.Shutdown(context.Background())
+ return m.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
})
t.Run("tcp", func(t *testing.T) {
diff --git a/internal/bindtodevice/manager_others.go b/internal/bindtodevice/manager_others.go
index 9e916f6..32fe66e 100644
--- a/internal/bindtodevice/manager_others.go
+++ b/internal/bindtodevice/manager_others.go
@@ -4,9 +4,10 @@ package bindtodevice
import (
"context"
+ "fmt"
"net/netip"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/errors"
)
@@ -22,17 +23,11 @@ func NewManager(c *ManagerConfig) (m *Manager) {
return &Manager{}
}
-// errUnsupported is returned from all [Manager] methods on OSs other than
-// Linux.
-//
-// TODO(a.garipov): Consider using [errors.ErrUnsupported] in Go 1.21.
-const errUnsupported errors.Error = "bindtodevice is only supported on linux"
-
// Add creates a new interface-listener record in m.
//
// It is only supported on Linux.
func (m *Manager) Add(id ID, ifaceName string, port uint16, cc *ControlConfig) (err error) {
- return errUnsupported
+ return fmt.Errorf("bindtodevice: add: %w; only supported on linux", errors.ErrUnsupported)
}
// ListenConfig returns a new *ListenConfig that receives connections from the
@@ -41,26 +36,29 @@ func (m *Manager) Add(id ID, ifaceName string, port uint16, cc *ControlConfig) (
//
// It is only supported on Linux.
func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err error) {
- return nil, errUnsupported
+ return nil, fmt.Errorf(
+ "bindtodevice: listenconfig: %w; only supported on linux",
+ errors.ErrUnsupported,
+ )
}
// type check
-var _ agd.Service = (*Manager)(nil)
+var _ agdservice.Interface = (*Manager)(nil)
-// Start implements the [agd.Service] interface for *Manager. If m is nil,
-// Start returns nil, since this feature is optional.
+// Start implements the [agdservice.Interface] interface for *Manager. If m is
+// nil, Start returns nil, since this feature is optional.
//
// It is only supported on Linux.
-func (m *Manager) Start() (err error) {
+func (m *Manager) Start(_ context.Context) (err error) {
if m == nil {
return nil
}
- return errUnsupported
+ return fmt.Errorf("bindtodevice: starting: %w; only supported on linux", errors.ErrUnsupported)
}
-// Shutdown implements the [agd.Service] interface for *Manager. If m is nil,
-// Shutdown returns nil, since this feature is optional.
+// Shutdown implements the [agdservice.Interface] interface for *Manager. If m
+// is nil, Shutdown returns nil, since this feature is optional.
//
// It is only supported on Linux.
func (m *Manager) Shutdown(_ context.Context) (err error) {
@@ -68,5 +66,8 @@ func (m *Manager) Shutdown(_ context.Context) (err error) {
return nil
}
- return errUnsupported
+ return fmt.Errorf(
+ "bindtodevice: shutting down: %w; only supported on linux",
+ errors.ErrUnsupported,
+ )
}
diff --git a/internal/bindtodevice/prefixaddr_linux.go b/internal/bindtodevice/prefixaddr_linux.go
deleted file mode 100644
index 472d7d8..0000000
--- a/internal/bindtodevice/prefixaddr_linux.go
+++ /dev/null
@@ -1,33 +0,0 @@
-//go:build linux
-
-package bindtodevice
-
-import (
- "net"
- "net/netip"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
-)
-
-// prefixNetAddr is a wrapper around netip.Prefix that makes it a [net.Addr].
-//
-// TODO(a.garipov): Support port 0, which will probably require atomic
-// operations and assistance in [Manager.Start].
-type prefixNetAddr struct {
- prefix netip.Prefix
- network string
- port uint16
-}
-
-// type check
-var _ net.Addr = (*prefixNetAddr)(nil)
-
-// String implements the [net.Addr] interface for *prefixNetAddr.
-//
-// See [agdnet.FormatPrefixAddr] for the format.
-func (addr *prefixNetAddr) String() (n string) {
- return agdnet.FormatPrefixAddr(addr.prefix, addr.port)
-}
-
-// Network implements the [net.Addr] interface for *prefixNetAddr.
-func (addr *prefixNetAddr) Network() (n string) { return addr.network }
diff --git a/internal/bindtodevice/socket_linux_internal_test.go b/internal/bindtodevice/socket_linux_internal_test.go
index 98a82a9..16bcea9 100644
--- a/internal/bindtodevice/socket_linux_internal_test.go
+++ b/internal/bindtodevice/socket_linux_internal_test.go
@@ -10,6 +10,7 @@ import (
"net"
"net/netip"
"os"
+ "slices"
"strings"
"syscall"
"testing"
@@ -21,7 +22,6 @@ import (
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "golang.org/x/exp/slices"
"golang.org/x/sys/unix"
)
@@ -105,8 +105,8 @@ func TestListenControl(t *testing.T) {
})
}
-// SubtestListenControlTCP is a shared subtest that uses lc to dial a listener and
-// perform two-way communication using the resulting connection.
+// SubtestListenControlTCP is a shared subtest that uses lc to dial a listener
+// and perform two-way communication using the resulting connection.
func SubtestListenControlTCP(
t *testing.T,
lc netext.ListenConfig,
@@ -120,7 +120,7 @@ func SubtestListenControlTCP(
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, lsnr.Close)
- // Make sure we can work with [prefixNetAddr] as well.
+ // Make sure we can work with [agdnet.PrefixNetAddr] as well.
addrStr, _, _ := strings.Cut(lsnr.Addr().String(), "/")
addr, err := netip.ParseAddrPort(addrStr)
require.NoError(t, err)
@@ -201,7 +201,7 @@ func SubtestListenControlUDP(
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, packetConn.Close)
- // Make sure we can work with [prefixNetAddr] as well.
+ // Make sure we can work with [agdnet.PrefixNetAddr] as well.
addrStr, _, _ := strings.Cut(packetConn.LocalAddr().String(), "/")
addr, err := netip.ParseAddrPort(addrStr)
require.NoError(t, err)
@@ -501,8 +501,7 @@ func BenchmarkReadPacketSession(b *testing.B) {
Type: unix.IP_ORIGDSTADDR,
}
- // TODO(a.garipov): Use binary.NativeEndian in Go 1.21 here and below.
- err := binary.Write(oobBuf, binary.LittleEndian, ctrlMsgHdr)
+ err := binary.Write(oobBuf, binary.NativeEndian, ctrlMsgHdr)
require.NoError(b, err)
pktInfo := unix.Inet4Pktinfo{
@@ -510,7 +509,7 @@ func BenchmarkReadPacketSession(b *testing.B) {
Addr: *(*[4]byte)(testRAddr.IP),
}
- err = binary.Write(oobBuf, binary.LittleEndian, pktInfo)
+ err = binary.Write(oobBuf, binary.NativeEndian, pktInfo)
require.NoError(b, err)
oobData := oobBuf.Bytes()
diff --git a/internal/cmd/backend.go b/internal/cmd/backend.go
index b01f158..b2b2c69 100644
--- a/internal/cmd/backend.go
+++ b/internal/cmd/backend.go
@@ -4,12 +4,11 @@ import (
"context"
"fmt"
"net/url"
- "time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/backend"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -34,6 +33,10 @@ type backendConfig struct {
// synchronization.
FullRefreshIvl timeutil.Duration `yaml:"full_refresh_interval"`
+ // FullRefreshRetryIvl is the interval between two retries of full
+ // synchronizations.
+ FullRefreshRetryIvl timeutil.Duration `yaml:"full_refresh_retry_interval"`
+
// BillStatIvl defines how often AdGuard DNS sends the billing statistics to
// the backend.
BillStatIvl timeutil.Duration `yaml:"bill_stat_interval"`
@@ -50,6 +53,8 @@ func (c *backendConfig) validate() (err error) {
return newMustBePositiveError("refresh_interval", c.RefreshIvl)
case c.FullRefreshIvl.Duration <= 0:
return newMustBePositiveError("full_refresh_interval", c.FullRefreshIvl)
+ case c.FullRefreshRetryIvl.Duration <= 0:
+ return newMustBePositiveError("full_refresh_retry_interval", c.FullRefreshRetryIvl)
case c.BillStatIvl.Duration <= 0:
return newMustBePositiveError("bill_stat_interval", c.BillStatIvl)
default:
@@ -64,7 +69,7 @@ func setupBackend(
conf *backendConfig,
envs *environments,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (profDB *profiledb.Default, rec *billstat.RuntimeRecorder, err error) {
rec, err = setupBillStat(conf, envs, sigHdlr, errColl)
if err != nil {
@@ -87,7 +92,7 @@ func setupBillStat(
conf *backendConfig,
envs *environments,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (rec *billstat.RuntimeRecorder, err error) {
apiURL := netutil.CloneURL(&envs.BillStatURL.URL)
billStatUploader, err := setupBillStatUploader(apiURL, errColl)
@@ -102,7 +107,7 @@ func setupBillStat(
refrIvl := conf.RefreshIvl.Duration
timeout := conf.Timeout.Duration
- billStatRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ billStatRefr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout)
},
@@ -112,8 +117,9 @@ func setupBillStat(
Interval: refrIvl,
RefreshOnShutdown: true,
RoutineLogsAreDebug: true,
+ RandomizeStart: false,
})
- err = billStatRefr.Start()
+ err = billStatRefr.Start(context.Background())
if err != nil {
return nil, fmt.Errorf("starting bill stat recorder refresher: %w", err)
}
@@ -129,7 +135,7 @@ func setupProfDB(
conf *backendConfig,
envs *environments,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (profDB *profiledb.Default, err error) {
apiURL := netutil.CloneURL(&envs.ProfilesURL.URL)
profStrg, err := setupProfStorage(apiURL, errColl)
@@ -137,15 +143,21 @@ func setupProfDB(
return nil, fmt.Errorf("creating profile storage: %w", err)
}
- profDB, err = profiledb.New(profStrg, conf.FullRefreshIvl.Duration, envs.ProfilesCachePath)
+ timeout := conf.Timeout.Duration
+ profDB, err = profiledb.New(&profiledb.Config{
+ Storage: profStrg,
+ FullSyncIvl: conf.FullRefreshIvl.Duration,
+ FullSyncRetryIvl: conf.FullRefreshRetryIvl.Duration,
+ InitialTimeout: timeout,
+ CacheFilePath: envs.ProfilesCachePath,
+ })
if err != nil {
return nil, fmt.Errorf("creating default profile database: %w", err)
}
refrIvl := conf.RefreshIvl.Duration
- timeout := conf.Timeout.Duration
- profDBRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ profDBRefr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout)
},
@@ -155,8 +167,9 @@ func setupProfDB(
Interval: refrIvl,
RefreshOnShutdown: false,
RoutineLogsAreDebug: true,
+ RandomizeStart: true,
})
- err = profDBRefr.Start()
+ err = profDBRefr.Start(context.Background())
if err != nil {
return nil, fmt.Errorf("starting default profile database refresher: %w", err)
}
@@ -168,8 +181,6 @@ func setupProfDB(
// Backend API URL schemes.
const (
- schemeHTTP = "http"
- schemeHTTPS = "https"
schemeGRPC = "grpc"
schemeGRPCS = "grpcs"
)
@@ -178,42 +189,32 @@ const (
// provided API URL.
func setupProfStorage(
apiURL *url.URL,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (s profiledb.Storage, err error) {
- switch apiURL.Scheme {
- case schemeGRPC, schemeGRPCS:
+ scheme := apiURL.Scheme
+ if scheme == schemeGRPC || scheme == schemeGRPCS {
return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
Endpoint: apiURL,
ErrColl: errColl,
})
- case schemeHTTP, schemeHTTPS:
- return backend.NewProfileStorage(&backend.ProfileStorageConfig{
- BaseEndpoint: apiURL,
- Now: time.Now,
- ErrColl: errColl,
- }), nil
- default:
- return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
}
+
+ return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
}
// setupBillStatUploader creates and returns a billstat uploader depending on
// the provided API URL.
func setupBillStatUploader(
apiURL *url.URL,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (s billstat.Uploader, err error) {
- switch apiURL.Scheme {
- case schemeGRPC, schemeGRPCS:
+ scheme := apiURL.Scheme
+ if scheme == schemeGRPC || scheme == schemeGRPCS {
return backendpb.NewBillStat(&backendpb.BillStatConfig{
ErrColl: errColl,
Endpoint: apiURL,
})
- case schemeHTTP, schemeHTTPS:
- return backend.NewBillStat(&backend.BillStatConfig{
- BaseEndpoint: apiURL,
- }), nil
- default:
- return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
}
+
+ return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
}
diff --git a/internal/cmd/check.go b/internal/cmd/check.go
index 5c9d405..0bc42a0 100644
--- a/internal/cmd/check.go
+++ b/internal/cmd/check.go
@@ -6,9 +6,9 @@ import (
"net/url"
"strings"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -44,7 +44,7 @@ type checkConfig struct {
func (c *checkConfig) toInternal(
envs *environments,
messages *dnsmsg.Constructor,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (conf *dnscheck.ConsulConfig) {
var kvURL, sessURL *url.URL
if envs.ConsulDNSCheckKVURL != nil && envs.ConsulDNSCheckSessionURL != nil {
diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go
index 78d73f9..059e398 100644
--- a/internal/cmd/cmd.go
+++ b/internal/cmd/cmd.go
@@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@@ -78,7 +79,7 @@ func Main() {
//
// See AGDNS-884.
- geoIP, geoIPRefr := &geoip.File{}, &agd.RefreshWorker{}
+ geoIP, geoIPRefr := &geoip.File{}, &agdservice.RefreshWorker{}
geoIPErrCh := make(chan error, 1)
go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl)
@@ -93,11 +94,13 @@ func Main() {
// TODO(ameshkov): Consider making a separated max_size config for
// safe-browsing and adult-blocking filters.
- maxFilterSize := int64(c.Filters.MaxSize.Bytes())
+ maxFilterSize := c.Filters.MaxSize.Bytes()
+ cloner := dnsmsg.NewCloner(metrics.ClonerStat{})
safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter(
c.SafeBrowsing,
filteringResolver,
+ cloner,
agd.FilterListIDSafeBrowsing,
envs.SafeBrowsingURL,
envs.FilterCachePath,
@@ -110,6 +113,7 @@ func Main() {
adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter(
c.AdultBlocking,
filteringResolver,
+ cloner,
agd.FilterListIDAdultBlocking,
envs.AdultBlockingURL,
envs.FilterCachePath,
@@ -123,6 +127,7 @@ func Main() {
// Reuse general safe browsing filter configuration.
c.SafeBrowsing,
filteringResolver,
+ cloner,
agd.FilterListIDNewRegDomains,
envs.NewRegDomainsURL,
envs.FilterCachePath,
@@ -137,6 +142,7 @@ func Main() {
fltStrgConf := c.Filters.toInternal(
errColl,
filteringResolver,
+ cloner,
envs,
safeBrowsingFilter,
adultBlockingFilter,
@@ -150,30 +156,37 @@ func Main() {
fltGroups, err := c.FilteringGroups.toInternal(fltStrg)
check(err)
- // Network interface listener
+ // Access
+
+ accessGlobal, err := access.NewGlobal(
+ c.Access.BlockedQuestionDomains,
+ c.Access.BlockedClientSubnets,
+ )
+ check(err)
+
+ // Network interface listener and server groups
+
+ messages := dnsmsg.NewConstructor(
+ cloner,
+ &dnsmsg.BlockingModeNullIP{},
+ c.Filters.ResponseTTL.Duration,
+ )
btdCtrlConf, ctrlConf := c.Network.toInternal()
btdMgr, err := c.InterfaceListeners.toInternal(errColl, btdCtrlConf)
check(err)
- err = btdMgr.Start()
+ srvGrps, err := c.ServerGroups.toInternal(messages, btdMgr, fltGroups, c.RateLimit, c.DNS)
+ check(err)
+
+ // Start the bind-to-device manager here, now that no further calls to
+ // btdMgr.ListenConfig are required.
+ err = btdMgr.Start(context.Background())
check(err)
sigHdlr.add(btdMgr)
- // access
-
- accessManager, err := access.New(c.Access.BlockedQuestionDomains, c.Access.BlockedClientSubnets)
- check(err)
-
- // Server groups
-
- messages := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, c.Filters.ResponseTTL.Duration)
-
- srvGrps, err := c.ServerGroups.toInternal(messages, btdMgr, fltGroups)
- check(err)
-
// TLS keys logging
if envs.SSLKeyLogFile != "" {
@@ -227,14 +240,13 @@ func Main() {
webSvc := websvc.New(webConf)
// The web service is considered critical, so its Start method panics
// instead of returning an error.
- _ = webSvc.Start()
+ _ = webSvc.Start(context.Background())
sigHdlr.add(webSvc)
// DNS service
- fwdConf, err := c.Upstream.toInternal()
- check(err)
+ fwdConf := c.Upstream.toInternal()
handler := forward.NewHandler(fwdConf)
@@ -246,8 +258,11 @@ func Main() {
}
dnsConf := &dnssvc.Config{
- AccessManager: accessManager,
Messages: messages,
+ Cloner: cloner,
+ ControlConf: ctrlConf,
+ ConnLimiter: connLimiter,
+ AccessManager: accessGlobal,
SafeBrowsing: hashprefix.NewMatcher(hashStorages),
BillStat: billStatRec,
ProfileDB: profDB,
@@ -261,9 +276,9 @@ func Main() {
QueryLog: c.buildQueryLog(envs),
RuleStat: ruleStat,
RateLimit: rateLimiter,
- ConnLimiter: connLimiter,
FilteringGroups: fltGroups,
ServerGroups: srvGrps,
+ HandleTimeout: c.DNS.HandleTimeout.Duration,
CacheSize: c.Cache.Size,
ECSCacheSize: c.Cache.ECSSize,
CacheMinTTL: c.Cache.TTLOverride.Min.Duration,
@@ -271,7 +286,6 @@ func Main() {
UseECSCache: c.Cache.Type == cacheTypeECS,
ResearchMetrics: bool(envs.ResearchMetrics),
ResearchLogs: bool(envs.ResearchLogs),
- ControlConf: ctrlConf,
}
dnsSvc, err := dnssvc.New(dnsConf)
@@ -283,14 +297,14 @@ func Main() {
check(err)
upstreamHealthcheckUpd := newUpstreamHealthcheck(handler, c.Upstream, errColl)
- err = upstreamHealthcheckUpd.Start()
+ err = upstreamHealthcheckUpd.Start(context.Background())
check(err)
sigHdlr.add(upstreamHealthcheckUpd)
// The DNS service is considered critical, so its Start method panics
// instead of returning an error.
- _ = dnsSvc.Start()
+ _ = dnsSvc.Start(context.Background())
sigHdlr.add(dnsSvc)
@@ -300,7 +314,7 @@ func Main() {
// The debug HTTP service is considered critical, so its Start method panics
// instead of returning an error.
- _ = debugSvc.Start()
+ _ = debugSvc.Start(context.Background())
sigHdlr.add(debugSvc)
@@ -320,7 +334,7 @@ func Main() {
//
// TODO(a.garipov): Consider making into a helper in package agd and using
// everywhere.
-func collectPanics(errColl agd.ErrorCollector) {
+func collectPanics(errColl errcoll.Interface) {
v := recover()
if v == nil {
return
diff --git a/internal/cmd/config.go b/internal/cmd/config.go
index c09d046..2121d62 100644
--- a/internal/cmd/config.go
+++ b/internal/cmd/config.go
@@ -33,6 +33,9 @@ type configuration struct {
// DNSDB is the configuration of DNSDB buffer.
DNSDB *dnsDBConfig `yaml:"dnsdb"`
+ // DNSDB is the configuration of common DNS settings.
+ DNS *dnsConfig `yaml:"dns"`
+
// Backend is the AdGuard HTTP backend service configuration. See the
// environments type for more backend parameters.
Backend *backendConfig `yaml:"backend"`
@@ -123,6 +126,9 @@ func (c *configuration) validate() (err error) {
}, {
validate: c.DNSDB.validate,
name: "dnsdb",
+ }, {
+ validate: c.DNS.validate,
+ name: "dns",
}, {
validate: c.Backend.validate,
name: "backend",
diff --git a/internal/cmd/conncheck.go b/internal/cmd/conncheck.go
index 9013f98..7d15c37 100644
--- a/internal/cmd/conncheck.go
+++ b/internal/cmd/conncheck.go
@@ -86,7 +86,7 @@ func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) (err error)
func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) {
for _, srvGrp := range serverGroups {
for _, s := range srvGrp.Servers {
- if containsIPv6BindAddress(s.BindData) {
+ if s.HasIPv6() {
return true
}
}
@@ -94,15 +94,3 @@ func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) {
return false
}
-
-// containsIPv6BindAddress returns true if provided bindData contains valid IPv6
-// address.
-func containsIPv6BindAddress(bindData []*agd.ServerBindData) (ok bool) {
- for _, bData := range bindData {
- if addr := bData.AddrPort; addr.Addr().Is6() {
- return true
- }
- }
-
- return false
-}
diff --git a/internal/cmd/ddr.go b/internal/cmd/ddr.go
index bf8667f..f7f825b 100644
--- a/internal/cmd/ddr.go
+++ b/internal/cmd/ddr.go
@@ -1,8 +1,10 @@
package cmd
import (
+ "cmp"
"fmt"
"net/netip"
+ "slices"
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -11,7 +13,6 @@ import (
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
- "golang.org/x/exp/slices"
)
// Discovery Of Designated Resolvers (DDR) configuration
@@ -57,16 +58,8 @@ func ddrRecsToSVCBTmpls(
tmpls = appendDDRSVCBTmpls(tmpls, msgs, r, target)
}
- // TODO(e.burkov): Use cmp.Compare when updated to go1.21.
slices.SortStableFunc(tmpls, func(a, b *dns.SVCB) (res int) {
- switch x, y := a.Priority, b.Priority; {
- case x < y:
- return -1
- case x > y:
- return +1
- default:
- return 0
- }
+ return cmp.Compare(a.Priority, b.Priority)
})
return targets, tmpls
diff --git a/internal/cmd/dns.go b/internal/cmd/dns.go
new file mode 100644
index 0000000..456065e
--- /dev/null
+++ b/internal/cmd/dns.go
@@ -0,0 +1,58 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/AdguardTeam/golibs/timeutil"
+ "github.com/c2h5oh/datasize"
+ "github.com/miekg/dns"
+)
+
+// dnsConfig contains common DNS settings.
+type dnsConfig struct {
+ // ReadTimeout defines the timeout for any read from a UDP connection or the
+ // first read from a TCP/TLS connection. It currently doesn't affect
+ // DNSCrypt, QUIC, or HTTPS.
+ ReadTimeout timeutil.Duration `yaml:"read_timeout"`
+
+ // TCPIdleTimeout defines the timeout for consecutive reads from a TCP/TLS
+ // connection. It currently doesn't affect DNSCrypt, QUIC, or HTTPS.
+ TCPIdleTimeout timeutil.Duration `yaml:"tcp_idle_timeout"`
+
+ // WriteTimeout defines the timeout for writing to a UDP or TCP/TLS
+ // connection. It currently doesn't affect DNSCrypt, QUIC, or HTTPS.
+ WriteTimeout timeutil.Duration `yaml:"write_timeout"`
+
+ // HandleTimeout defines the timeout for the entire handling of a single
+ // query.
+ HandleTimeout timeutil.Duration `yaml:"handle_timeout"`
+
+ // MaxUDPResponseSize is the maximum size of DNS response over UDP protocol.
+ MaxUDPResponseSize datasize.ByteSize `yaml:"max_udp_response_size"`
+}
+
+// validate returns an error if the configuration is invalid.
+func (c *dnsConfig) validate() (err error) {
+ switch {
+ case c == nil:
+ return errNilConfig
+ case c.ReadTimeout.Duration <= 0:
+ return newMustBePositiveError("read_timeout", c.ReadTimeout)
+ case c.TCPIdleTimeout.Duration <= 0:
+ return newMustBePositiveError("tcp_idle_timeout", c.TCPIdleTimeout)
+ case c.WriteTimeout.Duration <= 0:
+ return newMustBePositiveError("write_timeout", c.WriteTimeout)
+ case c.HandleTimeout.Duration <= 0:
+ return newMustBePositiveError("handle_timeout", c.HandleTimeout)
+ case c.MaxUDPResponseSize.Bytes() == 0:
+ return newMustBePositiveError("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
+ }
+}
diff --git a/internal/cmd/dnsdb.go b/internal/cmd/dnsdb.go
index b59825d..a5b4c42 100644
--- a/internal/cmd/dnsdb.go
+++ b/internal/cmd/dnsdb.go
@@ -1,12 +1,10 @@
package cmd
import (
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
)
-// DNSDB Configuration.
-
// dnsDBConfig is the configuration of the DNSDB module.
type dnsDBConfig struct {
// MaxSize is the maximum amount of records in the memory buffer.
@@ -29,7 +27,7 @@ func (c *dnsDBConfig) validate() (err error) {
}
// toInternal builds and returns an anonymous statistics collector.
-func (c *dnsDBConfig) toInternal(errColl agd.ErrorCollector) (d dnsdb.Interface) {
+func (c *dnsDBConfig) toInternal(errColl errcoll.Interface) (d dnsdb.Interface) {
if !c.Enabled {
return dnsdb.Empty{}
}
diff --git a/internal/cmd/env.go b/internal/cmd/env.go
index 1e4a3d2..c677df4 100644
--- a/internal/cmd/env.go
+++ b/internal/cmd/env.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "context"
"fmt"
"net"
"net/http"
@@ -8,7 +9,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
@@ -16,6 +17,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/caarlos0/env/v7"
"github.com/getsentry/sentry-go"
)
@@ -24,20 +26,20 @@ import (
// environments represents the configuration that is kept in the environment.
type environments struct {
- AdultBlockingURL *agdhttp.URL `env:"ADULT_BLOCKING_URL,notEmpty"`
- BillStatURL *agdhttp.URL `env:"BILLSTAT_URL,notEmpty"`
- BlockedServiceIndexURL *agdhttp.URL `env:"BLOCKED_SERVICE_INDEX_URL,notEmpty"`
- ConsulAllowlistURL *agdhttp.URL `env:"CONSUL_ALLOWLIST_URL,notEmpty"`
- ConsulDNSCheckKVURL *agdhttp.URL `env:"CONSUL_DNSCHECK_KV_URL"`
- ConsulDNSCheckSessionURL *agdhttp.URL `env:"CONSUL_DNSCHECK_SESSION_URL"`
- FilterIndexURL *agdhttp.URL `env:"FILTER_INDEX_URL,notEmpty"`
- GeneralSafeSearchURL *agdhttp.URL `env:"GENERAL_SAFE_SEARCH_URL,notEmpty"`
- LinkedIPTargetURL *agdhttp.URL `env:"LINKED_IP_TARGET_URL"`
- NewRegDomainsURL *agdhttp.URL `env:"NEW_REG_DOMAINS_URL,notEmpty"`
- ProfilesURL *agdhttp.URL `env:"PROFILES_URL,notEmpty"`
- RuleStatURL *agdhttp.URL `env:"RULESTAT_URL"`
- SafeBrowsingURL *agdhttp.URL `env:"SAFE_BROWSING_URL,notEmpty"`
- YoutubeSafeSearchURL *agdhttp.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"`
+ AdultBlockingURL *urlutil.URL `env:"ADULT_BLOCKING_URL,notEmpty"`
+ BillStatURL *urlutil.URL `env:"BILLSTAT_URL,notEmpty"`
+ BlockedServiceIndexURL *urlutil.URL `env:"BLOCKED_SERVICE_INDEX_URL,notEmpty"`
+ ConsulAllowlistURL *urlutil.URL `env:"CONSUL_ALLOWLIST_URL,notEmpty"`
+ ConsulDNSCheckKVURL *urlutil.URL `env:"CONSUL_DNSCHECK_KV_URL"`
+ ConsulDNSCheckSessionURL *urlutil.URL `env:"CONSUL_DNSCHECK_SESSION_URL"`
+ FilterIndexURL *urlutil.URL `env:"FILTER_INDEX_URL,notEmpty"`
+ GeneralSafeSearchURL *urlutil.URL `env:"GENERAL_SAFE_SEARCH_URL,notEmpty"`
+ LinkedIPTargetURL *urlutil.URL `env:"LINKED_IP_TARGET_URL"`
+ NewRegDomainsURL *urlutil.URL `env:"NEW_REG_DOMAINS_URL,notEmpty"`
+ ProfilesURL *urlutil.URL `env:"PROFILES_URL,notEmpty"`
+ RuleStatURL *urlutil.URL `env:"RULESTAT_URL"`
+ SafeBrowsingURL *urlutil.URL `env:"SAFE_BROWSING_URL,notEmpty"`
+ YoutubeSafeSearchURL *urlutil.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"`
ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"`
FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"`
@@ -50,7 +52,7 @@ type environments struct {
ListenAddr net.IP `env:"LISTEN_ADDR" envDefault:"127.0.0.1"`
- ListenPort int `env:"LISTEN_PORT" envDefault:"8181"`
+ ListenPort uint16 `env:"LISTEN_PORT" envDefault:"8181"`
LogTimestamp strictBool `env:"LOG_TIMESTAMP" envDefault:"1"`
LogVerbose strictBool `env:"VERBOSE" envDefault:"0"`
@@ -84,7 +86,7 @@ func (envs *environments) configureLogs() {
}
// buildErrColl builds and returns an error collector from environment.
-func (envs *environments) buildErrColl() (errColl agd.ErrorCollector, err error) {
+func (envs *environments) buildErrColl() (errColl errcoll.Interface, err error) {
dsn := envs.SentryDSN
if dsn == "stderr" {
return errcoll.NewWriterErrorCollector(os.Stderr), nil
@@ -157,7 +159,7 @@ func (envs *environments) debugConf(dnsDB dnsdb.Interface) (conf *debugsvc.Confi
// registers its refresher in sigHdlr, if necessary.
func (envs *environments) buildRuleStat(
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (r rulestat.Interface, err error) {
if envs.RuleStatURL == nil {
log.Info("main: warning: not collecting rule stats")
@@ -169,7 +171,7 @@ func (envs *environments) buildRuleStat(
URL: &envs.RuleStatURL.URL,
})
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: httpRuleStat,
ErrColl: errColl,
@@ -178,8 +180,9 @@ func (envs *environments) buildRuleStat(
Interval: 10 * time.Minute,
RefreshOnShutdown: true,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
})
- err = refr.Start()
+ err = refr.Start(context.Background())
if err != nil {
return nil, fmt.Errorf("starting rulestat refresher: %w", err)
}
diff --git a/internal/cmd/filter.go b/internal/cmd/filter.go
index 44fa1a9..f734813 100644
--- a/internal/cmd/filter.go
+++ b/internal/cmd/filter.go
@@ -5,8 +5,10 @@ import (
"fmt"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
"github.com/AdguardTeam/golibs/netutil"
@@ -49,8 +51,9 @@ type filtersConfig struct {
// toInternal converts c to the filter storage configuration for the DNS server.
// cacheDir must exist. c is assumed to be valid.
func (c *filtersConfig) toInternal(
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
resolver agdnet.Resolver,
+ cloner *dnsmsg.Cloner,
envs *environments,
safeBrowsing *hashprefix.Filter,
adultBlocking *hashprefix.Filter,
@@ -67,6 +70,7 @@ func (c *filtersConfig) toInternal(
Now: time.Now,
ErrColl: errColl,
Resolver: resolver,
+ Cloner: cloner,
CacheDir: envs.FilterCachePath,
CustomFilterCacheSize: c.CustomFilterCacheSize,
SafeSearchCacheSize: c.SafeSearchCacheSize,
@@ -75,7 +79,7 @@ func (c *filtersConfig) toInternal(
RuleListCacheSize: c.RuleListCache.Size,
RefreshIvl: c.RefreshIvl.Duration,
UseRuleListCache: c.RuleListCache.Enabled,
- MaxRuleListSize: int64(c.MaxSize.Bytes()),
+ MaxRuleListSize: c.MaxSize.Bytes(),
}
}
@@ -133,7 +137,7 @@ func (c *fltRuleListCache) validate() (err error) {
func setupFilterStorage(
conf *filter.DefaultStorageConfig,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
refreshTimeout time.Duration,
) (strg *filter.DefaultStorage, err error) {
strg, err = filter.NewDefaultStorage(conf)
@@ -141,7 +145,7 @@ func setupFilterStorage(
return nil, fmt.Errorf("creating default filter storage: %w", err)
}
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), refreshTimeout)
},
@@ -151,8 +155,9 @@ func setupFilterStorage(
Interval: conf.RefreshIvl,
RefreshOnShutdown: false,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
})
- err = refr.Start()
+ err = refr.Start(context.Background())
if err != nil {
return nil, fmt.Errorf("starting default filter storage update: %w", err)
}
diff --git a/internal/cmd/geoip.go b/internal/cmd/geoip.go
index 8d231db..e645928 100644
--- a/internal/cmd/geoip.go
+++ b/internal/cmd/geoip.go
@@ -1,9 +1,11 @@
package cmd
import (
+ "context"
"fmt"
- "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/timeutil"
)
@@ -47,11 +49,11 @@ func (c *geoIPConfig) validate() (err error) {
// refresher have been created successfully or an error if not.
func setupGeoIP(
geoIPPtr *geoip.File,
- refrPtr *agd.RefreshWorker,
+ refrPtr *agdservice.RefreshWorker,
errCh chan<- error,
conf *geoIPConfig,
envs *environments,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) {
geoIP, err := envs.geoIP(conf)
if err != nil {
@@ -60,7 +62,7 @@ func setupGeoIP(
return
}
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: geoIP,
ErrColl: errColl,
@@ -68,8 +70,9 @@ func setupGeoIP(
Interval: conf.RefreshIvl.Duration,
RefreshOnShutdown: false,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
})
- err = refr.Start()
+ err = refr.Start(context.Background())
if err != nil {
errCh <- fmt.Errorf("starting geoip refresher: %w", err)
diff --git a/internal/cmd/ifacelistener.go b/internal/cmd/ifacelistener.go
index 707c0c4..acbe33c 100644
--- a/internal/cmd/ifacelistener.go
+++ b/internal/cmd/ifacelistener.go
@@ -1,8 +1,8 @@
package cmd
import (
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mapsutil"
)
@@ -22,7 +22,7 @@ type interfaceListenersConfig struct {
// toInternal converts c to a bindtodevice.Manager. c is assumed to be valid.
func (c *interfaceListenersConfig) toInternal(
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
ctrlConf *bindtodevice.ControlConfig,
) (m *bindtodevice.Manager, err error) {
if c == nil {
diff --git a/internal/cmd/ratelimit.go b/internal/cmd/ratelimit.go
index ccc107a..7d91f29 100644
--- a/internal/cmd/ratelimit.go
+++ b/internal/cmd/ratelimit.go
@@ -1,14 +1,16 @@
package cmd
import (
+ "context"
"fmt"
"net/url"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
"github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
@@ -32,22 +34,28 @@ type rateLimitConfig struct {
// Rate limit options for IPv6 addresses.
IPv6 *rateLimitOptions `yaml:"ipv6"`
+ // QUIC is the configuration of QUIC streams limiting.
+ QUIC *ratelimitQUICConfig `yaml:"quic"`
+
+ // TCP is the configuration of TCP pipeline limiting.
+ TCP *ratelimitTCPConfig `yaml:"tcp"`
+
// ResponseSizeEstimate is the size of the estimate of the size of one DNS
// response for the purposes of rate limiting. Responses over this estimate
// are counted as several responses.
ResponseSizeEstimate datasize.ByteSize `yaml:"response_size_estimate"`
- // BackOffCount helps with repeated offenders. It defines, how many times
+ // BackoffCount helps with repeated offenders. It defines, how many times
// a client hits the rate limit before being held in the back off.
- BackOffCount int `yaml:"back_off_count"`
+ BackoffCount int `yaml:"backoff_count"`
- // BackOffDuration is how much a client that has hit the rate limit too
+ // BackoffDuration is how much a client that has hit the rate limit too
// often stays in the back off.
- BackOffDuration timeutil.Duration `yaml:"back_off_duration"`
+ BackoffDuration timeutil.Duration `yaml:"backoff_duration"`
- // BackOffPeriod is the time during which to count the number of times
+ // BackoffPeriod is the time during which to count the number of times
// a client has hit the rate limit for a back off.
- BackOffPeriod timeutil.Duration `yaml:"back_off_period"`
+ BackoffPeriod timeutil.Duration `yaml:"backoff_period"`
// RefuseANY, if true, makes the server refuse DNS * queries.
RefuseANY bool `yaml:"refuse_any"`
@@ -87,17 +95,17 @@ func (o *rateLimitOptions) validate() (err error) {
// toInternal converts c to the rate limiting configuration for the DNS server.
// c is assumed to be valid.
-func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.BackOffConfig) {
- return &ratelimit.BackOffConfig{
+func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.BackoffConfig) {
+ return &ratelimit.BackoffConfig{
Allowlist: al,
ResponseSizeEstimate: int(c.ResponseSizeEstimate.Bytes()),
- Duration: c.BackOffDuration.Duration,
- Period: c.BackOffPeriod.Duration,
+ Duration: c.BackoffDuration.Duration,
+ Period: c.BackoffPeriod.Duration,
IPv4RPS: c.IPv4.RPS,
IPv4SubnetKeyLen: c.IPv4.SubnetKeyLen,
IPv6RPS: c.IPv6.RPS,
IPv6SubnetKeyLen: c.IPv6.SubnetKeyLen,
- Count: c.BackOffCount,
+ Count: c.BackoffCount,
RefuseANY: c.RefuseANY,
}
}
@@ -111,25 +119,15 @@ func (c *rateLimitConfig) validate() (err error) {
return fmt.Errorf("allowlist: %w", errNilConfig)
}
- err = c.ConnectionLimit.validate()
- if err != nil {
- return fmt.Errorf("connection_limit: %w", err)
- }
-
- err = c.IPv4.validate()
- if err != nil {
- return fmt.Errorf("ipv4: %w", err)
- }
-
- err = c.IPv6.validate()
- if err != nil {
- return fmt.Errorf("ipv6: %w", err)
- }
-
return coalesceError(
- validatePositive("back_off_count", c.BackOffCount),
- validatePositive("back_off_duration", c.BackOffDuration),
- validatePositive("back_off_period", c.BackOffPeriod),
+ 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),
validatePositive("allowlist.refresh_interval", c.Allowlist.RefreshIvl),
)
@@ -141,8 +139,8 @@ func setupRateLimiter(
conf *rateLimitConfig,
consulAllowlist *url.URL,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
-) (rateLimiter *ratelimit.BackOff, connLimiter *connlimiter.Limiter, err error) {
+ errColl errcoll.Interface,
+) (rateLimiter *ratelimit.Backoff, connLimiter *connlimiter.Limiter, err error) {
allowSubnets, err := agdnet.ParseSubnets(conf.Allowlist.List...)
if err != nil {
return nil, nil, fmt.Errorf("parsing allowlist subnets: %w", err)
@@ -154,7 +152,7 @@ func setupRateLimiter(
return nil, nil, fmt.Errorf("creating allowlist refresher: %w", err)
}
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: refresher,
ErrColl: errColl,
@@ -162,15 +160,17 @@ func setupRateLimiter(
Interval: conf.Allowlist.RefreshIvl.Duration,
RefreshOnShutdown: false,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
})
- err = refr.Start()
+
+ err = refr.Start(context.Background())
if err != nil {
return nil, nil, fmt.Errorf("starting allowlist refresher: %w", err)
}
sigHdlr.add(refr)
- return ratelimit.NewBackOff(conf.toInternal(allowlist)), conf.ConnectionLimit.toInternal(), nil
+ return ratelimit.NewBackoff(conf.toInternal(allowlist)), conf.ConnectionLimit.toInternal(), nil
}
// connLimitConfig is the configuration structure for the stream-connection
@@ -229,3 +229,41 @@ func (c *connLimitConfig) validate() (err error) {
return nil
}
}
+
+// ratelimitTCPConfig is the configuration of TCP pipeline limiting.
+type ratelimitTCPConfig struct {
+ // MaxPipelineCount is the maximum number of simultaneously processing TCP
+ // messages per one connection.
+ MaxPipelineCount uint `yaml:"max_pipeline_count"`
+
+ // Enabled, if true, enables TCP limiting.
+ Enabled bool `yaml:"enabled"`
+}
+
+// validate returns an error if the config is invalid.
+func (c *ratelimitTCPConfig) validate() (err error) {
+ if c == nil {
+ return errNilConfig
+ }
+
+ return validatePositive("max_pipeline_count", c.MaxPipelineCount)
+}
+
+// ratelimitQUICConfig is the configuration of QUIC streams limiting.
+type ratelimitQUICConfig struct {
+ // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer
+ // is allowed to open.
+ MaxStreamsPerPeer int `yaml:"max_streams_per_peer"`
+
+ // Enabled, if true, enables QUIC limiting.
+ Enabled bool `yaml:"enabled"`
+}
+
+// validate returns an error if the config is invalid.
+func (c *ratelimitQUICConfig) validate() (err error) {
+ if c == nil {
+ return errNilConfig
+ }
+
+ return validatePositive("max_streams_per_peer", c.MaxStreamsPerPeer)
+}
diff --git a/internal/cmd/safebrowsing.go b/internal/cmd/safebrowsing.go
index d86f72e..5aefb0c 100644
--- a/internal/cmd/safebrowsing.go
+++ b/internal/cmd/safebrowsing.go
@@ -1,15 +1,19 @@
package cmd
import (
+ "context"
"fmt"
"path/filepath"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil"
)
@@ -37,12 +41,13 @@ type safeBrowsingConfig struct {
// toInternal converts c to the safe browsing filter configuration for the
// filter storage of the DNS server. c is assumed to be valid.
func (c *safeBrowsingConfig) toInternal(
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
resolver agdnet.Resolver,
+ cloner *dnsmsg.Cloner,
id agd.FilterListID,
- url *agdhttp.URL,
+ url *urlutil.URL,
cacheDir string,
- maxSize int64,
+ maxSize uint64,
) (fltConf *hashprefix.FilterConfig, err error) {
hashes, err := hashprefix.NewStorage("")
if err != nil {
@@ -50,6 +55,7 @@ func (c *safeBrowsingConfig) toInternal(
}
return &hashprefix.FilterConfig{
+ Cloner: cloner,
Hashes: hashes,
URL: netutil.CloneURL(&url.URL),
ErrColl: errColl,
@@ -88,14 +94,15 @@ func (c *safeBrowsingConfig) validate() (err error) {
func setupHashPrefixFilter(
conf *safeBrowsingConfig,
resolver *agdnet.CachingResolver,
+ cloner *dnsmsg.Cloner,
id agd.FilterListID,
- url *agdhttp.URL,
+ url *urlutil.URL,
cachePath string,
- maxSize int64,
+ maxSize uint64,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (strg *hashprefix.Storage, flt *hashprefix.Filter, err error) {
- fltConf, err := conf.toInternal(errColl, resolver, id, url, cachePath, maxSize)
+ fltConf, err := conf.toInternal(errColl, resolver, cloner, id, url, cachePath, maxSize)
if err != nil {
return nil, nil, fmt.Errorf("configuring hash prefix filter %s: %w", id, err)
}
@@ -105,7 +112,7 @@ func setupHashPrefixFilter(
return nil, nil, fmt.Errorf("creating hash prefix filter %s: %w", id, err)
}
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: flt,
ErrColl: errColl,
@@ -113,8 +120,9 @@ func setupHashPrefixFilter(
Interval: fltConf.Staleness,
RefreshOnShutdown: false,
RoutineLogsAreDebug: false,
+ RandomizeStart: false,
})
- err = refr.Start()
+ err = refr.Start(context.Background())
if err != nil {
return nil, nil, fmt.Errorf("starting refresher for hash prefix filter %s: %w", id, err)
}
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 5e43e00..c06bcc8 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -11,13 +11,13 @@ import (
"github.com/AdguardTeam/golibs/stringutil"
)
-// Server configuration
-
// toInternal returns the configuration of DNS servers for a single server
-// group. srvs is assumed to be valid.
+// group. srvs and other parts of the configuration are assumed to be valid.
func (srvs servers) toInternal(
tlsConfig *agd.TLS,
btdMgr *bindtodevice.Manager,
+ ratelimitConf *rateLimitConfig,
+ dnsConf *dnsConfig,
) (dnsSrvs []*agd.Server, err error) {
dnsSrvs = make([]*agd.Server, 0, len(srvs))
for _, srv := range srvs {
@@ -28,29 +28,41 @@ func (srvs servers) toInternal(
}
name := agd.ServerName(srv.Name)
- switch p := srv.Protocol; p {
- case srvProtoDNS:
- dnsSrvs = append(dnsSrvs, &agd.Server{
- Name: name,
- BindData: bindData,
- Protocol: agd.ProtoDNS,
- LinkedIPEnabled: srv.LinkedIPEnabled,
- })
- case srvProtoDNSCrypt:
+ dnsSrv := &agd.Server{
+ Name: name,
+ ReadTimeout: dnsConf.ReadTimeout.Duration,
+ WriteTimeout: dnsConf.WriteTimeout.Duration,
+ LinkedIPEnabled: srv.LinkedIPEnabled,
+ Protocol: srv.Protocol.toInternal(),
+ }
+
+ tcpConf := &agd.TCPConfig{
+ IdleTimeout: dnsConf.TCPIdleTimeout.Duration,
+ MaxPipelineCount: ratelimitConf.TCP.MaxPipelineCount,
+ MaxPipelineEnabled: ratelimitConf.TCP.Enabled,
+ }
+
+ switch dnsSrv.Protocol {
+ case agd.ProtoDNS:
+ dnsSrv.TCPConf = tcpConf
+ dnsSrv.UDPConf = &agd.UDPConfig{
+ MaxRespSize: uint16(dnsConf.MaxUDPResponseSize.Bytes()),
+ }
+ case agd.ProtoDNSCrypt:
var dcConf *agd.DNSCryptConfig
dcConf, err = srv.DNSCrypt.toInternal()
if err != nil {
return nil, fmt.Errorf("server %q: dnscrypt: %w", srv.Name, err)
}
- dnsSrvs = append(dnsSrvs, &agd.Server{
- DNSCrypt: dcConf,
- Name: name,
- BindData: bindData,
- Protocol: agd.ProtoDNSCrypt,
- LinkedIPEnabled: srv.LinkedIPEnabled,
- })
+ dnsSrv.DNSCrypt = dcConf
default:
+ dnsSrv.TCPConf = tcpConf
+ dnsSrv.QUICConf = &agd.QUICConfig{
+ MaxStreamsPerPeer: ratelimitConf.QUIC.MaxStreamsPerPeer,
+ QUICLimitsEnabled: ratelimitConf.QUIC.Enabled,
+ }
+
tlsConf := tlsConfig.Conf.Clone()
// Attach the functions that will count TLS handshake metrics.
@@ -62,14 +74,12 @@ func (srvs servers) toInternal(
tlsConf.Certificates,
)
- dnsSrvs = append(dnsSrvs, &agd.Server{
- TLS: tlsConf,
- Name: name,
- BindData: bindData,
- Protocol: p.toInternal(),
- LinkedIPEnabled: srv.LinkedIPEnabled,
- })
+ dnsSrv.TLS = tlsConf
}
+
+ dnsSrv.SetBindData(bindData)
+
+ dnsSrvs = append(dnsSrvs, dnsSrv)
}
return dnsSrvs, nil
@@ -128,9 +138,13 @@ func (p serverProto) needsTLS() (ok bool) {
}
// toInternal returns the equivalent agd.Protocol value if there is one. If
-// there is no such value, it returns agd.ProtoInvalid.
+// there is no such value, it returns [agd.ProtoInvalid].
func (p serverProto) toInternal() (sp agd.Protocol) {
switch p {
+ case srvProtoDNS:
+ return agd.ProtoDNS
+ case srvProtoDNSCrypt:
+ return agd.ProtoDNSCrypt
case srvProtoHTTPS:
return agd.ProtoDoH
case srvProtoQUIC:
@@ -215,7 +229,7 @@ func (s *server) bindData(
bindData = append(bindData, &agd.ServerBindData{
ListenConfig: lc,
- Address: lc.Addr(),
+ PrefixAddr: lc.Addr(),
})
}
}
diff --git a/internal/cmd/servergroup.go b/internal/cmd/servergroup.go
index 70b35d0..4920d92 100644
--- a/internal/cmd/servergroup.go
+++ b/internal/cmd/servergroup.go
@@ -10,18 +10,19 @@ import (
"github.com/AdguardTeam/golibs/stringutil"
)
-// Server Group Configuration
-
// serverGroups are the DNS server groups. A valid instance of serverGroups has
// no nil items.
type serverGroups []*serverGroup
// toInternal returns the configuration for all server groups in the DNS
-// service. srvGrps is assumed to be valid.
+// service. srvGrps and other parts of the configuration are assumed to be
+// valid.
func (srvGrps serverGroups) toInternal(
messages *dnsmsg.Constructor,
btdMgr *bindtodevice.Manager,
fltGrps map[agd.FilteringGroupID]*agd.FilteringGroup,
+ ratelimitConf *rateLimitConfig,
+ dnsConf *dnsConfig,
) (svcSrvGrps []*agd.ServerGroup, err error) {
svcSrvGrps = make([]*agd.ServerGroup, len(srvGrps))
for i, g := range srvGrps {
@@ -44,7 +45,7 @@ func (srvGrps serverGroups) toInternal(
FilteringGroup: fltGrpID,
}
- svcSrvGrps[i].Servers, err = g.Servers.toInternal(tlsConf, btdMgr)
+ svcSrvGrps[i].Servers, err = g.Servers.toInternal(tlsConf, btdMgr, ratelimitConf, dnsConf)
if err != nil {
return nil, fmt.Errorf("server group %q: %w", g.Name, err)
}
diff --git a/internal/cmd/signal.go b/internal/cmd/signal.go
index cacf204..eaa79df 100644
--- a/internal/cmd/signal.go
+++ b/internal/cmd/signal.go
@@ -6,7 +6,7 @@ import (
"os/signal"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/unix"
)
@@ -17,7 +17,7 @@ type signalHandler struct {
// services are the services that are shut down before application
// exiting.
- services []agd.Service
+ services []agdservice.Interface
}
// newSignalHandler returns a new signalHandler that shuts down services.
@@ -32,7 +32,7 @@ func newSignalHandler() (h signalHandler) {
}
// add adds a service to the signal handler.
-func (h *signalHandler) add(s agd.Service) {
+func (h *signalHandler) add(s agdservice.Interface) {
h.services = append(h.services, s)
}
diff --git a/internal/cmd/tls.go b/internal/cmd/tls.go
index e560058..1bc88be 100644
--- a/internal/cmd/tls.go
+++ b/internal/cmd/tls.go
@@ -7,15 +7,17 @@ import (
"fmt"
"os"
"path/filepath"
+ "slices"
"strings"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/prometheus/client_golang/prometheus"
- "golang.org/x/exp/slices"
)
// TLS Configuration And Utilities
@@ -217,9 +219,6 @@ func newTicketRotator(grps []*agd.ServerGroup) (tr *ticketRotator, err error) {
return tr, nil
}
-// type check
-var _ agd.Refresher = (*ticketRotator)(nil)
-
// sessTickLen is the length of a single TLS session ticket key in bytes.
//
// NOTE: Unlike Nginx, Go's crypto/tls doesn't use the random bytes from the
@@ -227,7 +226,10 @@ var _ agd.Refresher = (*ticketRotator)(nil)
// 48 bytes of the hashed data as the key name, the AES key, and the HMAC key.
const sessTickLen = 32
-// Refresh implements the agd.Refresher interface for *ticketRotator.
+// type check
+var _ agdservice.Refresher = (*ticketRotator)(nil)
+
+// Refresh implements the [agdservice.Refresher] interface for *ticketRotator.
func (r *ticketRotator) Refresh(_ context.Context) (err error) {
for conf, files := range r.confs {
keys := make([][sessTickLen]byte, 0, len(files))
@@ -299,14 +301,14 @@ func enableTLSKeyLogging(grps []*agd.ServerGroup, keyLogFileName string) (err er
func setupTicketRotator(
srvGrps []*agd.ServerGroup,
sigHdlr signalHandler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (err error) {
tickRot, err := newTicketRotator(srvGrps)
if err != nil {
return fmt.Errorf("setting up ticket rotator: %w", err)
}
- refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: tickRot,
ErrColl: errColl,
@@ -315,8 +317,9 @@ func setupTicketRotator(
Interval: 1 * time.Minute,
RefreshOnShutdown: false,
RoutineLogsAreDebug: true,
+ RandomizeStart: false,
})
- err = refr.Start()
+ err = refr.Start(context.Background())
if err != nil {
return fmt.Errorf("starting ticket rotator refresh: %w", err)
}
diff --git a/internal/cmd/upstream.go b/internal/cmd/upstream.go
index 9380031..24eae49 100644
--- a/internal/cmd/upstream.go
+++ b/internal/cmd/upstream.go
@@ -8,41 +8,39 @@ import (
"strings"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
)
// DNS upstream configuration
-// upstreamConfig module configuration
+// upstreamConfig is the upstream module configuration.
type upstreamConfig struct {
// Healthcheck contains the upstream healthcheck configuration.
Healthcheck *upstreamHealthcheckConfig `yaml:"healthcheck"`
- // Server is the upstream url of the server we're using to forward DNS
- // queries. It starts with tcp://, udp://, or with an IP address.
- Server string `yaml:"server"`
+ // Fallback is the configuration for the upstream fallback servers.
+ Fallback *upstreamFallbackConfig `yaml:"fallback"`
- // FallbackServers is a list of the DNS servers we're using to fallback to
- // when the upstream server fails to respond
- FallbackServers []netip.AddrPort `yaml:"fallback"`
-
- // Timeout is the timeout for DNS requests to the upstreams.
- Timeout timeutil.Duration `yaml:"timeout"`
+ // Servers is a list of the upstream servers configurations we use to
+ // forward DNS queries.
+ Servers []*upstreamServerConfig `yaml:"servers"`
}
// toInternal converts c to the data storage configuration for the DNS server.
-func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig, err error) {
- network, addrPort, err := splitUpstreamURL(c.Server)
- if err != nil {
- return nil, err
- }
+// c is assumed to be valid.
+func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig) {
+ upstreams := c.Servers
+ fallbacks := c.Fallback.Servers
- fallbacks := c.FallbackServers
- metricsListener := prometheus.NewForwardMetricsListener(len(fallbacks) + 1)
+ upsConfs := toUpstreamConfigs(upstreams)
+ fallbackConfs := toUpstreamConfigs(fallbacks)
+
+ metricsListener := prometheus.NewForwardMetricsListener(len(upstreams) + len(fallbacks))
var hcInit time.Duration
if c.Healthcheck.Enabled {
@@ -50,17 +48,15 @@ func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig, err error
}
fwdConf = &forward.HandlerConfig{
- Address: addrPort,
- Network: network,
MetricsListener: metricsListener,
HealthcheckDomainTmpl: c.Healthcheck.DomainTmpl,
- FallbackAddresses: c.FallbackServers,
- Timeout: c.Timeout.Duration,
+ UpstreamsAddresses: upsConfs,
+ FallbackAddresses: fallbackConfs,
HealthcheckBackoffDuration: c.Healthcheck.BackoffDuration.Duration,
HealthcheckInitDuration: hcInit,
}
- return fwdConf, nil
+ return fwdConf
}
// validate returns an error if the upstream configuration is invalid.
@@ -68,20 +64,20 @@ func (c *upstreamConfig) validate() (err error) {
switch {
case c == nil:
return errNilConfig
- case c.Server == "":
- return errors.Error("no server")
- case len(c.FallbackServers) == 0:
- return errors.Error("no fallback")
- case c.Timeout.Duration <= 0:
- return newMustBePositiveError("timeout", c.Timeout)
+ case len(c.Servers) == 0:
+ return errors.Error("no servers")
}
- err = validateAddrs(c.FallbackServers)
- if err != nil {
- return fmt.Errorf("fallback: %w", err)
+ for i, s := range c.Servers {
+ if err = s.validate(); err != nil {
+ return fmt.Errorf("servers: at index %d: %w", i, err)
+ }
}
- return errors.Annotate(c.Healthcheck.validate(), "healthcheck: %w")
+ return coalesceError(
+ validateProp("fallback", c.Fallback.validate),
+ validateProp("healthcheck", c.Healthcheck.validate),
+ )
}
// splitUpstreamURL separates server url to net protocol and port address.
@@ -163,13 +159,13 @@ func (c *upstreamHealthcheckConfig) validate() (err error) {
func newUpstreamHealthcheck(
handler *forward.Handler,
conf *upstreamConfig,
- errColl agd.ErrorCollector,
-) (refr agd.Service) {
+ errColl errcoll.Interface,
+) (refr agdservice.Interface) {
if !conf.Healthcheck.Enabled {
- return agd.EmptyService{}
+ return agdservice.Empty{}
}
- return agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ return agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(
context.Background(),
@@ -182,5 +178,76 @@ func newUpstreamHealthcheck(
Interval: conf.Healthcheck.Interval.Duration,
RefreshOnShutdown: false,
RoutineLogsAreDebug: true,
+ RandomizeStart: false,
})
}
+
+// upstreamFallbackConfig is the configuration for the upstream fallback
+// servers.
+type upstreamFallbackConfig struct {
+ // Servers is a list of the upstream servers configurations we use to
+ // fallback when the upstream servers fail to respond.
+ Servers []*upstreamServerConfig `yaml:"servers"`
+}
+
+// validate returns an error if the upstream fallback configuration is invalid.
+func (c *upstreamFallbackConfig) validate() (err error) {
+ switch {
+ case c == nil:
+ return errNilConfig
+ case len(c.Servers) == 0:
+ return errors.Error("no servers")
+ }
+
+ for i, s := range c.Servers {
+ if err = s.validate(); err != nil {
+ return fmt.Errorf("servers: at index %d: %w", i, err)
+ }
+ }
+
+ return nil
+}
+
+// upstreamServerConfig is the configuration for the upstream server.
+type upstreamServerConfig struct {
+ // Address is the url of the DNS server in the `[scheme://]ip:port`
+ // format.
+ Address string `yaml:"address"`
+
+ // Timeout is the timeout for DNS requests.
+ Timeout timeutil.Duration `yaml:"timeout"`
+}
+
+// validate returns an error if the upstream server configuration is invalid.
+func (c *upstreamServerConfig) validate() (err error) {
+ switch {
+ case c == nil:
+ return errNilConfig
+ case c.Timeout.Duration <= 0:
+ return newMustBePositiveError("timeout", c.Timeout)
+ }
+
+ _, _, err = splitUpstreamURL(c.Address)
+ if err != nil {
+ return fmt.Errorf("invalid addr: %s", c.Address)
+ }
+
+ return nil
+}
+
+// toUpstreamConfigs converts confs to the list of upstream configurations.
+// confs must be valid.
+func toUpstreamConfigs(confs []*upstreamServerConfig) (upsConfs []*forward.UpstreamPlainConfig) {
+ upsConfs = make([]*forward.UpstreamPlainConfig, 0, len(confs))
+ for _, c := range confs {
+ net, addrPort, _ := splitUpstreamURL(c.Address)
+
+ upsConfs = append(upsConfs, &forward.UpstreamPlainConfig{
+ Network: net,
+ Address: addrPort,
+ Timeout: c.Timeout.Duration,
+ })
+ }
+
+ return upsConfs
+}
diff --git a/internal/cmd/validation.go b/internal/cmd/validation.go
index a5bba44..1b3a164 100644
--- a/internal/cmd/validation.go
+++ b/internal/cmd/validation.go
@@ -19,6 +19,17 @@ func validatePositive[T numberOrDuration](prop string, v T) (err error) {
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 {
diff --git a/internal/cmd/websvc.go b/internal/cmd/websvc.go
index 337aeec..a294334 100644
--- a/internal/cmd/websvc.go
+++ b/internal/cmd/websvc.go
@@ -9,12 +9,12 @@ import (
"os"
"path"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/websvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil"
)
@@ -33,7 +33,7 @@ type webConfig struct {
// RootRedirectURL is the URL to which non-DNS and non-Debug HTTP requests
// are redirected. If not set, a 404 page is shown.
- RootRedirectURL *agdhttp.URL `yaml:"root_redirect_url"`
+ RootRedirectURL *urlutil.URL `yaml:"root_redirect_url"`
// StaticContent is the content that is served statically at the given
// paths. If not set, no static content is shown.
@@ -60,7 +60,7 @@ type webConfig struct {
func (c *webConfig) toInternal(
envs *environments,
dnsCk http.Handler,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (conf *websvc.Config, err error) {
if c == nil {
return nil, nil
@@ -179,7 +179,7 @@ type linkedIPServer struct {
// toInternal converts s to a linkedIP server configuration. s is assumed to be
// valid.
func (s *linkedIPServer) toInternal(
- targetURL *agdhttp.URL,
+ targetURL *urlutil.URL,
) (srv *websvc.LinkedIPServer, err error) {
if s == nil {
return nil, nil
diff --git a/internal/connlimiter/conn.go b/internal/connlimiter/conn.go
index eb61a9a..eb08c32 100644
--- a/internal/connlimiter/conn.go
+++ b/internal/connlimiter/conn.go
@@ -18,9 +18,9 @@ import (
type limitConn struct {
net.Conn
+ serverInfo *dnsserver.ServerInfo
decrement func()
start time.Time
- serverInfo dnsserver.ServerInfo
isClosed atomic.Bool
}
diff --git a/internal/connlimiter/limiter.go b/internal/connlimiter/limiter.go
index 797345b..161a65e 100644
--- a/internal/connlimiter/limiter.go
+++ b/internal/connlimiter/limiter.go
@@ -53,7 +53,7 @@ func New(c *Config) (l *Limiter, err error) {
// 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) {
+func (l *Limiter) Limit(lsnr net.Listener, srvInfo *dnsserver.ServerInfo) (limited net.Listener) {
name, addr := srvInfo.Name, srvInfo.Addr
proto := srvInfo.Proto.String()
diff --git a/internal/connlimiter/limiter_test.go b/internal/connlimiter/limiter_test.go
index 840f4ce..dc68765 100644
--- a/internal/connlimiter/limiter_test.go
+++ b/internal/connlimiter/limiter_test.go
@@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
const testTimeout = 1 * time.Second
// testServerInfo is the common server information for tests.
-var testServerInfo = dnsserver.ServerInfo{
+var testServerInfo = &dnsserver.ServerInfo{
Name: "test_server",
Addr: "127.0.0.1:0",
Proto: agd.ProtoDoT,
diff --git a/internal/connlimiter/listener.go b/internal/connlimiter/listener.go
index 59d1f97..2a60849 100644
--- a/internal/connlimiter/listener.go
+++ b/internal/connlimiter/listener.go
@@ -20,6 +20,10 @@ import (
type limitListener struct {
net.Listener
+ // serverInfo is used for logging and metrics in both the listener itself
+ // and in its conns. It's never nil.
+ serverInfo *dnsserver.ServerInfo
+
// counterCond is the condition variable that protects counter and isClosed
// through its locker, as well as signals when connections can be accepted
// again or when the listener has been closed.
@@ -35,10 +39,6 @@ type limitListener struct {
// waiting for an accept.
waitingHist prometheus.Observer
- // serverInfo is used for logging and metrics in both the listener itself
- // and in its conns.
- serverInfo dnsserver.ServerInfo
-
// isClosed shows whether this listener has been closed.
isClosed bool
}
diff --git a/internal/consul/allowlist.go b/internal/consul/allowlist.go
index 6428313..6627099 100644
--- a/internal/consul/allowlist.go
+++ b/internal/consul/allowlist.go
@@ -10,8 +10,8 @@ import (
"net/url"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
@@ -48,9 +48,10 @@ func NewAllowlistRefresher(
}
// type check
-var _ agd.Refresher = (*AllowlistRefresher)(nil)
+var _ agdservice.Refresher = (*AllowlistRefresher)(nil)
-// Refresh implements the agd.Refresher interface for *AllowlistRefresher.
+// Refresh implements the [agdservice.Refresher] interface for
+// *AllowlistRefresher.
func (l *AllowlistRefresher) Refresh(ctx context.Context) (err error) {
defer func() {
metrics.ConsulAllowlistUpdateTime.SetToCurrentTime()
diff --git a/internal/debugsvc/debugsvc.go b/internal/debugsvc/debugsvc.go
index 39e7b12..ee2f935 100644
--- a/internal/debugsvc/debugsvc.go
+++ b/internal/debugsvc/debugsvc.go
@@ -7,7 +7,7 @@ import (
"io"
"net/http"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/pprofutil"
@@ -69,11 +69,12 @@ func startServer(s *server) {
}
// type check
-var _ agd.Service = (*Service)(nil)
+var _ agdservice.Interface = (*Service)(nil)
-// Start implements the agd.Service interface for *Service. It starts serving
-// all endpoints. err is always nil, if any endpoint fails to start, it panics.
-func (svc *Service) Start() (err error) {
+// Start implements the [agdservice.Interface] interface for *Service. It
+// starts serving all endpoints. err is always nil, if any endpoint fails to
+// start, it panics.
+func (svc *Service) Start(_ context.Context) (err error) {
for _, srv := range svc.servers {
go startServer(srv)
}
@@ -81,8 +82,8 @@ func (svc *Service) Start() (err error) {
return nil
}
-// Shutdown implements the agd.Service interface for *Service. It stops serving
-// all endpoints.
+// Shutdown implements the [agdservice.Interface] interface for *Service. It
+// stops serving all endpoints.
func (svc *Service) Shutdown(ctx context.Context) (err error) {
srvNum := 0
for _, srv := range svc.servers {
diff --git a/internal/debugsvc/debugsvc_test.go b/internal/debugsvc/debugsvc_test.go
index 6f1ac89..fa339f2 100644
--- a/internal/debugsvc/debugsvc_test.go
+++ b/internal/debugsvc/debugsvc_test.go
@@ -1,13 +1,13 @@
package debugsvc_test
import (
- "context"
"fmt"
"io"
"net/http"
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
@@ -18,6 +18,9 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
+// testTimeout is a common timeout for tests.
+const testTimeout = 1 * time.Second
+
func TestService_Start(t *testing.T) {
// TODO(a.garipov): Consider adding an HTTP server constructor as a part of
// the configuration structure to use net/http/httptest's server in tests.
@@ -42,12 +45,11 @@ func TestService_Start(t *testing.T) {
var err error
require.NotPanics(t, func() {
- err = svc.Start()
+ err = svc.Start(agdtest.ContextWithTimeout(t, testTimeout))
})
require.NoError(t, err)
-
testutil.CleanupAndRequireSuccess(t, func() (err error) {
- return svc.Shutdown(context.Background())
+ return svc.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
})
client := http.Client{
diff --git a/internal/dnscheck/consul.go b/internal/dnscheck/consul.go
index 7817ddc..a4b9e9a 100644
--- a/internal/dnscheck/consul.go
+++ b/internal/dnscheck/consul.go
@@ -7,13 +7,14 @@ import (
"net/http"
"net/netip"
"net/url"
+ "slices"
"sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
@@ -22,7 +23,6 @@ import (
"github.com/miekg/dns"
cache "github.com/patrickmn/go-cache"
"github.com/prometheus/client_golang/prometheus"
- "golang.org/x/exp/slices"
"golang.org/x/time/rate"
)
@@ -40,7 +40,7 @@ type Consul struct {
kv consulKV
messages *dnsmsg.Constructor
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
domains []string
nodeLocation string
@@ -65,7 +65,7 @@ type ConsulConfig struct {
// ErrColl is the error collector that is used to collect non-critical
// errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// Domains are the lower-cased domain names used to detect DNS check requests.
Domains []string
@@ -149,6 +149,8 @@ func (cc *Consul) Check(
) (resp *dns.Msg, err error) {
var matched bool
defer func() {
+ incErrMetrics("dns", err)
+
if !matched {
return
}
@@ -171,14 +173,13 @@ func (cc *Consul) Check(
return cc.resp(ri, req)
}
- si := dnsserver.MustServerInfoFromContext(ctx)
- inf := cc.newInfo(si.Proto.String(), ri)
+ inf := cc.newInfo(ri)
cc.addToCache(randomID, inf)
err = cc.kv.set(ctx, randomID, inf)
if err != nil {
- agd.Collectf(ctx, cc.errColl, "dnscheck: consul setting: %w", err)
+ errcoll.Collectf(ctx, cc.errColl, "dnscheck: consul setting: %w", httpKVError{err: err})
}
return cc.resp(ri, req)
@@ -195,12 +196,12 @@ func (cc *Consul) addToCache(randomID string, inf *info) {
// newInfo returns an information record with all available data about the
// server and the request. ri must not be nil.
-func (cc *Consul) newInfo(protoStr string, ri *agd.RequestInfo) (inf *info) {
+func (cc *Consul) newInfo(ri *agd.RequestInfo) (inf *info) {
inf = &info{
ServerGroupName: ri.ServerGroup,
ServerName: ri.Server,
- Protocol: protoStr,
+ Protocol: ri.Proto.String(),
NodeLocation: cc.nodeLocation,
NodeName: cc.nodeName,
@@ -309,13 +310,10 @@ func (cc *Consul) serveCheckTest(ctx context.Context, w http.ResponseWriter, r *
err = json.NewEncoder(w).Encode(inf)
if err != nil {
- agd.Collectf(ctx, cc.errColl, "dnscheck: http resp write error: %w", err)
+ errcoll.Collectf(ctx, cc.errColl, "dnscheck: http resp write error: %w", err)
}
}
-// errRateLimited is returned by Consul.info when the request is rate limited.
-const errRateLimited errors.Error = "rate limited"
-
// info returns an information record by the random request ID.
func (cc *Consul) info(ctx context.Context, randomID string) (inf *info, err error) {
defer func() {
@@ -323,6 +321,8 @@ func (cc *Consul) info(ctx context.Context, randomID string) (inf *info, err err
"type": "http",
"valid": metrics.BoolString(err == nil),
}).Inc()
+
+ incErrMetrics("http", err)
}()
cc.mu.Lock()
@@ -335,7 +335,7 @@ func (cc *Consul) info(ctx context.Context, randomID string) (inf *info, err err
inf, err = cc.kv.get(ctx, randomID)
if err != nil {
- agd.Collectf(ctx, cc.errColl, "dnscheck: consul getting: %w", err)
+ errcoll.Collectf(ctx, cc.errColl, "dnscheck: consul getting: %w", httpKVError{err: err})
return nil, fmt.Errorf("getting from consul: %w", err)
}
diff --git a/internal/dnscheck/consul_test.go b/internal/dnscheck/consul_test.go
index ec2e5d2..e166c5c 100644
--- a/internal/dnscheck/consul_test.go
+++ b/internal/dnscheck/consul_test.go
@@ -16,7 +16,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@@ -110,10 +109,6 @@ func TestConsul_ServeHTTP(t *testing.T) {
require.NoError(t, err)
ctx := context.Background()
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{
- Name: theOnlyVal["server_name"].(string),
- Proto: agd.ProtoDNS,
- })
var resp *dns.Msg
resp, err = dnsCk.Check(
@@ -131,6 +126,7 @@ func TestConsul_ServeHTTP(t *testing.T) {
Host: randomid + "-" + checkDomain,
RemoteIP: testRemoteIP,
QType: dns.TypeA,
+ Proto: agd.ProtoDNS,
},
)
require.NoError(t, err)
@@ -221,7 +217,7 @@ func TestConsul_Check(t *testing.T) {
}}
conf := &dnscheck.ConsulConfig{
- Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
+ Messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
Domains: []string{checkDomain},
IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
IPv6: []netip.Addr{netip.MustParseAddr("1234::5678")},
@@ -231,10 +227,6 @@ func TestConsul_Check(t *testing.T) {
require.NoError(t, err)
ctx := context.Background()
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{
- Name: "test-server-name",
- Proto: agd.ProtoDNS,
- })
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -248,7 +240,8 @@ func TestConsul_Check(t *testing.T) {
Host: tc.host,
RemoteIP: testRemoteIP,
QType: tc.qtype,
- Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
+ Messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
+ Proto: agd.ProtoDNS,
}
resp, cErr := dnsCk.Check(ctx, req, ri)
diff --git a/internal/dnscheck/error.go b/internal/dnscheck/error.go
new file mode 100644
index 0000000..81bcc8a
--- /dev/null
+++ b/internal/dnscheck/error.go
@@ -0,0 +1,64 @@
+package dnscheck
+
+import (
+ "context"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
+ "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
+ "github.com/AdguardTeam/golibs/errors"
+)
+
+// errRateLimited is returned by [Consul.info] when the request is rate limited.
+const errRateLimited errors.Error = "rate limited"
+
+// httpKVError is an error returned by the Consul KV database HTTP client.
+type httpKVError struct {
+ err error
+}
+
+// type check
+var _ error = httpKVError{}
+
+// Error implements the error interface for httpKVError.
+func (err httpKVError) Error() (msg string) {
+ return err.err.Error()
+}
+
+// type check
+var _ errors.Wrapper = httpKVError{}
+
+// Unwrap implements the [errors.Wrapper] interface for httpKVError.
+func (err httpKVError) Unwrap() (unwrapped error) {
+ return err.err
+}
+
+// type check
+var _ errcoll.SentryReportableError = httpKVError{}
+
+// IsSentryReportable implements the [errcoll.SentryReportableError] interface
+// for httpKVError.
+func (err httpKVError) IsSentryReportable() (ok bool) {
+ return !errors.Is(err.err, errRateLimited) &&
+ !errors.Is(err.err, context.Canceled) &&
+ !errors.Is(err.err, context.DeadlineExceeded)
+}
+
+// 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, errRateLimited):
+ errType = "ratelimit"
+ default:
+ errType = "other"
+ }
+
+ metrics.DNSCheckErrorTotal.WithLabelValues(src, errType).Inc()
+}
diff --git a/internal/dnscheck/httpkv_test.go b/internal/dnscheck/httpkv_test.go
index 82923f8..34ed17c 100644
--- a/internal/dnscheck/httpkv_test.go
+++ b/internal/dnscheck/httpkv_test.go
@@ -18,7 +18,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
@@ -152,11 +151,6 @@ func TestHTTPKV(t *testing.T) {
dnsCk, err := dnscheck.NewConsul(conf)
require.NoError(t, err)
- ctx := context.Background()
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{
- Proto: agd.ProtoDNS,
- })
-
req := dnsservertest.CreateMessage(randomid+"-"+localDomain, dns.TypeA)
ri := &agd.RequestInfo{
Device: &agd.Device{ID: "some-device-id"},
@@ -166,8 +160,10 @@ func TestHTTPKV(t *testing.T) {
Host: randomid + "-" + localDomain,
RemoteIP: testRemoteIP,
QType: dns.TypeA,
+ Proto: agd.ProtoDNS,
}
+ ctx := context.Background()
_, err = dnsCk.Check(ctx, req, ri)
require.NoError(t, err)
dnscheck.FlushConsulCache(t, dnsCk)
diff --git a/internal/dnsdb/dnsdb.go b/internal/dnsdb/dnsdb.go
index 0d51ac7..fa6c326 100644
--- a/internal/dnsdb/dnsdb.go
+++ b/internal/dnsdb/dnsdb.go
@@ -13,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"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"
)
@@ -37,14 +38,14 @@ func (Empty) Record(_ context.Context, _ *dns.Msg, _ *agd.RequestInfo) {}
// Default is the default DNSDB implementation.
type Default struct {
buffer *atomic.Pointer[buffer]
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
maxSize int
}
// DefaultConfig is the default DNS database configuration structure.
type DefaultConfig struct {
// ErrColl is used to collect HTTP errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// MaxSize is the maximum amount of records in the memory buffer.
MaxSize int
diff --git a/internal/dnsdb/http.go b/internal/dnsdb/http.go
index f63aafd..ace0110 100644
--- a/internal/dnsdb/http.go
+++ b/internal/dnsdb/http.go
@@ -8,8 +8,8 @@ import (
"net/http"
"strings"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
)
@@ -33,7 +33,7 @@ func (db *Default) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
if err != nil {
h.Set(httphdr.XError, err.Error())
- agd.Collectf(ctx, db.errColl, "dnsdb: http handler error: %w", err)
+ errcoll.Collectf(ctx, db.errColl, "dnsdb: http handler error: %w", err)
}
}()
diff --git a/internal/dnsmsg/blockingmode.go b/internal/dnsmsg/blockingmode.go
index 31ad638..a7b68cf 100644
--- a/internal/dnsmsg/blockingmode.go
+++ b/internal/dnsmsg/blockingmode.go
@@ -1,11 +1,7 @@
package dnsmsg
import (
- "encoding/json"
- "fmt"
"net/netip"
-
- "github.com/AdguardTeam/golibs/errors"
)
// BlockingMode is a sum type of all possible ways to construct blocked or
@@ -19,153 +15,6 @@ type BlockingMode interface {
isBlockingMode()
}
-// BlockingModeCodec is a wrapper around a BlockingMode that implements the
-// [json.Marshaler] and [json.Unmarshaler] interfaces.
-//
-// TODO(s.chzhen): Remove once it's not used anymore.
-type BlockingModeCodec struct {
- Mode BlockingMode
-}
-
-// Blocking mode type names.
-const (
- bmTypeCustomIP = "custom_ip"
- bmTypeNullIP = "null_ip"
- bmTypeNXDOMAIN = "nxdomain"
- bmTypeREFUSED = "refused"
-)
-
-// type check
-var _ json.Marshaler = BlockingModeCodec{}
-
-// MarshalJSON implements the [json.Marshaler] interface for BlockingModeCodec.
-func (c BlockingModeCodec) MarshalJSON() (b []byte, err error) {
- var j *blockingModeJSON
- switch m := c.Mode.(type) {
- case nil:
- return nil, errors.Error("nil blocking mode")
- case *BlockingModeCustomIP:
- j = &blockingModeJSON{
- Type: bmTypeCustomIP,
- }
-
- if m.IPv4.IsValid() {
- j.IPv4 = &m.IPv4
- }
-
- if m.IPv6.IsValid() {
- j.IPv6 = &m.IPv6
- }
- case *BlockingModeNullIP:
- j = &blockingModeJSON{
- Type: bmTypeNullIP,
- }
- case *BlockingModeNXDOMAIN:
- j = &blockingModeJSON{
- Type: bmTypeNXDOMAIN,
- }
- case *BlockingModeREFUSED:
- j = &blockingModeJSON{
- Type: bmTypeREFUSED,
- }
- default:
- return nil, fmt.Errorf("unexpected blocking mode %T(%[1]v)", m)
- }
-
- return json.Marshal(j)
-}
-
-// type check
-var _ json.Unmarshaler = (*BlockingModeCodec)(nil)
-
-// blockingModeJSON contains common fields for all BlockingMode JSON object
-// properties.
-type blockingModeJSON struct {
- IPv4 *netip.Addr `json:"ipv4,omitempty"`
- IPv6 *netip.Addr `json:"ipv6,omitempty"`
- Type string `json:"type"`
-}
-
-// UnmarshalJSON implements the [json.Unmarshaler] interface for
-// *BlockingModeCodec.
-func (c *BlockingModeCodec) UnmarshalJSON(b []byte) (err error) {
- j := &blockingModeJSON{}
- err = json.Unmarshal(b, j)
- if err != nil {
- // Don't wrap the error, because it's the main JSON one.
- return err
- }
-
- switch t := j.Type; t {
- case bmTypeCustomIP:
- var m *BlockingModeCustomIP
- m, err = j.toCustomIP()
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return err
- }
-
- c.Mode = m
- case bmTypeNullIP:
- c.Mode = &BlockingModeNullIP{}
- case bmTypeNXDOMAIN:
- c.Mode = &BlockingModeNXDOMAIN{}
- case bmTypeREFUSED:
- c.Mode = &BlockingModeREFUSED{}
- default:
- return fmt.Errorf("unexpected blocking mode type %q", t)
- }
-
- return nil
-}
-
-// toCustomIP converts j into a correct *BlockingModeCustomIP. j.Type should be
-// [bmTypeCustomIP].
-func (j *blockingModeJSON) toCustomIP() (m *BlockingModeCustomIP, err error) {
- defer func() {
- err = errors.Annotate(err, "bad options for blocking mode %q: %w", bmTypeCustomIP)
- }()
-
- ipv4Ptr, ipv6Ptr := j.IPv4, j.IPv6
- if ipv4Ptr == nil && ipv6Ptr == nil {
- return nil, errors.Error("ipv4 or ipv6 must be set")
- }
-
- m = &BlockingModeCustomIP{}
- m.IPv4, err = decodeCustomIP(ipv4Ptr, netip.Addr.Is4, "ipv4")
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return nil, err
- }
-
- m.IPv6, err = decodeCustomIP(ipv6Ptr, netip.Addr.Is6, "ipv6")
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return nil, err
- }
-
- return m, nil
-}
-
-// decodeCustomIP is a helper that dereferences ipPtr, if it is not nil, checks
-// the resulting IP address, and returns an informative error if necessary.
-func decodeCustomIP(
- ipPtr *netip.Addr,
- isCorrectProto func(netip.Addr) bool,
- protoName string,
-) (ip netip.Addr, err error) {
- if ipPtr == nil {
- return netip.Addr{}, nil
- }
-
- ip = *ipPtr
- if !isCorrectProto(ip) {
- return netip.Addr{}, fmt.Errorf("address %q is not %s", ip, protoName)
- }
-
- return ip, nil
-}
-
// BlockingModeCustomIP makes the [dnsmsg.Constructor] return responses with
// custom IP addresses to A and AAAA requests. For all other types of requests,
// as well as if one of the addresses isn't set, it returns a response with no
diff --git a/internal/dnsmsg/blockingmode_example_test.go b/internal/dnsmsg/blockingmode_example_test.go
deleted file mode 100644
index 5f8b537..0000000
--- a/internal/dnsmsg/blockingmode_example_test.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package dnsmsg_test
-
-import (
- "encoding/json"
- "fmt"
- "net/netip"
- "os"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
-)
-
-func ExampleBlockingModeCodec_MarshalJSON() {
- enc := json.NewEncoder(os.Stdout)
- enc.SetIndent("", " ")
-
- fmt.Println("Custom IP:")
- err := enc.Encode(&dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeCustomIP{
- IPv4: netip.MustParseAddr("1.2.3.4"),
- IPv6: netip.MustParseAddr("1234::cdef"),
- },
- })
- if err != nil {
- panic(err)
- }
-
- fmt.Println("Null IP:")
- err = enc.Encode(&dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- })
- if err != nil {
- panic(err)
- }
-
- fmt.Println("NXDOMAIN:")
- err = enc.Encode(&dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNXDOMAIN{},
- })
- if err != nil {
- panic(err)
- }
-
- fmt.Println("REFUSED:")
- err = enc.Encode(&dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeREFUSED{},
- })
- if err != nil {
- panic(err)
- }
-
- // Output:
- // Custom IP:
- // {
- // "ipv4": "1.2.3.4",
- // "ipv6": "1234::cdef",
- // "type": "custom_ip"
- // }
- // Null IP:
- // {
- // "type": "null_ip"
- // }
- // NXDOMAIN:
- // {
- // "type": "nxdomain"
- // }
- // REFUSED:
- // {
- // "type": "refused"
- // }
-}
-
-func ExampleBlockingModeCodec_UnmarshalJSON() {
- c := &dnsmsg.BlockingModeCodec{}
-
- fmt.Println("Custom IP:")
- err := json.Unmarshal([]byte(`{
- "type":"custom_ip",
- "ipv4":"1.2.3.4",
- "ipv6":"1234::cdef"
- }`), c)
- if err != nil {
- panic(err)
- }
-
- fmt.Printf("%T(%+[1]v)\n", c.Mode)
-
- fmt.Println("Null IP:")
- err = json.Unmarshal([]byte(`{
- "type":"null_ip"
- }`), c)
- if err != nil {
- panic(err)
- }
-
- fmt.Printf("%T(%+[1]v)\n", c.Mode)
-
- fmt.Println("NXDOMAIN:")
- err = json.Unmarshal([]byte(`{
- "type":"nxdomain"
- }`), c)
- if err != nil {
- panic(err)
- }
-
- fmt.Printf("%T(%+[1]v)\n", c.Mode)
-
- fmt.Println("REFUSED:")
- err = json.Unmarshal([]byte(`{
- "type":"refused"
- }`), c)
- if err != nil {
- panic(err)
- }
-
- fmt.Printf("%T(%+[1]v)\n", c.Mode)
-
- // Output:
- // Custom IP:
- // *dnsmsg.BlockingModeCustomIP(&{IPv4:1.2.3.4 IPv6:1234::cdef})
- // Null IP:
- // *dnsmsg.BlockingModeNullIP(&{})
- // NXDOMAIN:
- // *dnsmsg.BlockingModeNXDOMAIN(&{})
- // REFUSED:
- // *dnsmsg.BlockingModeREFUSED(&{})
-}
-
-func ExampleBlockingModeCodec_UnmarshalJSON_invalid() {
- c := &dnsmsg.BlockingModeCodec{}
-
- err := json.Unmarshal([]byte(`{
- "type":"bad_type"
- }`), c)
- fmt.Println(err)
-
- err = json.Unmarshal([]byte(`{
- "type":"custom_ip"
- }`), c)
- fmt.Println(err)
-
- err = json.Unmarshal([]byte(`{
- "type":"custom_ip",
- "ipv4":"1234::cdef"
- }`), c)
- fmt.Println(err)
-
- err = json.Unmarshal([]byte(`{
- "type":"custom_ip",
- "ipv6":"1.2.3.4"
- }`), c)
- fmt.Println(err)
-
- // Output:
- // unexpected blocking mode type "bad_type"
- // bad options for blocking mode "custom_ip": ipv4 or ipv6 must be set
- // bad options for blocking mode "custom_ip": address "1234::cdef" is not ipv4
- // bad options for blocking mode "custom_ip": address "1.2.3.4" is not ipv6
-}
diff --git a/internal/dnsmsg/cloner.go b/internal/dnsmsg/cloner.go
index 89f685e..7f82d4a 100644
--- a/internal/dnsmsg/cloner.go
+++ b/internal/dnsmsg/cloner.go
@@ -1,36 +1,36 @@
package dnsmsg
import (
- "fmt"
- "net"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
- "golang.org/x/exp/slices"
)
// Cloner is a pool that can clone common parts of DNS messages with fewer
// allocations.
//
-// TODO(a.garipov): Add ECS/OPT.
+// TODO(a.garipov): Use in filtering when cloning a [filter.ResultModified]
+// message.
//
-// TODO(a.garipov): Use.
-//
-// TODO(a.garipov): Consider merging into [Constructor].
+// TODO(a.garipov): Use in [Constructor].
type Cloner struct {
+ // Statistics.
+
+ stat ClonerStat
+
// Top-level structures.
- msg *agdsync.TypedPool[dns.Msg]
- question *agdsync.TypedPool[[]dns.Question]
+ msg *syncutil.Pool[dns.Msg]
// Mostly-answer structures.
- a *agdsync.TypedPool[dns.A]
- aaaa *agdsync.TypedPool[dns.AAAA]
- cname *agdsync.TypedPool[dns.CNAME]
- ptr *agdsync.TypedPool[dns.PTR]
- srv *agdsync.TypedPool[dns.SRV]
- txt *agdsync.TypedPool[dns.TXT]
+ a *syncutil.Pool[dns.A]
+ aaaa *syncutil.Pool[dns.AAAA]
+ cname *syncutil.Pool[dns.CNAME]
+ mx *syncutil.Pool[dns.MX]
+ ptr *syncutil.Pool[dns.PTR]
+ srv *syncutil.Pool[dns.SRV]
+ txt *syncutil.Pool[dns.TXT]
// Mostly-answer custom cloners.
@@ -38,57 +38,62 @@ type Cloner struct {
// Mostly-NS structures.
- soa *agdsync.TypedPool[dns.SOA]
+ soa *syncutil.Pool[dns.SOA]
+
+ // Mostly-extra custom cloners.
+
+ opt *optCloner
}
// NewCloner returns a new properly initialized *Cloner.
-func NewCloner() (c *Cloner) {
+func NewCloner(stat ClonerStat) (c *Cloner) {
return &Cloner{
- msg: agdsync.NewTypedPool(func() (v *dns.Msg) {
- return &dns.Msg{}
- }),
- question: agdsync.NewTypedPool(func() (v *[]dns.Question) {
- q := make([]dns.Question, 1)
+ stat: stat,
- return &q
+ msg: syncutil.NewPool(func() (v *dns.Msg) {
+ return &dns.Msg{
+ // Allocate the question, since pretty much all DNS messages
+ // that are processed by DNS require exactly one.
+ Question: make([]dns.Question, 1),
+ }
}),
- a: agdsync.NewTypedPool(func() (v *dns.A) {
+ a: syncutil.NewPool(func() (v *dns.A) {
return &dns.A{}
}),
- aaaa: agdsync.NewTypedPool(func() (v *dns.AAAA) {
+ aaaa: syncutil.NewPool(func() (v *dns.AAAA) {
return &dns.AAAA{}
}),
- cname: agdsync.NewTypedPool(func() (v *dns.CNAME) {
+ cname: syncutil.NewPool(func() (v *dns.CNAME) {
return &dns.CNAME{}
}),
- ptr: agdsync.NewTypedPool(func() (v *dns.PTR) {
+ mx: syncutil.NewPool(func() (v *dns.MX) {
+ return &dns.MX{}
+ }),
+ ptr: syncutil.NewPool(func() (v *dns.PTR) {
return &dns.PTR{}
}),
- srv: agdsync.NewTypedPool(func() (v *dns.SRV) {
+ srv: syncutil.NewPool(func() (v *dns.SRV) {
return &dns.SRV{}
}),
- txt: agdsync.NewTypedPool(func() (v *dns.TXT) {
+ txt: syncutil.NewPool(func() (v *dns.TXT) {
return &dns.TXT{}
}),
https: newHTTPSCloner(),
- soa: agdsync.NewTypedPool(func() (v *dns.SOA) {
+ soa: syncutil.NewPool(func() (v *dns.SOA) {
return &dns.SOA{}
}),
+
+ opt: newOPTCloner(),
}
}
-// Clone returns a deep clone of msg. full is true if msg was cloned entirely
-// without the use of [dns.Copy].
-//
-// msg must have exactly one question.
-//
-// TODO(a.garipov): Don't require one question?
-func (c *Cloner) Clone(msg *dns.Msg) (clone *dns.Msg, full bool) {
+// Clone returns a deep clone of msg.
+func (c *Cloner) Clone(msg *dns.Msg) (clone *dns.Msg) {
if msg == nil {
- return nil, true
+ return nil
}
clone = c.msg.Get()
@@ -96,13 +101,80 @@ func (c *Cloner) Clone(msg *dns.Msg) (clone *dns.Msg, full bool) {
clone.MsgHdr = msg.MsgHdr
clone.Compress = msg.Compress
- clone.Question = *c.question.Get()
- clone.Question[0] = msg.Question[0]
+ clone.Question = appendIfNotNil(clone.Question[:0], msg.Question)
- clone.Answer, full = c.appendAnswer(clone.Answer[:0], msg.Answer)
+ var ansFull, nsFull, exFull bool
+ clone.Answer, ansFull = c.appendAnswer(clone.Answer[:0], msg.Answer)
+ clone.Ns, nsFull = c.appendNS(clone.Ns[:0], msg.Ns)
+ clone.Extra, exFull = c.appendExtra(clone.Extra[:0], msg.Extra)
- clone.Ns = clone.Ns[:0]
- for _, orig := range msg.Ns {
+ c.stat.OnClone(ansFull && nsFull && exFull)
+
+ return clone
+}
+
+// appendAnswer appends deep clones of all resource records from original to
+// clones and returns it.
+//
+// TODO(a.garipov): Consider ways of DRY'ing and merging with [Cloner.appendNS]
+// and [Cloner.appendExtra].
+func (c *Cloner) appendAnswer(clones, original []dns.RR) (res []dns.RR, full bool) {
+ if original == nil {
+ // TODO(a.garipov): This loses the RR slice in the message from the
+ // pool. Consider ways of mitigating that.
+ return nil, true
+ }
+
+ full = true
+ for _, orig := range original {
+ ansClone, ansFull := c.cloneAnswerRR(orig)
+ clones = append(clones, ansClone)
+ full = full && ansFull
+ }
+
+ return clones, full
+}
+
+// cloneAnswerRR returns a deep clone of orig. full is true if orig was
+// recognized.
+func (c *Cloner) cloneAnswerRR(orig dns.RR) (clone dns.RR, full bool) {
+ switch orig := orig.(type) {
+ case *dns.A:
+ clone = newANetIP(c, orig.A)
+ case *dns.AAAA:
+ clone = newAAAANetIP(c, orig.AAAA)
+ case *dns.CNAME:
+ clone = newCNAME(c, orig.Target)
+ case *dns.HTTPS:
+ return c.https.clone(orig)
+ case *dns.MX:
+ clone = newMX(c, orig.Mx, orig.Preference)
+ case *dns.PTR:
+ clone = newPTR(c, orig.Ptr)
+ case *dns.SRV:
+ clone = newSRV(c, orig.Target, orig.Priority, orig.Weight, orig.Port)
+ case *dns.TXT:
+ clone = newTXT(c, orig.Txt)
+ default:
+ return dns.Copy(orig), false
+ }
+
+ *clone.Header() = *orig.Header()
+
+ return clone, true
+}
+
+// appendNS appends deep clones of all resource records from original to
+// clones and returns it.
+func (c *Cloner) appendNS(clones, original []dns.RR) (res []dns.RR, full bool) {
+ if original == nil {
+ // TODO(a.garipov): This loses the RR slice in the message from the
+ // pool. Consider ways of mitigating that.
+ return nil, true
+ }
+
+ full = true
+ for _, orig := range original {
var nsClone dns.RR
switch orig := orig.(type) {
case *dns.SOA:
@@ -116,97 +188,55 @@ func (c *Cloner) Clone(msg *dns.Msg) (clone *dns.Msg, full bool) {
full = false
}
- clone.Ns = append(clone.Ns, nsClone)
+ clones = append(clones, nsClone)
}
- clone.Extra = clone.Extra[:0]
- for _, orig := range msg.Extra {
+ return clones, full
+}
+
+// appendExtra appends deep clones of all resource records from original to
+// clones and returns it.
+func (c *Cloner) appendExtra(clones, original []dns.RR) (res []dns.RR, full bool) {
+ if original == nil {
+ // TODO(a.garipov): This loses the RR slice in the message from the
+ // pool. Consider ways of mitigating that.
+ return nil, true
+ }
+
+ full = true
+ for _, orig := range original {
var exClone dns.RR
switch orig := orig.(type) {
+ case *dns.OPT:
+ var optFull bool
+ exClone, optFull = c.opt.clone(orig)
+ full = full && optFull
// TODO(a.garipov): Add more if necessary.
default:
exClone = dns.Copy(orig)
full = false
}
- clone.Extra = append(clone.Extra, exClone)
- }
-
- return clone, full
-}
-
-// appendAnswer appends deep clones of all resource recornds from original to
-// clones and returns it.
-func (c *Cloner) appendAnswer(clones, original []dns.RR) (res []dns.RR, full bool) {
- full = true
- for _, orig := range original {
- var ansClone dns.RR
- switch orig := orig.(type) {
- case *dns.A:
- ans := c.a.Get()
- ans.Hdr = orig.Hdr
-
- ans.A = append(ans.A[:0], orig.A...)
-
- ansClone = ans
- case *dns.AAAA:
- ans := c.aaaa.Get()
- ans.Hdr = orig.Hdr
-
- ans.AAAA = append(ans.AAAA[:0], orig.AAAA...)
-
- ansClone = ans
- case *dns.CNAME:
- ans := c.cname.Get()
- *ans = *orig
-
- ansClone = ans
- case *dns.HTTPS:
- var httpsFull bool
- ansClone, httpsFull = c.https.clone(orig)
- full = full && httpsFull
- case *dns.PTR:
- ans := c.ptr.Get()
- *ans = *orig
-
- ansClone = ans
- case *dns.SRV:
- ans := c.srv.Get()
- *ans = *orig
-
- ansClone = ans
- case *dns.TXT:
- ans := c.txt.Get()
- ans.Hdr = orig.Hdr
-
- ans.Txt = append(ans.Txt[:0], orig.Txt...)
-
- ansClone = ans
- default:
- ansClone = dns.Copy(orig)
- full = false
- }
-
- clones = append(clones, ansClone)
+ clones = append(clones, exClone)
}
return clones, full
}
-// Put returns structures from msg into c's pools. Neither msg nor any of its
-// parts must not be used after this.
-//
-// msg must have exactly one question.
-//
-// TODO(a.garipov): Don't require one question?
-func (c *Cloner) Put(msg *dns.Msg) {
- if msg == nil {
+// type check
+var _ dnsserver.Disposer = (*Cloner)(nil)
+
+// Dispose implements the [dnsserver.Disposer] interface for *Cloner. It
+// returns structures from resp into c's pools. Neither resp nor any of its
+// parts must be used after this.
+func (c *Cloner) Dispose(resp *dns.Msg) {
+ if resp == nil {
return
}
- c.putAnswers(msg.Answer)
+ c.putAnswers(resp.Answer)
- for _, ns := range msg.Ns {
+ for _, ns := range resp.Ns {
switch ns := ns.(type) {
case *dns.SOA:
c.soa.Put(ns)
@@ -215,14 +245,16 @@ func (c *Cloner) Put(msg *dns.Msg) {
}
}
- for _, ex := range msg.Extra {
- // TODO(a.garipov): Add OPT.
- _ = ex
+ for _, ex := range resp.Extra {
+ switch ex := ex.(type) {
+ case *dns.OPT:
+ c.opt.put(ex)
+ default:
+ // Go on.
+ }
}
- c.question.Put(&msg.Question)
-
- c.msg.Put(msg)
+ c.msg.Put(resp)
}
// putAnswers returns answers into c's pools.
@@ -237,6 +269,8 @@ func (c *Cloner) putAnswers(answers []dns.RR) {
c.cname.Put(ans)
case *dns.HTTPS:
c.https.put(ans)
+ case *dns.MX:
+ c.mx.Put(ans)
case *dns.PTR:
c.ptr.Put(ans)
case *dns.SRV:
@@ -248,263 +282,3 @@ func (c *Cloner) putAnswers(answers []dns.RR) {
}
}
}
-
-// httpsCloner is a pool that can clone common parts of DNS messages of type
-// HTTPS with fewer allocations.
-type httpsCloner struct {
- // Top-level structures.
-
- rr *agdsync.TypedPool[dns.HTTPS]
-
- // Values.
-
- alpn *agdsync.TypedPool[dns.SVCBAlpn]
- dohpath *agdsync.TypedPool[dns.SVCBDoHPath]
- echconfig *agdsync.TypedPool[dns.SVCBECHConfig]
- ipv4hint *agdsync.TypedPool[dns.SVCBIPv4Hint]
- ipv6hint *agdsync.TypedPool[dns.SVCBIPv6Hint]
- local *agdsync.TypedPool[dns.SVCBLocal]
- mandatory *agdsync.TypedPool[dns.SVCBMandatory]
- noDefALPN *agdsync.TypedPool[dns.SVCBNoDefaultAlpn]
- port *agdsync.TypedPool[dns.SVCBPort]
-
- // Miscellaneous.
-
- ip *agdsync.TypedPool[net.IP]
-}
-
-// newHTTPSCloner returns a new properly initialized *httpsCloner.
-func newHTTPSCloner() (c *httpsCloner) {
- return &httpsCloner{
- rr: agdsync.NewTypedPool(func() (v *dns.HTTPS) {
- return &dns.HTTPS{}
- }),
-
- alpn: agdsync.NewTypedPool(func() (v *dns.SVCBAlpn) {
- return &dns.SVCBAlpn{}
- }),
- dohpath: agdsync.NewTypedPool(func() (v *dns.SVCBDoHPath) {
- return &dns.SVCBDoHPath{}
- }),
- echconfig: agdsync.NewTypedPool(func() (v *dns.SVCBECHConfig) {
- return &dns.SVCBECHConfig{}
- }),
- ipv4hint: agdsync.NewTypedPool(func() (v *dns.SVCBIPv4Hint) {
- return &dns.SVCBIPv4Hint{}
- }),
- ipv6hint: agdsync.NewTypedPool(func() (v *dns.SVCBIPv6Hint) {
- return &dns.SVCBIPv6Hint{}
- }),
- local: agdsync.NewTypedPool(func() (v *dns.SVCBLocal) {
- return &dns.SVCBLocal{}
- }),
- mandatory: agdsync.NewTypedPool(func() (v *dns.SVCBMandatory) {
- return &dns.SVCBMandatory{}
- }),
- noDefALPN: agdsync.NewTypedPool(func() (v *dns.SVCBNoDefaultAlpn) {
- return &dns.SVCBNoDefaultAlpn{}
- }),
- port: agdsync.NewTypedPool(func() (v *dns.SVCBPort) {
- return &dns.SVCBPort{}
- }),
-
- ip: agdsync.NewTypedPool(func() (v *net.IP) {
- // Use the IPv6 length to increase the effectiveness of the pool.
- ip := make(net.IP, 16)
-
- return &ip
- }),
- }
-}
-
-// clone returns a deep clone of rr. full is true if rr was cloned entirely
-// without the use of [dns.Copy].
-func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) {
- if rr == nil {
- return nil, true
- }
-
- clone = c.rr.Get()
-
- clone.Hdr = rr.Hdr
- clone.Priority = rr.Priority
- clone.Target = rr.Target
-
- clone.Value = clone.Value[:0]
- for _, orig := range rr.Value {
- valClone, knownKV := c.cloneKV(orig)
- if !knownKV {
- // This branch is only reached if there is a new SVCB key-value type
- // in miekg/dns. Give up and just use their copy function.
- return dns.Copy(rr).(*dns.HTTPS), false
- }
-
- clone.Value = append(clone.Value, valClone)
- }
-
- return clone, true
-}
-
-// cloneKV returns a deep clone of orig. full is true if orig was recognized.
-func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue, known bool) {
- switch orig := orig.(type) {
- case *dns.SVCBAlpn:
- v := c.alpn.Get()
-
- v.Alpn = append(v.Alpn[:0], orig.Alpn...)
-
- clone = v
- case *dns.SVCBDoHPath:
- v := c.dohpath.Get()
- *v = *orig
-
- clone = v
- case *dns.SVCBECHConfig:
- v := c.echconfig.Get()
-
- v.ECH = append(v.ECH[:0], orig.ECH...)
-
- clone = v
- case *dns.SVCBIPv4Hint:
- v := c.ipv4hint.Get()
-
- v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
-
- clone = v
- case *dns.SVCBIPv6Hint:
- v := c.ipv6hint.Get()
-
- v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
-
- clone = v
- case *dns.SVCBLocal:
- v := c.local.Get()
- v.KeyCode = orig.KeyCode
-
- v.Data = append(v.Data[:0], orig.Data...)
-
- clone = v
- case *dns.SVCBMandatory:
- v := c.mandatory.Get()
-
- v.Code = append(v.Code[:0], orig.Code...)
-
- clone = v
- case *dns.SVCBNoDefaultAlpn:
- clone = c.noDefALPN.Get()
- case *dns.SVCBPort:
- v := c.port.Get()
- *v = *orig
-
- clone = v
- default:
- // This branch is only reached if there is a new SVCB key-value type
- // in miekg/dns.
- return nil, false
- }
-
- return clone, true
-}
-
-// appendIPs appends the clones of IP addresses from orig to hints and returns
-// the resulting slice. clone is allocated as a single continuous slice.
-func (c *httpsCloner) appendIPs(hints, orig []net.IP) (clone []net.IP) {
- if len(orig) == 0 {
- if orig == nil {
- return nil
- }
-
- return []net.IP{}
- }
-
- // Use a single large slice and subslice it to make it easier to maintain a
- // pool of these.
- ips := *c.ip.Get()
- ips = ips[:0]
-
- neededCap := 0
- for _, origIP := range orig {
- neededCap += len(origIP)
- }
-
- ips = slices.Grow(ips, neededCap)
-
- hints = hints[:0]
- for _, origIP := range orig {
- ips = append(ips, origIP...)
- origLen := len(origIP)
- lastIdx := len(ips)
- hints = append(hints, ips[lastIdx-origLen:lastIdx])
- }
-
- return hints
-}
-
-// put returns structures from rr into c's pools.
-func (c *httpsCloner) put(rr *dns.HTTPS) {
- if rr == nil {
- return
- }
-
- for _, kv := range rr.Value {
- c.putKV(kv)
- }
-
- c.rr.Put(rr)
-}
-
-// putKV returns structures from kv into c's pools.
-func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) {
- switch kv := kv.(type) {
- case *dns.SVCBAlpn:
- c.alpn.Put(kv)
- case *dns.SVCBDoHPath:
- c.dohpath.Put(kv)
- case *dns.SVCBECHConfig:
- c.echconfig.Put(kv)
- case *dns.SVCBIPv4Hint:
- putIPHint(c, kv)
- case *dns.SVCBIPv6Hint:
- putIPHint(c, kv)
- case *dns.SVCBLocal:
- c.local.Put(kv)
- case *dns.SVCBMandatory:
- c.mandatory.Put(kv)
- case *dns.SVCBNoDefaultAlpn:
- c.noDefALPN.Put(kv)
- case *dns.SVCBPort:
- c.port.Put(kv)
- default:
- // This branch is only reached if there is a new SVCB key-value type
- // in miekg/dns. Noting to do.
- }
-}
-
-// putIPHint is a generic helper that returns the structures of kv into c.
-func putIPHint[T *dns.SVCBIPv4Hint | *dns.SVCBIPv6Hint](c *httpsCloner, kv T) {
- switch kv := any(kv).(type) {
- case *dns.SVCBIPv4Hint:
- // TODO(a.garipov): Put the common code above the switch when Go learns
- // about common fields between types.
- if len(kv.Hint) > 0 {
- // Assume that the array underlying these slices is a single and
- // continuous one.
- c.ip.Put(&kv.Hint[0])
- }
-
- c.ipv4hint.Put(kv)
- case *dns.SVCBIPv6Hint:
- // TODO(a.garipov): Put the common code above the switch when Go learns
- // about common fields between types.
- if len(kv.Hint) > 0 {
- // Assume that the array underlying these slices is a single and
- // continuous one.
- c.ip.Put(&kv.Hint[0])
- }
-
- c.ipv6hint.Put(kv)
- default:
- // Must not happen, because there is a strict type parameter above.
- panic(fmt.Errorf("bad type %T", kv))
- }
-}
diff --git a/internal/dnsmsg/cloner_test.go b/internal/dnsmsg/cloner_test.go
index c79c64f..3da1b14 100644
--- a/internal/dnsmsg/cloner_test.go
+++ b/internal/dnsmsg/cloner_test.go
@@ -11,19 +11,40 @@ import (
"github.com/stretchr/testify/require"
)
+// testClonerStat is a [dnsmsg.ClonerStat] implementation for tests.
+type testClonerStat struct {
+ // TODO(a.garipov): Consider better naming for methods of dnsmsg.ClonerStat.
+ onOnClone func(isFull bool)
+}
+
+// type check
+var _ dnsmsg.ClonerStat = (*testClonerStat)(nil)
+
+// OnClone implements the [ClonerStat] interface for *testClonerStat.
+func (s *testClonerStat) OnClone(isFull bool) {
+ s.onOnClone(isFull)
+}
+
// clonerTestCase is the type for the common test cases for the cloner tests and
// benchmarks.
type clonerTestCase struct {
msg *dns.Msg
wantFull assert.BoolAssertionFunc
name string
+
+ // handledByClone is true if [dnsmsg.Clone] is able to correctly clone the
+ // message. It is often false for the messages that contain nil slices,
+ // because package github.com/miekg/dns often does not take nilness into
+ // account.
+ handledByClone bool
}
// clonerTestCases are the common test cases for the clone benchmarks.
var clonerTestCases = []clonerTestCase{{
- msg: dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET),
- name: "req_a",
- wantFull: assert.True,
+ msg: dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET),
+ name: "req_a",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -32,8 +53,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewA(testFQDN, 10, testIPv4),
},
),
- name: "resp_a",
- wantFull: assert.True,
+ name: "resp_a",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -43,8 +65,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewA(testFQDN, 10, testIPv4.Next()),
},
),
- name: "resp_a_many",
- wantFull: assert.True,
+ name: "resp_a_many",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -56,12 +79,14 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewSOA(testFQDN, 10, "ns.example.", "mbox.example."),
},
),
- name: "resp_a_soa",
- wantFull: assert.True,
+ name: "resp_a_soa",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
- msg: dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET),
- name: "req_aaaa",
- wantFull: assert.True,
+ msg: dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET),
+ name: "req_aaaa",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -70,8 +95,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewAAAA(testFQDN, 10, testIPv6),
},
),
- name: "resp_aaaa",
- wantFull: assert.True,
+ name: "resp_aaaa",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -81,8 +107,14 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewA("cname.example.", 10, testIPv4),
},
),
- name: "resp_cname_a",
- wantFull: assert.True,
+ name: "resp_cname_a",
+ wantFull: assert.True,
+ handledByClone: true,
+}, {
+ msg: newMXResp(testFQDN, 10),
+ name: "resp_mx",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -91,8 +123,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewPTR("4.3.2.1.in-addr.arpa", 10, "ptr.example."),
},
),
- name: "resp_ptr",
- wantFull: assert.True,
+ name: "resp_ptr",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -101,8 +134,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewTXT(testFQDN, 10, "a", "b", "c"),
},
),
- name: "resp_txt",
- wantFull: assert.True,
+ name: "resp_txt",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -111,8 +145,9 @@ var clonerTestCases = []clonerTestCase{{
dnsservertest.NewSRV(testFQDN, 10, "target.example.", 1, 1, 8080),
},
),
- name: "resp_srv",
- wantFull: assert.True,
+ name: "resp_srv",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: dnsservertest.NewResp(
dns.RcodeSuccess,
@@ -121,8 +156,9 @@ var clonerTestCases = []clonerTestCase{{
&dns.DNSKEY{},
},
),
- name: "resp_not_full",
- wantFull: assert.False,
+ name: "resp_not_full",
+ wantFull: assert.False,
+ handledByClone: true,
}, {
msg: newHTTPSResp([]dns.SVCBKeyValue{
&dns.SVCBAlpn{Alpn: []string{"http/1.1", "h2", "h3"}},
@@ -141,18 +177,50 @@ var clonerTestCases = []clonerTestCase{{
&dns.SVCBNoDefaultAlpn{},
&dns.SVCBPort{Port: 443},
}),
- name: "resp_https",
- wantFull: assert.True,
+ name: "resp_https",
+ wantFull: assert.True,
+ handledByClone: true,
}, {
msg: newHTTPSResp([]dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{}},
&dns.SVCBIPv6Hint{Hint: []net.IP{}},
}),
- name: "resp_https_empty_hint",
- wantFull: assert.True,
+ name: "resp_https_empty_hint",
+ wantFull: assert.True,
+ handledByClone: true,
+}, {
+ msg: newHTTPSResp([]dns.SVCBKeyValue{
+ &dns.SVCBMandatory{},
+ }),
+ name: "resp_https_empty_mandatory",
+ wantFull: assert.True,
+ handledByClone: true,
+}, {
+ msg: newHTTPSResp(nil),
+ name: "resp_https_nil_hint",
+ wantFull: assert.True,
+ handledByClone: false,
+}, {
+ msg: newOPTResp([]dns.EDNS0{
+ &dns.EDNS0_SUBNET{
+ Code: dns.EDNS0SUBNET,
+ Family: 1,
+ SourceNetmask: 24,
+ SourceScope: 24,
+ Address: net.IP{1, 2, 3, 0},
+ },
+ }),
+ name: "resp_a_ecs",
+ wantFull: assert.True,
+ handledByClone: true,
+}, {
+ msg: newOPTResp(nil),
+ name: "resp_a_ecs_nil",
+ wantFull: assert.True,
+ handledByClone: false,
}}
-// newHTTPSResp is a hepler that returns a response of type HTTPS with the given
+// newHTTPSResp is a helper that returns a response of type HTTPS with the given
// parameter values.
func newHTTPSResp(kv []dns.SVCBKeyValue) (resp *dns.Msg) {
ans := &dns.HTTPS{
@@ -176,36 +244,88 @@ func newHTTPSResp(kv []dns.SVCBKeyValue) (resp *dns.Msg) {
)
}
-func TestCloner_Clone(t *testing.T) {
- c := dnsmsg.NewCloner()
+// newMXResp is a helper that returns a response of type MX with the given
+// parameter values.
+func newMXResp(mx string, pref uint16) (resp *dns.Msg) {
+ ans := &dns.MX{
+ Hdr: dns.RR_Header{
+ Name: testFQDN,
+ Rrtype: dns.TypeMX,
+ Class: dns.ClassINET,
+ Ttl: 10,
+ },
+ Preference: pref,
+ Mx: mx,
+ }
+ return dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ dnsservertest.NewReq(testFQDN, dns.TypeMX, dns.ClassINET),
+ dnsservertest.SectionAnswer{ans},
+ )
+}
+
+// newOPTResp is a helper that returns a response of type OPT with the given
+// parameter values.
+func newOPTResp(opt []dns.EDNS0) (resp *dns.Msg) {
+ ex := &dns.OPT{
+ Hdr: dns.RR_Header{
+ Name: testFQDN,
+ Rrtype: dns.TypeA,
+ Class: dns.ClassINET,
+ Ttl: 10,
+ },
+ Option: opt,
+ }
+
+ return dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET),
+ dnsservertest.SectionAnswer{
+ dnsservertest.NewA(testFQDN, 10, testIPv4),
+ },
+ dnsservertest.SectionExtra{ex},
+ )
+}
+
+func TestCloner_Clone(t *testing.T) {
for _, tc := range clonerTestCases {
t.Run(tc.name, func(t *testing.T) {
- clone, full := c.Clone(tc.msg)
+ var gotIsFull bool
+ c := dnsmsg.NewCloner(&testClonerStat{
+ onOnClone: func(isFull bool) {
+ gotIsFull = isFull
+ },
+ })
+
+ clone := c.Clone(tc.msg)
assert.NotSame(t, tc.msg, clone)
assert.Equal(t, tc.msg, clone)
- tc.wantFull(t, full)
+ tc.wantFull(t, gotIsFull)
- // Check again after putting it back.
- c.Put(clone)
+ // Check again after disposing of it.
+ c.Dispose(clone)
- clone, full = c.Clone(tc.msg)
+ clone = c.Clone(tc.msg)
assert.NotSame(t, tc.msg, clone)
assert.Equal(t, tc.msg, clone)
- tc.wantFull(t, full)
+ tc.wantFull(t, gotIsFull)
})
}
}
// Sinks for benchmarks
var (
- msgSink *dns.Msg
- boolSink bool
+ msgSink *dns.Msg
)
func BenchmarkClone(b *testing.B) {
for _, tc := range clonerTestCases {
b.Run(tc.name, func(b *testing.B) {
+ if !tc.handledByClone {
+ b.Skip("not handled by dnsmsg.Clone, skipping")
+ }
+
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -222,39 +342,47 @@ func BenchmarkClone(b *testing.B) {
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
- // BenchmarkClone/req_a-16 32849714 231.7 ns/op 168 B/op 2 allocs/op
- // BenchmarkClone/resp_a-16 12051967 509.1 ns/op 256 B/op 5 allocs/op
- // BenchmarkClone/resp_a_many-16 8579755 669.4 ns/op 344 B/op 7 allocs/op
- // BenchmarkClone/resp_a_soa-16 10393932 681.9 ns/op 368 B/op 6 allocs/op
- // BenchmarkClone/req_aaaa-16 25616247 232.1 ns/op 168 B/op 2 allocs/op
- // BenchmarkClone/resp_aaaa-16 14519920 493.4 ns/op 264 B/op 5 allocs/op
- // BenchmarkClone/resp_cname_a-16 8652282 662.2 ns/op 320 B/op 6 allocs/op
- // BenchmarkClone/resp_ptr-16 13558555 370.0 ns/op 232 B/op 4 allocs/op
- // BenchmarkClone/resp_txt-16 12322016 532.7 ns/op 296 B/op 5 allocs/op
- // BenchmarkClone/resp_srv-16 15878784 396.3 ns/op 248 B/op 4 allocs/op
- // BenchmarkClone/resp_not_full-16 15718658 384.6 ns/op 248 B/op 4 allocs/op
- // BenchmarkClone/resp_https-16 2621149 2020 ns/op 880 B/op 24 allocs/op
- // BenchmarkClone/resp_https_empty_hint-16 6829873 890.8 ns/op 424 B/op 8 allocs/op
+ // BenchmarkClone/req_a-16 24691725 250.1 ns/op 168 B/op 2 allocs/op
+ // BenchmarkClone/resp_a-16 12547648 429.8 ns/op 256 B/op 5 allocs/op
+ // BenchmarkClone/resp_a_many-16 10174539 602.5 ns/op 344 B/op 7 allocs/op
+ // BenchmarkClone/resp_a_soa-16 10228933 600.0 ns/op 368 B/op 6 allocs/op
+ // BenchmarkClone/req_aaaa-16 24920611 248.6 ns/op 168 B/op 2 allocs/op
+ // BenchmarkClone/resp_aaaa-16 13603160 474.0 ns/op 264 B/op 5 allocs/op
+ // BenchmarkClone/resp_cname_a-16 10398249 589.2 ns/op 320 B/op 6 allocs/op
+ // BenchmarkClone/resp_mx-16 15299034 414.9 ns/op 248 B/op 4 allocs/op
+ // BenchmarkClone/resp_ptr-16 14701116 386.7 ns/op 232 B/op 4 allocs/op
+ // BenchmarkClone/resp_txt-16 11148487 495.6 ns/op 296 B/op 5 allocs/op
+ // BenchmarkClone/resp_srv-16 16175085 398.3 ns/op 248 B/op 4 allocs/op
+ // BenchmarkClone/resp_not_full-16 16115785 366.2 ns/op 248 B/op 4 allocs/op
+ // BenchmarkClone/resp_https-16 2940937 1998 ns/op 880 B/op 24 allocs/op
+ // BenchmarkClone/resp_https_empty_hint-16 8712819 744.4 ns/op 424 B/op 8 allocs/op
+ // BenchmarkClone/resp_https_empty_mandatory-16 8605273 655.8 ns/op 384 B/op 7 allocs/op
+ // BenchmarkClone/resp_a_ecs-16 7337880 777.1 ns/op 384 B/op 8 allocs/op
}
func BenchmarkCloner_Clone(b *testing.B) {
- c := dnsmsg.NewCloner()
-
for _, tc := range clonerTestCases {
b.Run(tc.name, func(b *testing.B) {
+ var gotIsFull bool
+ c := dnsmsg.NewCloner(&testClonerStat{
+ onOnClone: func(isFull bool) {
+ gotIsFull = isFull
+ },
+ })
+
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- msgSink, boolSink = c.Clone(tc.msg)
+ msgSink = c.Clone(tc.msg)
if i < b.N-1 {
- // Don't put the last one to be sure that we can compare
- // that one.
- c.Put(msgSink)
+ // Don't dispose of the last one to be sure that we can
+ // compare that one.
+ c.Dispose(msgSink)
}
}
require.Equal(b, tc.msg, msgSink)
- tc.wantFull(b, boolSink)
+ tc.wantFull(b, gotIsFull)
})
}
@@ -264,17 +392,64 @@ func BenchmarkCloner_Clone(b *testing.B) {
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
- // BenchmarkCloner_Clone/req_a-16 163590546 36.33 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_a-16 100000000 56.55 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_a_many-16 72498543 84.52 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_a_soa-16 81750753 73.07 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/req_aaaa-16 165287482 39.00 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_aaaa-16 99625165 59.56 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_cname_a-16 72154432 81.15 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_ptr-16 100418211 60.88 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_txt-16 80963180 73.66 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_srv-16 89021206 69.35 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_not_full-16 31277523 187.6 ns/op 64 B/op 1 allocs/op
- // BenchmarkCloner_Clone/resp_https-16 14601229 396.3 ns/op 0 B/op 0 allocs/op
- // BenchmarkCloner_Clone/resp_https_empty_hint-16 45725181 127.4 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/req_a-16 167307522 36.48 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_a-16 92398767 66.01 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_a_many-16 60790945 111.8 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_a_soa-16 61474227 91.50 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/req_aaaa-16 158363983 39.89 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_aaaa-16 72113028 76.83 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_cname_a-16 67518502 89.24 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_mx-16 89713944 70.96 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_ptr-16 87175648 67.42 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_txt-16 80373494 75.37 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_srv-16 85734901 70.36 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_not_full-16 28868667 211.3 ns/op 64 B/op 1 allocs/op
+ // BenchmarkCloner_Clone/resp_https-16 13196191 402.3 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_https_empty_hint-16 48459688 125.6 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_https_empty_mandatory-16 63759298 96.10 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_https_nil_hint-16 89683288 66.75 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_a_ecs-16 53174110 119.6 ns/op 0 B/op 0 allocs/op
+ // BenchmarkCloner_Clone/resp_a_ecs_nil-16 69814755 92.42 ns/op 0 B/op 0 allocs/op
+}
+
+func FuzzCloner_Clone(f *testing.F) {
+ for _, tc := range clonerTestCases {
+ b, err := tc.msg.Pack()
+ require.NoError(f, err)
+
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, input []byte) {
+ msg := &dns.Msg{}
+ err := msg.Unpack(input)
+ if err != nil || len(msg.Question) != 1 {
+ return
+ }
+
+ var gotIsFull bool
+ c := dnsmsg.NewCloner(&testClonerStat{
+ onOnClone: func(isFull bool) {
+ gotIsFull = isFull
+ },
+ })
+
+ clone := c.Clone(msg)
+ if !gotIsFull {
+ // TODO(a.garipov): Currently we cannot analyze partial clones,
+ // because these may contain e.g. HTTPS records in Ns fields, which
+ // [dns.Copy] doesn't clone properly due to nilness issues.
+ // Consider changing the code to fix that.
+ return
+ }
+
+ assert.Equal(t, msg, clone)
+
+ c.Dispose(clone)
+ clone = c.Clone(msg)
+
+ require.True(t, gotIsFull)
+
+ assert.Equal(t, msg, clone)
+ })
}
diff --git a/internal/dnsmsg/clonerstat.go b/internal/dnsmsg/clonerstat.go
new file mode 100644
index 0000000..ca02c46
--- /dev/null
+++ b/internal/dnsmsg/clonerstat.go
@@ -0,0 +1,20 @@
+package dnsmsg
+
+// ClonerStat is an interface for entities that collect statistics about a
+// [Cloner].
+//
+// All methods must be safe for concurrent use.
+type ClonerStat interface {
+ // OnClone is called on [Cloner.Clone] calls. isFull is true if the clone
+ // was full.
+ OnClone(isFull bool)
+}
+
+// EmptyClonerStat is a [ClonerStat] implementation that does nothing.
+type EmptyClonerStat struct{}
+
+// type check
+var _ ClonerStat = EmptyClonerStat{}
+
+// OnClone implements the [ClonerStat] interface for EmptyClonerStat.
+func (EmptyClonerStat) OnClone(_ bool) {}
diff --git a/internal/dnsmsg/constructor.go b/internal/dnsmsg/constructor.go
index 9e7e06e..cbc75ce 100644
--- a/internal/dnsmsg/constructor.go
+++ b/internal/dnsmsg/constructor.go
@@ -13,16 +13,21 @@ import (
// Constructor creates DNS messages for blocked or modified responses.
type Constructor struct {
+ cloner *Cloner
blockingMode BlockingMode
fltRespTTL time.Duration
}
// NewConstructor returns a properly initialized constructor with the given
// options. respTTL is the time-to-live value used for responses created by
-// this message constructor. bm is the blocking mode to use in
-// [Constructor.NewBlockedRespMsg].
-func NewConstructor(bm BlockingMode, respTTL time.Duration) (c *Constructor) {
+// this message constructor. cloner may be nil. bm is the blocking mode to use
+// in [Constructor.NewBlockedRespMsg]; it must not be nil.
+func NewConstructor(cloner *Cloner, bm BlockingMode, respTTL time.Duration) (c *Constructor) {
return &Constructor{
+ // TODO(a.garipov): Allowing a nil cloner is really an optimization
+ // to make ri.Messages allocate less. Consider setting a constructor
+ // for a profile once and require a non-nil cloner.
+ cloner: cloner,
blockingMode: bm,
fltRespTTL: respTTL,
}
@@ -88,6 +93,40 @@ func (c *Constructor) NewIPRespMsg(req *dns.Msg, ips ...netip.Addr) (msg *dns.Ms
}
}
+// NewCNAMEWithIPs generates a filtered response to req with CNAME record and
+// provided ips. cname is the fully-qualified name and must not be empty, ips
+// must be of the same family.
+func (c *Constructor) NewCNAMEWithIPs(
+ req *dns.Msg,
+ cname string,
+ ips ...netip.Addr,
+) (resp *dns.Msg, err error) {
+ resp = c.NewRespMsg(req)
+
+ resp.Answer = make([]dns.RR, 0, len(ips)+1)
+ resp.Answer = append(resp.Answer, c.NewAnswerCNAME(req, cname))
+
+ var ans dns.RR
+ for i, ip := range ips {
+ switch qt := req.Question[0].Qtype; qt {
+ case dns.TypeA:
+ ans, err = c.NewAnswerA(cname, ip)
+ case dns.TypeAAAA:
+ ans, err = c.NewAnswerAAAA(cname, ip)
+ default:
+ return nil, fmt.Errorf("bad qtype for a or aaaa resp: %d", qt)
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("bad ip at idx %d: %w", i, err)
+ }
+
+ resp.Answer = append(resp.Answer, ans)
+ }
+
+ return resp, err
+}
+
// NewMsgFORMERR returns a properly initialized FORMERR response.
func (c *Constructor) NewMsgFORMERR(req *dns.Msg) (resp *dns.Msg) {
return c.newMsgRCode(req, dns.RcodeFormatError)
@@ -127,7 +166,7 @@ func (c *Constructor) newMsgRCode(req *dns.Msg, rc RCode) (resp *dns.Msg) {
// NewTXTRespMsg returns a DNS TXT response message with the given strings as
// content. The TTL is also set to c.FilteredResponseTTL.
func (c *Constructor) NewTXTRespMsg(req *dns.Msg, strs ...string) (msg *dns.Msg, err error) {
- ans, err := c.NewAnsTXT(req, strs)
+ ans, err := c.NewAnswerTXT(req, strs)
if err != nil {
return nil, err
}
@@ -150,7 +189,7 @@ func (c *Constructor) AppendDebugExtra(req, resp *dns.Msg, str string) (err erro
if strLen <= MaxTXTStringLen {
resp.Extra = append(resp.Extra, &dns.TXT{
- Hdr: c.newHdrWithClass(req, dns.TypeTXT, dns.ClassCHAOS),
+ Hdr: c.newHdrWithClass(req.Question[0].Name, dns.TypeTXT, dns.ClassCHAOS),
Txt: []string{str},
})
@@ -176,7 +215,7 @@ func (c *Constructor) AppendDebugExtra(req, resp *dns.Msg, str string) (err erro
}
resp.Extra = append(resp.Extra, &dns.TXT{
- Hdr: c.newHdrWithClass(req, dns.TypeTXT, dns.ClassCHAOS),
+ Hdr: c.newHdrWithClass(req.Question[0].Name, dns.TypeTXT, dns.ClassCHAOS),
Txt: newStr,
})
@@ -185,62 +224,90 @@ func (c *Constructor) AppendDebugExtra(req, resp *dns.Msg, str string) (err erro
// newHdr returns a new resource record header.
func (c *Constructor) newHdr(req *dns.Msg, rrType RRType) (hdr dns.RR_Header) {
- return dns.RR_Header{
- Name: req.Question[0].Name,
- Rrtype: rrType,
- Ttl: uint32(c.fltRespTTL.Seconds()),
- Class: dns.ClassINET,
- }
+ return c.newHdrWithClass(req.Question[0].Name, rrType, dns.ClassINET)
}
// newHdrWithClass returns a new resource record header with specified class.
-func (c *Constructor) newHdrWithClass(req *dns.Msg, rrType RRType, cl dns.Class) (h dns.RR_Header) {
+// fqdn is the fully-qualified name and must not be empty.
+func (c *Constructor) newHdrWithClass(fqdn string, rrType RRType, cl dns.Class) (h dns.RR_Header) {
return dns.RR_Header{
- Name: req.Question[0].Name,
+ Name: fqdn,
Rrtype: rrType,
Ttl: uint32(c.fltRespTTL.Seconds()),
Class: uint16(cl),
}
}
-// NewAnsA returns a new resource record with an IPv4 address. ip must be an
-// IPv4 address. If ip is a zero netip.Addr, it is replaced by an unspecified
-// (aka null) IP, 0.0.0.0.
-func (c *Constructor) NewAnsA(req *dns.Msg, ip netip.Addr) (ans *dns.A, err error) {
+// NewAnswerA returns a new resource record with the given IPv4 address and
+// fqdn. fqdn is the fully-qualified name and must not be empty. ip must be
+// an IPv4 address. If ip is a zero netip.Addr, it is replaced by an
+// unspecified (aka null) IP, 0.0.0.0.
+//
+// TODO(a.garipov): Use FQDN in all other answer constructors.
+func (c *Constructor) NewAnswerA(fqdn string, ip netip.Addr) (rr *dns.A, err error) {
if ip == (netip.Addr{}) {
ip = netip.IPv4Unspecified()
} else if !ip.Is4() {
return nil, fmt.Errorf("bad ipv4: %s", ip)
}
- data := ip.As4()
+ rr = newA(c.cloner, ip)
+ rr.Hdr = c.newHdrWithClass(fqdn, dns.TypeA, dns.ClassINET)
- return &dns.A{
- Hdr: c.newHdr(req, dns.TypeA),
- A: data[:],
- }, nil
+ return rr, nil
}
-// NewAnsAAAA returns a new resource record with an IPv6 address. ip must be an
+// NewAnswerAAAA returns a new resource record with the given IPv6 address and
+// fqdn. fqdn is the fully-qualified name and must not be empty. ip must be an
// IPv6 address. If ip is a zero netip.Addr, it is replaced by an unspecified
// (aka null) IP, [::].
-func (c *Constructor) NewAnsAAAA(req *dns.Msg, ip netip.Addr) (ans *dns.AAAA, err error) {
+func (c *Constructor) NewAnswerAAAA(fqdn string, ip netip.Addr) (rr *dns.AAAA, err error) {
if ip == (netip.Addr{}) {
ip = netip.IPv6Unspecified()
} else if !ip.Is6() {
return nil, fmt.Errorf("bad ipv6: %s", ip)
}
- data := ip.As16()
+ rr = newAAAA(c.cloner, ip)
+ rr.Hdr = c.newHdrWithClass(fqdn, dns.TypeAAAA, dns.ClassINET)
- return &dns.AAAA{
- Hdr: c.newHdr(req, dns.TypeAAAA),
- AAAA: data[:],
- }, nil
+ return rr, nil
}
-// NewAnsTXT returns a new resource record of TXT type.
-func (c *Constructor) NewAnsTXT(req *dns.Msg, strs []string) (ans *dns.TXT, err error) {
+// NewAnswerCNAME returns a new resource record of CNAME type.
+func (c *Constructor) NewAnswerCNAME(req *dns.Msg, target string) (rr *dns.CNAME) {
+ rr = newCNAME(c.cloner, dns.Fqdn(target))
+ rr.Hdr = c.newHdr(req, dns.TypeCNAME)
+
+ return rr
+}
+
+// NewAnswerMX returns a new resource record of MX type.
+func (c *Constructor) NewAnswerMX(req *dns.Msg, mx *rules.DNSMX) (rr *dns.MX) {
+ rr = newMX(c.cloner, dns.Fqdn(mx.Exchange), mx.Preference)
+ rr.Hdr = c.newHdr(req, dns.TypeMX)
+
+ return rr
+}
+
+// NewAnswerPTR returns a new resource record of PTR type.
+func (c *Constructor) NewAnswerPTR(req *dns.Msg, ptr string) (rr *dns.PTR) {
+ rr = newPTR(c.cloner, dns.Fqdn(ptr))
+ rr.Hdr = c.newHdr(req, dns.TypePTR)
+
+ return rr
+}
+
+// NewAnswerSRV returns a new resource record of SRV type.
+func (c *Constructor) NewAnswerSRV(req *dns.Msg, srv *rules.DNSSRV) (rr *dns.SRV) {
+ rr = newSRV(c.cloner, dns.Fqdn(srv.Target), srv.Priority, srv.Weight, srv.Port)
+ rr.Hdr = c.newHdr(req, dns.TypeSRV)
+
+ return rr
+}
+
+// NewAnswerTXT returns a new resource record of TXT type.
+func (c *Constructor) NewAnswerTXT(req *dns.Msg, strs []string) (rr *dns.TXT, err error) {
qt := req.Question[0].Qtype
if qt != dns.TypeTXT {
return nil, fmt.Errorf("bad qtype for txt resp: %s", dns.Type(qt))
@@ -259,46 +326,10 @@ func (c *Constructor) NewAnsTXT(req *dns.Msg, strs []string) (ans *dns.TXT, err
}
}
- return &dns.TXT{
- Hdr: c.newHdr(req, dns.TypeTXT),
- Txt: strs,
- }, nil
-}
+ rr = newTXT(c.cloner, strs)
+ rr.Hdr = c.newHdr(req, dns.TypeTXT)
-// NewAnsPTR returns a new resource record of PTR type.
-func (c *Constructor) NewAnsPTR(req *dns.Msg, ptr string) (ans *dns.PTR) {
- return &dns.PTR{
- Hdr: c.newHdr(req, dns.TypePTR),
- Ptr: dns.Fqdn(ptr),
- }
-}
-
-// NewAnswerMX returns a new resource record of MX type.
-func (c *Constructor) NewAnswerMX(req *dns.Msg, mx *rules.DNSMX) (ans *dns.MX) {
- return &dns.MX{
- Hdr: c.newHdr(req, dns.TypeMX),
- Preference: mx.Preference,
- Mx: dns.Fqdn(mx.Exchange),
- }
-}
-
-// NewAnswerSRV returns a new resource record of SRV type.
-func (c *Constructor) NewAnswerSRV(req *dns.Msg, srv *rules.DNSSRV) (ans *dns.SRV) {
- return &dns.SRV{
- Hdr: c.newHdr(req, dns.TypeSRV),
- Priority: srv.Priority,
- Weight: srv.Weight,
- Port: srv.Port,
- Target: dns.Fqdn(srv.Target),
- }
-}
-
-// NewAnswerCNAME returns a new resource record of CNAME type.
-func (c *Constructor) NewAnswerCNAME(req *dns.Msg, cname string) (ans *dns.CNAME) {
- return &dns.CNAME{
- Hdr: c.newHdr(req, dns.TypeCNAME),
- Target: dns.Fqdn(cname),
- }
+ return rr, nil
}
// newSOARecords generates the Start Of Authority record for AdGuardDNS. It
@@ -359,7 +390,7 @@ func (c *Constructor) newMsgA(req *dns.Msg, ips ...netip.Addr) (msg *dns.Msg, er
msg = c.NewRespMsg(req)
for i, ip := range ips {
var ans dns.RR
- ans, err = c.NewAnsA(req, ip)
+ ans, err = c.NewAnswerA(req.Question[0].Name, ip)
if err != nil {
return nil, fmt.Errorf("bad ip at idx %d: %w", i, err)
}
@@ -376,7 +407,7 @@ func (c *Constructor) newMsgAAAA(req *dns.Msg, ips ...netip.Addr) (msg *dns.Msg,
msg = c.NewRespMsg(req)
for i, ip := range ips {
var ans dns.RR
- ans, err = c.NewAnsAAAA(req, ip)
+ ans, err = c.NewAnswerAAAA(req.Question[0].Name, ip)
if err != nil {
return nil, fmt.Errorf("bad ip at idx %d: %w", i, err)
}
diff --git a/internal/dnsmsg/constructor_test.go b/internal/dnsmsg/constructor_test.go
index 6b1821f..113515f 100644
--- a/internal/dnsmsg/constructor_test.go
+++ b/internal/dnsmsg/constructor_test.go
@@ -26,7 +26,7 @@ func newTXTExtra(ttl uint32, strs ...string) (extra []dns.RR) {
}
func TestConstructor_NewBlockedRespMsg_nullIP(t *testing.T) {
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
testCases := []struct {
name string
@@ -80,7 +80,7 @@ func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) {
wantA bool
wantAAAA bool
}{{
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeCustomIP{
IPv4: testIPv4,
IPv6: testIPv6,
}, testFltRespTTL),
@@ -88,14 +88,14 @@ func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) {
wantA: true,
wantAAAA: true,
}, {
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeCustomIP{
IPv4: testIPv4,
}, testFltRespTTL),
name: "ipv4_only",
wantA: true,
wantAAAA: false,
}, {
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeCustomIP{
IPv6: testIPv6,
}, testFltRespTTL),
name: "ipv6_only",
@@ -148,11 +148,11 @@ func TestConstructor_NewBlockedRespMsg_noAnswer(t *testing.T) {
name string
rcode dnsmsg.RCode
}{{
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNXDOMAIN{}, testFltRespTTL),
+ messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNXDOMAIN{}, testFltRespTTL),
name: "nxdomain",
rcode: dns.RcodeNameError,
}, {
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeREFUSED{}, testFltRespTTL),
+ messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeREFUSED{}, testFltRespTTL),
name: "refused",
rcode: dns.RcodeRefused,
}}
@@ -174,7 +174,7 @@ func TestConstructor_NewBlockedRespMsg_noAnswer(t *testing.T) {
}
func TestConstructor_noAnswerMethods(t *testing.T) {
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
req := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
testCases := []struct {
@@ -220,7 +220,7 @@ func TestConstructor_noAnswerMethods(t *testing.T) {
}
func TestConstructor_NewTXTRespMsg(t *testing.T) {
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
req := dnsservertest.NewReq(testFQDN, dns.TypeTXT, dns.ClassINET)
tooLong := strings.Repeat("1", dnsmsg.MaxTXTStringLen+1)
@@ -277,7 +277,7 @@ func TestConstructor_NewTXTRespMsg(t *testing.T) {
}
func TestConstructor_AppendDebugExtra(t *testing.T) {
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
shortText := "This is a short test text"
longText := strings.Repeat("a", 2*dnsmsg.MaxTXTStringLen)
diff --git a/internal/dnsmsg/dnsmsg.go b/internal/dnsmsg/dnsmsg.go
index 7b415b3..f8ee388 100644
--- a/internal/dnsmsg/dnsmsg.go
+++ b/internal/dnsmsg/dnsmsg.go
@@ -9,7 +9,6 @@ import (
"math"
"net/netip"
- "github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)
@@ -152,8 +151,7 @@ func SetMinTTL(r *dns.Msg, minTTL uint32) {
for _, rr := range r.Answer {
h := rr.Header()
- // TODO(d.kolyshev): Use built-in max in go 1.21.
- h.Ttl = mathutil.Max(h.Ttl, minTTL)
+ h.Ttl = max(h.Ttl, minTTL)
}
}
@@ -206,5 +204,21 @@ func getTTLIfLower(r dns.RR, ttl uint32) (res uint32) {
// Go on.
}
- return mathutil.Min(r.Header().Ttl, ttl)
+ return min(r.Header().Ttl, ttl)
+}
+
+// appendIfNotNil returns nil if original is nil. Otherwise, it appends
+// original to clones and returns it.
+//
+// TODO(a.garipov): Consider moving to module golibs.
+func appendIfNotNil[T any](clones, original []T) (res []T) {
+ if len(original) == 0 {
+ if original == nil {
+ return nil
+ }
+
+ return []T{}
+ }
+
+ return append(clones, original...)
}
diff --git a/internal/dnsmsg/error.go b/internal/dnsmsg/error.go
index b47becc..a897cb9 100644
--- a/internal/dnsmsg/error.go
+++ b/internal/dnsmsg/error.go
@@ -29,3 +29,7 @@ var _ errors.Wrapper = BadECSError{}
func (err BadECSError) Unwrap() (unwrapped error) {
return err.Err
}
+
+// IsSentryReportable implements the [errcoll.SentryReportableError] interface
+// for BadECSError.
+func (err BadECSError) IsSentryReportable() (ok bool) { return false }
diff --git a/internal/dnsmsg/error_test.go b/internal/dnsmsg/error_test.go
new file mode 100644
index 0000000..e254f24
--- /dev/null
+++ b/internal/dnsmsg/error_test.go
@@ -0,0 +1,9 @@
+package dnsmsg_test
+
+import (
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
+)
+
+// type check
+var _ errcoll.SentryReportableError = dnsmsg.BadECSError{}
diff --git a/internal/dnsmsg/httpscloner.go b/internal/dnsmsg/httpscloner.go
new file mode 100644
index 0000000..05b51e2
--- /dev/null
+++ b/internal/dnsmsg/httpscloner.go
@@ -0,0 +1,241 @@
+package dnsmsg
+
+import (
+ "net"
+
+ "github.com/AdguardTeam/golibs/syncutil"
+ "github.com/miekg/dns"
+)
+
+// httpsCloner is a pool that can clone common parts of DNS messages of type
+// HTTPS with fewer allocations.
+type httpsCloner struct {
+ // Top-level structures.
+
+ rr *syncutil.Pool[dns.HTTPS]
+
+ // Values.
+
+ alpn *syncutil.Pool[dns.SVCBAlpn]
+ dohpath *syncutil.Pool[dns.SVCBDoHPath]
+ echconfig *syncutil.Pool[dns.SVCBECHConfig]
+ ipv4hint *syncutil.Pool[dns.SVCBIPv4Hint]
+ ipv6hint *syncutil.Pool[dns.SVCBIPv6Hint]
+ local *syncutil.Pool[dns.SVCBLocal]
+ mandatory *syncutil.Pool[dns.SVCBMandatory]
+ noDefALPN *syncutil.Pool[dns.SVCBNoDefaultAlpn]
+ port *syncutil.Pool[dns.SVCBPort]
+
+ // Miscellaneous.
+
+ ip *syncutil.Pool[[16]byte]
+}
+
+// newHTTPSCloner returns a new properly initialized *httpsCloner.
+func newHTTPSCloner() (c *httpsCloner) {
+ return &httpsCloner{
+ rr: syncutil.NewPool(func() (v *dns.HTTPS) {
+ return &dns.HTTPS{}
+ }),
+
+ alpn: syncutil.NewPool(func() (v *dns.SVCBAlpn) {
+ return &dns.SVCBAlpn{}
+ }),
+ dohpath: syncutil.NewPool(func() (v *dns.SVCBDoHPath) {
+ return &dns.SVCBDoHPath{}
+ }),
+ echconfig: syncutil.NewPool(func() (v *dns.SVCBECHConfig) {
+ return &dns.SVCBECHConfig{}
+ }),
+ ipv4hint: syncutil.NewPool(func() (v *dns.SVCBIPv4Hint) {
+ return &dns.SVCBIPv4Hint{}
+ }),
+ ipv6hint: syncutil.NewPool(func() (v *dns.SVCBIPv6Hint) {
+ return &dns.SVCBIPv6Hint{}
+ }),
+ local: syncutil.NewPool(func() (v *dns.SVCBLocal) {
+ return &dns.SVCBLocal{}
+ }),
+ mandatory: syncutil.NewPool(func() (v *dns.SVCBMandatory) {
+ return &dns.SVCBMandatory{}
+ }),
+ noDefALPN: syncutil.NewPool(func() (v *dns.SVCBNoDefaultAlpn) {
+ return &dns.SVCBNoDefaultAlpn{}
+ }),
+ port: syncutil.NewPool(func() (v *dns.SVCBPort) {
+ return &dns.SVCBPort{}
+ }),
+
+ ip: syncutil.NewPool(func() (v *[16]byte) {
+ // Use the IPv6 length to increase the effectiveness of the pool.
+ return &[16]byte{}
+ }),
+ }
+}
+
+// clone returns a deep clone of rr. full is true if rr was cloned entirely
+// without the use of [dns.Copy].
+func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) {
+ if rr == nil {
+ return nil, true
+ }
+
+ clone = c.rr.Get()
+
+ clone.Hdr = rr.Hdr
+ clone.Priority = rr.Priority
+ clone.Target = rr.Target
+
+ if rr.Value == nil {
+ clone.Value = nil
+
+ return clone, true
+ }
+
+ clone.Value = clone.Value[:0]
+ for _, orig := range rr.Value {
+ valClone, valFull := c.cloneKV(orig)
+ if !valFull {
+ // This branch is only reached if there is a new SVCB key-value type
+ // in miekg/dns. Give up and just use their copy function.
+ return dns.Copy(rr).(*dns.HTTPS), false
+ }
+
+ clone.Value = append(clone.Value, valClone)
+ }
+
+ return clone, true
+}
+
+// cloneKV returns a deep clone of orig. full is true if orig was recognized.
+func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue, full bool) {
+ switch orig := orig.(type) {
+ case *dns.SVCBAlpn:
+ v := c.alpn.Get()
+
+ v.Alpn = appendIfNotNil(v.Alpn[:0], orig.Alpn)
+
+ clone = v
+ case *dns.SVCBDoHPath:
+ v := c.dohpath.Get()
+ *v = *orig
+
+ clone = v
+ case *dns.SVCBECHConfig:
+ v := c.echconfig.Get()
+
+ v.ECH = appendIfNotNil(v.ECH[:0], orig.ECH)
+
+ clone = v
+ case *dns.SVCBIPv4Hint:
+ v := c.ipv4hint.Get()
+
+ v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
+
+ clone = v
+ case *dns.SVCBIPv6Hint:
+ v := c.ipv6hint.Get()
+
+ v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
+
+ clone = v
+ case *dns.SVCBLocal:
+ v := c.local.Get()
+ v.KeyCode = orig.KeyCode
+
+ v.Data = appendIfNotNil(v.Data[:0], orig.Data)
+
+ clone = v
+ case *dns.SVCBMandatory:
+ v := c.mandatory.Get()
+
+ v.Code = appendIfNotNil(v.Code[:0], orig.Code)
+
+ clone = v
+ case *dns.SVCBNoDefaultAlpn:
+ clone = c.noDefALPN.Get()
+ case *dns.SVCBPort:
+ v := c.port.Get()
+ *v = *orig
+
+ clone = v
+ default:
+ // This branch is only reached if there is a new SVCB key-value type
+ // in miekg/dns.
+ return nil, false
+ }
+
+ return clone, true
+}
+
+// appendIPs appends the clones of IP addresses from orig to hints and returns
+// the resulting slice. clone is allocated as a single continuous slice.
+func (c *httpsCloner) appendIPs(hints, orig []net.IP) (clone []net.IP) {
+ if len(orig) == 0 {
+ if orig == nil {
+ return nil
+ }
+
+ return []net.IP{}
+ }
+
+ for _, origIP := range orig {
+ ipArr := c.ip.Get()
+ ip := append(ipArr[:0], origIP...)
+ hints = append(hints, ip)
+ }
+
+ return hints
+}
+
+// put returns structures from rr into c's pools.
+func (c *httpsCloner) put(rr *dns.HTTPS) {
+ if rr == nil {
+ return
+ }
+
+ for _, kv := range rr.Value {
+ c.putKV(kv)
+ }
+
+ c.rr.Put(rr)
+}
+
+// putKV returns structures from kv into c's pools.
+func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) {
+ switch kv := kv.(type) {
+ case *dns.SVCBAlpn:
+ c.alpn.Put(kv)
+ case *dns.SVCBDoHPath:
+ c.dohpath.Put(kv)
+ case *dns.SVCBECHConfig:
+ c.echconfig.Put(kv)
+ case *dns.SVCBIPv4Hint:
+ c.putIPs(kv.Hint)
+ c.ipv4hint.Put(kv)
+ case *dns.SVCBIPv6Hint:
+ c.putIPs(kv.Hint)
+ c.ipv6hint.Put(kv)
+ case *dns.SVCBLocal:
+ c.local.Put(kv)
+ case *dns.SVCBMandatory:
+ c.mandatory.Put(kv)
+ case *dns.SVCBNoDefaultAlpn:
+ c.noDefALPN.Put(kv)
+ case *dns.SVCBPort:
+ c.port.Put(kv)
+ default:
+ // This branch is only reached if there is a new SVCB key-value type
+ // in miekg/dns. Noting to do.
+ }
+}
+
+// putIPs returns the underlying arrays of ips into c if possible.
+func (c *httpsCloner) putIPs(ips []net.IP) {
+ for _, ip := range ips {
+ if cap(ip) >= 16 {
+ // nolint:looppointer // Slicing is used to get the array pointer.
+ c.ip.Put((*[16]byte)(ip[:16]))
+ }
+ }
+}
diff --git a/internal/dnsmsg/optcloner.go b/internal/dnsmsg/optcloner.go
new file mode 100644
index 0000000..80810a0
--- /dev/null
+++ b/internal/dnsmsg/optcloner.go
@@ -0,0 +1,108 @@
+package dnsmsg
+
+import (
+ "net"
+
+ "github.com/AdguardTeam/golibs/syncutil"
+ "github.com/miekg/dns"
+)
+
+// optCloner is a pool that can clone common parts of DNS messages of type OPT
+// with fewer allocations.
+type optCloner struct {
+ // Top-level structures.
+
+ rr *syncutil.Pool[dns.OPT]
+
+ // Options.
+
+ cookie *syncutil.Pool[dns.EDNS0_COOKIE]
+ subnet *syncutil.Pool[dns.EDNS0_SUBNET]
+}
+
+// newOPTCloner returns a new properly initialized *optCloner.
+func newOPTCloner() (c *optCloner) {
+ return &optCloner{
+ rr: syncutil.NewPool(func() (v *dns.OPT) {
+ return &dns.OPT{}
+ }),
+
+ cookie: syncutil.NewPool(func() (v *dns.EDNS0_COOKIE) {
+ return &dns.EDNS0_COOKIE{}
+ }),
+ subnet: syncutil.NewPool(func() (v *dns.EDNS0_SUBNET) {
+ return &dns.EDNS0_SUBNET{
+ // Use the IPv6 length to increase the effectiveness of the
+ // pool.
+ Address: make(net.IP, 16),
+ }
+ }),
+ }
+}
+
+// clone returns a deep clone of rr. full is true if rr was cloned entirely
+// without the use of [dns.Copy].
+func (c *optCloner) clone(rr *dns.OPT) (clone *dns.OPT, full bool) {
+ if rr == nil {
+ return nil, true
+ }
+
+ clone = c.rr.Get()
+
+ clone.Hdr = rr.Hdr
+ if rr.Option == nil {
+ clone.Option = nil
+
+ return clone, true
+ }
+
+ clone.Option = clone.Option[:0]
+ for _, orig := range rr.Option {
+ var optClone dns.EDNS0
+
+ switch orig := orig.(type) {
+ case *dns.EDNS0_COOKIE:
+ opt := c.cookie.Get()
+ *opt = *orig
+
+ optClone = opt
+ case *dns.EDNS0_SUBNET:
+ opt := c.subnet.Get()
+ opt.Code = orig.Code
+ opt.Family = orig.Family
+ opt.SourceNetmask = orig.SourceNetmask
+ opt.SourceScope = orig.SourceScope
+
+ opt.Address = append(opt.Address[:0], orig.Address...)
+
+ optClone = opt
+ // TODO(a.garipov): Add more if necessary.
+ default:
+ return dns.Copy(rr).(*dns.OPT), false
+ }
+
+ clone.Option = append(clone.Option, optClone)
+ }
+
+ return clone, true
+}
+
+// put returns structures from rr into c's pools.
+func (c *optCloner) put(rr *dns.OPT) {
+ if rr == nil {
+ return
+ }
+
+ for _, opt := range rr.Option {
+ switch opt := opt.(type) {
+ case *dns.EDNS0_COOKIE:
+ c.cookie.Put(opt)
+ case *dns.EDNS0_SUBNET:
+ c.subnet.Put(opt)
+ default:
+ // Go on.
+ }
+ }
+
+ c.rr.Put(rr)
+}
diff --git a/internal/dnsmsg/rrconstructor.go b/internal/dnsmsg/rrconstructor.go
new file mode 100644
index 0000000..0cb49ee
--- /dev/null
+++ b/internal/dnsmsg/rrconstructor.go
@@ -0,0 +1,142 @@
+package dnsmsg
+
+import (
+ "net"
+ "net/netip"
+
+ "github.com/miekg/dns"
+)
+
+// newA constructs a new resource record of type A, optionally using c to
+// allocate the structure. callers must set rr.Hdr. ip must be an IPv4
+// address.
+func newA(c *Cloner, ip netip.Addr) (rr *dns.A) {
+ if c == nil {
+ rr = &dns.A{}
+ } else {
+ rr = c.a.Get()
+ }
+
+ data := ip.As4()
+ rr.A = appendIfNotNil(rr.A[:0], data[:])
+
+ return rr
+}
+
+// newANetIP constructs a new resource record of type A, optionally using c to
+// allocate the structure. callers must set rr.Hdr.
+func newANetIP(c *Cloner, ip net.IP) (rr *dns.A) {
+ if c == nil {
+ rr = &dns.A{}
+ } else {
+ rr = c.a.Get()
+ }
+
+ rr.A = appendIfNotNil(rr.A[:0], ip)
+
+ return rr
+}
+
+// newAAAA constructs a new resource record of type AAAA, optionally using c to
+// allocate the structure. callers must set rr.Hdr. ip must be an IPv6
+// address.
+func newAAAA(c *Cloner, ip netip.Addr) (rr *dns.AAAA) {
+ if c == nil {
+ rr = &dns.AAAA{}
+ } else {
+ rr = c.aaaa.Get()
+ }
+
+ data := ip.As16()
+ rr.AAAA = appendIfNotNil(rr.AAAA[:0], data[:])
+
+ return rr
+}
+
+// newAAAANetIP constructs a new resource record of type AAAA, optionally using
+// c to allocate the structure. callers must set rr.Hdr.
+func newAAAANetIP(c *Cloner, ip net.IP) (rr *dns.AAAA) {
+ if c == nil {
+ rr = &dns.AAAA{}
+ } else {
+ rr = c.aaaa.Get()
+ }
+
+ rr.AAAA = appendIfNotNil(rr.AAAA[:0], ip)
+
+ return rr
+}
+
+// newCNAME constructs a new resource record of type CNAME, optionally using c
+// to allocate the structure. callers must set rr.Hdr.
+func newCNAME(c *Cloner, target string) (rr *dns.CNAME) {
+ if c == nil {
+ rr = &dns.CNAME{}
+ } else {
+ rr = c.cname.Get()
+ }
+
+ rr.Target = target
+
+ return rr
+}
+
+// newMX constructs a new resource record of type MX, optionally using c to
+// allocate the structure. callers must set rr.Hdr.
+func newMX(c *Cloner, mx string, pref uint16) (rr *dns.MX) {
+ if c == nil {
+ rr = &dns.MX{}
+ } else {
+ rr = c.mx.Get()
+ }
+
+ rr.Mx = mx
+ rr.Preference = pref
+
+ return rr
+}
+
+// newPTR constructs a new resource record of type PTR, optionally using c to
+// allocate the structure. callers must set rr.Hdr.
+func newPTR(c *Cloner, ptr string) (rr *dns.PTR) {
+ if c == nil {
+ rr = &dns.PTR{}
+ } else {
+ rr = c.ptr.Get()
+ }
+
+ rr.Ptr = ptr
+
+ return rr
+}
+
+// newSRV constructs a new resource record of type SRV, optionally using c to
+// allocate the structure. callers must set rr.Hdr.
+func newSRV(c *Cloner, target string, prio, weight, port uint16) (rr *dns.SRV) {
+ if c == nil {
+ rr = &dns.SRV{}
+ } else {
+ rr = c.srv.Get()
+ }
+
+ rr.Target = target
+ rr.Priority = prio
+ rr.Weight = weight
+ rr.Port = port
+
+ return rr
+}
+
+// newTXT constructs a new resource record of type TXT, optionally using c to
+// allocate the structure. callers must set rr.Hdr.
+func newTXT(c *Cloner, txt []string) (rr *dns.TXT) {
+ if c == nil {
+ rr = &dns.TXT{}
+ } else {
+ rr = c.txt.Get()
+ }
+
+ rr.Txt = appendIfNotNil(rr.Txt[:0], txt)
+
+ return rr
+}
diff --git a/internal/dnsmsg/svcbmsg_test.go b/internal/dnsmsg/svcbmsg_test.go
index 3b32f9e..1b8e4c3 100644
--- a/internal/dnsmsg/svcbmsg_test.go
+++ b/internal/dnsmsg/svcbmsg_test.go
@@ -17,7 +17,7 @@ import (
func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
// Preconditions.
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
req := &dns.Msg{
Question: []dns.Question{{
Name: "abcd",
@@ -159,7 +159,7 @@ func TestConstructor_NewDDR(t *testing.T) {
dohPath = "/dns-query"
)
- mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ mc := dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
testCases := []struct {
name string
diff --git a/internal/dnsserver/cache/cache.go b/internal/dnsserver/cache/cache.go
index fbf9829..8dcf89a 100644
--- a/internal/dnsserver/cache/cache.go
+++ b/internal/dnsserver/cache/cache.go
@@ -14,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
- "github.com/AdguardTeam/golibs/mathutil"
"github.com/bluele/gcache"
"github.com/miekg/dns"
)
@@ -152,8 +151,7 @@ func (m *Middleware) set(msg *dns.Msg) (err error) {
exp := time.Duration(ttl) * time.Second
if m.useTTLOverride && msg.Rcode != dns.RcodeServerFailure {
- // TODO(d.kolyshev): Use built-in max in go 1.21.
- exp = mathutil.Max(exp, m.cacheMinTTL)
+ exp = max(exp, m.cacheMinTTL)
setMinTTL(msg, uint32(exp.Seconds()))
}
@@ -266,8 +264,7 @@ func setMinTTL(r *dns.Msg, minTTL uint32) {
for _, rr := range r.Answer {
h := rr.Header()
- // TODO(d.kolyshev): Use built-in max in go 1.21.
- h.Ttl = mathutil.Max(h.Ttl, minTTL)
+ h.Ttl = max(h.Ttl, minTTL)
}
}
diff --git a/internal/dnsserver/context.go b/internal/dnsserver/context.go
index b935638..c89a756 100644
--- a/internal/dnsserver/context.go
+++ b/internal/dnsserver/context.go
@@ -2,21 +2,78 @@ package dnsserver
import (
"context"
+ "fmt"
"net/url"
"time"
)
-// Context Helpers
+// 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
const (
ctxKeyServerInfo ctxKey = iota
- ctxKeyStartTime
ctxKeyRequestInfo
- ctxKeyClientInfo
)
+// type check
+var _ fmt.Stringer = ctxKey(0)
+
+// String implements the [fmt.Stringer] interface for ctxKey.
+func (k ctxKey) String() (s string) {
+ switch k {
+ case ctxKeyServerInfo:
+ return "dnsserver.ctxKeyServerInfo"
+ case ctxKeyRequestInfo:
+ return "dnsserver.ctxKeyRequestInfo"
+ default:
+ panic(fmt.Errorf("bad ctx key value %d", k))
+ }
+}
+
// ServerInfo is a structure that contains basic server information. It is
// attached to every context.Context created inside dnsserver.
type ServerInfo struct {
@@ -30,77 +87,84 @@ type ServerInfo struct {
Proto Protocol
}
-// ContextWithServerInfo attaches ServerInfo to the specified context.
-func ContextWithServerInfo(parent context.Context, s ServerInfo) (ctx context.Context) {
- return context.WithValue(parent, ctxKeyServerInfo, s)
+// ContextWithServerInfo attaches ServerInfo to the specified context. s should
+// not be nil.
+func ContextWithServerInfo(parent context.Context, si *ServerInfo) (ctx context.Context) {
+ return context.WithValue(parent, ctxKeyServerInfo, si)
}
// ServerInfoFromContext gets ServerInfo attached to the context.
-func ServerInfoFromContext(ctx context.Context) (s ServerInfo, found bool) {
- s, found = ctx.Value(ctxKeyServerInfo).(ServerInfo)
+func ServerInfoFromContext(ctx context.Context) (si *ServerInfo, found bool) {
+ v := ctx.Value(ctxKeyServerInfo)
+ if v == nil {
+ return nil, false
+ }
- return s, found
+ ri, ok := v.(*ServerInfo)
+ if !ok {
+ panicBadType(ctxKeyServerInfo, v)
+ }
+
+ return ri, true
}
// MustServerInfoFromContext gets ServerInfo attached to the context and panics
// if it is not found.
-func MustServerInfoFromContext(ctx context.Context) (s ServerInfo) {
- s, found := ServerInfoFromContext(ctx)
+func MustServerInfoFromContext(ctx context.Context) (si *ServerInfo) {
+ si, found := ServerInfoFromContext(ctx)
if !found {
panic("server info not found in the context")
}
- return s
-}
-
-// ContextWithStartTime attaches request's start time to the specified context.
-func ContextWithStartTime(parent context.Context, t time.Time) (ctx context.Context) {
- return context.WithValue(parent, ctxKeyStartTime, t)
-}
-
-// StartTimeFromContext gets request's start time from the context.
-func StartTimeFromContext(ctx context.Context) (startTime time.Time, found bool) {
- startTime, found = ctx.Value(ctxKeyStartTime).(time.Time)
- return startTime, found
-}
-
-// MustStartTimeFromContext gets request's start time from the context or panics
-// if it's not found.
-func MustStartTimeFromContext(ctx context.Context) (t time.Time) {
- st, found := ctx.Value(ctxKeyStartTime).(time.Time)
- if !found {
- panic("request's start time not found in the context")
- }
-
- return st
+ return si
}
// RequestInfo is a structure that contains basic request information. It is
// attached to every context.Context linked to processing a DNS request.
type RequestInfo struct {
- // RequestSize is the size of a DNS request in bytes.
- RequestSize int
+ // URL is the request URL. It is set only if the protocol of the server is
+ // DoH.
+ URL *url.URL
- // ResponseSize is the size of a DNS response in bytes. May be 0 if no
- // response was sent.
- ResponseSize int
+ // Userinfo is the userinfo from the basic authentication header. It is set
+ // only if the protocol of the server is DoH.
+ Userinfo *url.Userinfo
+
+ // StartTime is the request's start time. It's never zero value.
+ StartTime time.Time
+
+ // TLSServerName is the server name field of the client's TLS hello request.
+ // It is set only if the protocol of the server is either DoQ, DoT or DoH.
+ // Note, that the original SNI is transformed to lower-case.
+ //
+ // TODO(ameshkov): use r.TLS with DoH3 (see addRequestInfo).
+ TLSServerName string
}
-// ContextWithRequestInfo attaches RequestInfo to the specified context.
-func ContextWithRequestInfo(parent context.Context, ri RequestInfo) (ctx context.Context) {
+// ContextWithRequestInfo attaches RequestInfo to the specified context. ri
+// should not be nil.
+func ContextWithRequestInfo(parent context.Context, ri *RequestInfo) (ctx context.Context) {
return context.WithValue(parent, ctxKeyRequestInfo, ri)
}
// RequestInfoFromContext gets RequestInfo from the specified context.
-func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, found bool) {
- ri, found = ctx.Value(ctxKeyRequestInfo).(RequestInfo)
+func RequestInfoFromContext(ctx context.Context) (ri *RequestInfo, found bool) {
+ v := ctx.Value(ctxKeyRequestInfo)
+ if v == nil {
+ return nil, false
+ }
- return ri, found
+ ri, ok := v.(*RequestInfo)
+ if !ok {
+ panicBadType(ctxKeyRequestInfo, v)
+ }
+
+ return ri, true
}
// MustRequestInfoFromContext gets RequestInfo attached to the context and
// panics if it is not found.
-func MustRequestInfoFromContext(ctx context.Context) (ri RequestInfo) {
+func MustRequestInfoFromContext(ctx context.Context) (ri *RequestInfo) {
ri, found := RequestInfoFromContext(ctx)
if !found {
panic("request info not found in the context")
@@ -109,40 +173,8 @@ func MustRequestInfoFromContext(ctx context.Context) (ri RequestInfo) {
return ri
}
-// ClientInfo is a structure that contains basic information about the client.
-// It is attached to every context.Context created inside dnsserver.
-type ClientInfo struct {
- // URL is the request URL. It is set only if the protocol of the
- // server is DoH.
- URL *url.URL
-
- // TLSServerName is the server name field of the client's TLS hello
- // request. It is set only if the protocol of the server is either DoQ
- // or DoT or DoH. Note, that the original SNI is transformed to lower-case.
- //
- // TODO(ameshkov): use r.TLS with DoH3 (see httpContextWithClientInfo).
- TLSServerName string
-}
-
-// ContextWithClientInfo attaches the client information to the context.
-func ContextWithClientInfo(parent context.Context, ci ClientInfo) (ctx context.Context) {
- return context.WithValue(parent, ctxKeyClientInfo, ci)
-}
-
-// ClientInfoFromContext returns the client information from the context.
-func ClientInfoFromContext(ctx context.Context) (ci ClientInfo, found bool) {
- ci, found = ctx.Value(ctxKeyClientInfo).(ClientInfo)
-
- return ci, found
-}
-
-// MustClientInfoFromContext gets ClientInfo attached to the context and panics
-// if it is not found.
-func MustClientInfoFromContext(ctx context.Context) (ci ClientInfo) {
- ci, found := ClientInfoFromContext(ctx)
- if !found {
- panic("client info not found in the context")
- }
-
- return ci
+// panicBadType is a helper that panics with a message about the context key and
+// the expected type.
+func panicBadType(key ctxKey, v any) {
+ panic(fmt.Errorf("bad type for %s: %T(%[2]v)", key, v))
}
diff --git a/internal/dnsserver/context_test.go b/internal/dnsserver/context_test.go
index 981cbc3..28ffdc6 100644
--- a/internal/dnsserver/context_test.go
+++ b/internal/dnsserver/context_test.go
@@ -3,7 +3,6 @@ package dnsserver_test
import (
"context"
"testing"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/stretchr/testify/require"
@@ -14,7 +13,7 @@ func TestServerInfoFromContext(t *testing.T) {
_, ok := dnsserver.ServerInfoFromContext(ctx)
require.False(t, ok)
- serverInfo := dnsserver.ServerInfo{
+ serverInfo := &dnsserver.ServerInfo{
Name: "test",
Addr: "127.0.0.1",
Proto: dnsserver.ProtoDNS,
@@ -32,23 +31,3 @@ func TestMustServerInfoFromContext(t *testing.T) {
_ = dnsserver.MustServerInfoFromContext(ctx)
})
}
-
-func TestContextWithStartTime(t *testing.T) {
- ctx := context.Background()
- _, ok := dnsserver.StartTimeFromContext(ctx)
- require.False(t, ok)
-
- startTime := time.Now()
- ctx = dnsserver.ContextWithStartTime(ctx, startTime)
-
- st, ok := dnsserver.StartTimeFromContext(ctx)
- require.True(t, ok)
- require.Equal(t, startTime, st)
-}
-
-func TestMustStartTimeFromContext(t *testing.T) {
- require.Panics(t, func() {
- ctx := context.Background()
- _ = dnsserver.MustStartTimeFromContext(ctx)
- })
-}
diff --git a/internal/dnsserver/disposer.go b/internal/dnsserver/disposer.go
new file mode 100644
index 0000000..6b68551
--- /dev/null
+++ b/internal/dnsserver/disposer.go
@@ -0,0 +1,25 @@
+package dnsserver
+
+import "github.com/miekg/dns"
+
+// Disposer is an interface for pools that can save parts of DNS response
+// messages for later reuse.
+//
+// TODO(a.garipov): Think of ways of extending [ResponseWriter] to do this
+// instead.
+//
+// TODO(a.garipov): Think of a better name. Recycle? Scrap?
+type Disposer interface {
+ // Dispose saves parts of resp for later reuse. resp may be nil.
+ // Implementations must be safe for concurrent use.
+ Dispose(resp *dns.Msg)
+}
+
+// EmptyDisposer is a [Disposer] that does nothing.
+type EmptyDisposer struct{}
+
+// type check
+var _ Disposer = EmptyDisposer{}
+
+// Dispose implements the [Disposer] interface for EmptyDisposer.
+func (EmptyDisposer) Dispose(_ *dns.Msg) {}
diff --git a/internal/dnsserver/dnsserver.go b/internal/dnsserver/dnsserver.go
index 36ade4d..df85084 100644
--- a/internal/dnsserver/dnsserver.go
+++ b/internal/dnsserver/dnsserver.go
@@ -20,11 +20,10 @@ type Handler interface {
// 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.
- // Also, it always contains request's start time that can be retrieved
- // using StartTimeFromContext. Finally, it also must contain ClientInfo
- // that can be retrieved using MustClientInfoFromContext.
+ // 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)
}
@@ -95,5 +94,7 @@ type ResponseWriter interface {
//
// 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/dnsservertest/handler.go b/internal/dnsserver/dnsservertest/handler.go
index 18b444b..179e8c9 100644
--- a/internal/dnsserver/dnsservertest/handler.go
+++ b/internal/dnsserver/dnsservertest/handler.go
@@ -19,9 +19,8 @@ func CreateTestHandler(recordsCount int) (h dnsserver.Handler) {
f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
// Check that necessary context keys are set.
si := dnsserver.MustServerInfoFromContext(ctx)
- _ = dnsserver.MustStartTimeFromContext(ctx)
- ci := dnsserver.MustClientInfoFromContext(ctx)
- if si.Proto.IsStdEncrypted() && ci.TLSServerName == "" {
+ ri := dnsserver.MustRequestInfoFromContext(ctx)
+ if si.Proto.IsStdEncrypted() && ri.TLSServerName == "" {
return errors.Error("client info does not contain server name")
}
diff --git a/internal/dnsserver/dnsservertest/quictracer.go b/internal/dnsserver/dnsservertest/quictracer.go
index 543f0e9..1a77475 100644
--- a/internal/dnsserver/dnsservertest/quictracer.go
+++ b/internal/dnsserver/dnsservertest/quictracer.go
@@ -7,47 +7,66 @@ import (
"github.com/quic-go/quic-go/logging"
)
-// QUICTracer implements the logging.Tracer interface.
+// QUICTracer is a helper structure for tracing QUIC connections.
type QUICTracer struct {
- logging.NullTracer
- tracers []*quicConnTracer
-
// mu protects fields of *QUICTracer and also protects fields of every
// nested *quicConnTracer.
- mu sync.Mutex
+ mu *sync.Mutex
+
+ connTracers []*quicConnTracer
}
-// type check
-var _ logging.Tracer = (*QUICTracer)(nil)
+// NewQUICTracer returns a new QUIC tracer helper.
+func NewQUICTracer() (t *QUICTracer) {
+ return &QUICTracer{
+ mu: &sync.Mutex{},
+ }
+}
// TracerForConnection implements the logging.Tracer interface for *quicTracer.
-func (q *QUICTracer) TracerForConnection(
+func (t *QUICTracer) TracerForConnection(
_ context.Context,
_ logging.Perspective,
- odcid logging.ConnectionID,
-) (connTracer logging.ConnectionTracer) {
- q.mu.Lock()
- defer q.mu.Unlock()
+ _ logging.ConnectionID,
+) (connTracer *logging.ConnectionTracer) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
- tracer := &quicConnTracer{id: odcid, parent: q}
- q.tracers = append(q.tracers, tracer)
+ ct := &quicConnTracer{
+ parentMu: t.mu,
+ }
- return tracer
+ t.connTracers = append(t.connTracers, ct)
+
+ return &logging.ConnectionTracer{
+ SentLongHeaderPacket: ct.SentLongHeaderPacket,
+ }
}
-// QUICConnInfo contains information about packets that were recorded by
-// *QUICTracer.
+// ConnectionsInfo returns the traced connections' information.
+func (t *QUICTracer) ConnectionsInfo() (conns []*QUICConnInfo) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ for _, tracer := range t.connTracers {
+ conns = append(conns, &QUICConnInfo{
+ headers: tracer.headers,
+ })
+ }
+
+ return conns
+}
+
+// QUICConnInfo contains information about packets that were recorded by a
+// [QUICTracer].
type QUICConnInfo struct {
- id logging.ConnectionID
- packets []logging.Header
+ headers []*logging.Header
}
// Is0RTT returns true if this connection's packets contain 0-RTT packets.
func (c *QUICConnInfo) Is0RTT() (ok bool) {
- for _, packet := range c.packets {
- hdr := packet
- packetType := logging.PacketTypeFromHeader(&hdr)
- if packetType == logging.PacketType0RTT {
+ for _, hdr := range c.headers {
+ if t := logging.PacketTypeFromHeader(hdr); t == logging.PacketType0RTT {
return true
}
}
@@ -55,43 +74,23 @@ func (c *QUICConnInfo) Is0RTT() (ok bool) {
return false
}
-// ConnectionsInfo returns the traced connections' information.
-func (q *QUICTracer) ConnectionsInfo() (conns []QUICConnInfo) {
- q.mu.Lock()
- defer q.mu.Unlock()
-
- for _, tracer := range q.tracers {
- conns = append(conns, QUICConnInfo{
- id: tracer.id,
- packets: tracer.packets,
- })
- }
-
- return conns
-}
-
-// quicConnTracer implements the [logging.ConnectionTracer] interface.
+// quicConnTracer is a helper structure for tracing QUIC connections.
type quicConnTracer struct {
- id logging.ConnectionID
- parent *QUICTracer
- packets []logging.Header
-
- logging.NullConnectionTracer
+ parentMu *sync.Mutex
+ headers []*logging.Header
}
-// type check
-var _ logging.ConnectionTracer = (*quicConnTracer)(nil)
-
-// SentLongHeaderPacket implements the [logging.ConnectionTracer] interface for
-// *quicConnTracer.
+// SentLongHeaderPacket is a method for the [logging.ConnectionTracer] method.
func (q *quicConnTracer) SentLongHeaderPacket(
- hdr *logging.ExtendedHeader,
+ extHdr *logging.ExtendedHeader,
_ logging.ByteCount,
+ _ logging.ECN,
_ *logging.AckFrame,
_ []logging.Frame,
) {
- q.parent.mu.Lock()
- defer q.parent.mu.Unlock()
+ q.parentMu.Lock()
+ defer q.parentMu.Unlock()
- q.packets = append(q.packets, hdr.Header)
+ hdr := extHdr.Header
+ q.headers = append(q.headers, &hdr)
}
diff --git a/internal/dnsserver/dnsservertest/server.go b/internal/dnsserver/dnsservertest/server.go
index 38af5b1..1d3d34e 100644
--- a/internal/dnsserver/dnsservertest/server.go
+++ b/internal/dnsserver/dnsservertest/server.go
@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/ameshkov/dnscrypt/v2"
+ "github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
@@ -32,6 +33,7 @@ func RunDNSServer(t testing.TB, h dnsserver.Handler) (s *dnsserver.ServerDNS, ad
Addr: "127.0.0.1:0",
Handler: h,
},
+ MaxUDPRespSize: dns.MaxMsgSize,
}
s = dnsserver.NewServerDNS(conf)
require.Equal(t, dnsserver.ProtoDNS, s.Proto())
diff --git a/internal/dnsserver/doc.go b/internal/dnsserver/doc.go
index 7de2088..c5e55ac 100644
--- a/internal/dnsserver/doc.go
+++ b/internal/dnsserver/doc.go
@@ -151,7 +151,11 @@ Package dnsserver supports customizing server behavior using middlewares. All
you need to do is implement dnsserver.Middleware interface and use it this way:
forwarder := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort("94.140.14.140:53"),
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("94.140.14.140:53"),
+ Timeout: 5 * time.Second,
+ }},
})
middleware := querylog.NewLogMiddleware(os.Stdout)
handler := dnsserver.WithMiddlewares(forwarder, middleware)
diff --git a/internal/dnsserver/example_test.go b/internal/dnsserver/example_test.go
index 4d058a0..4025830 100644
--- a/internal/dnsserver/example_test.go
+++ b/internal/dnsserver/example_test.go
@@ -57,8 +57,10 @@ func ExampleNewServerDNS() {
func ExampleWithMiddlewares() {
// Init a handler func function with middlewares.
forwarder := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort("94.140.14.140:53"),
- Network: forward.NetworkAny,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("94.140.14.140:53"),
+ }},
})
middleware := querylog.NewLogMiddleware(os.Stdout)
diff --git a/internal/dnsserver/forward/error.go b/internal/dnsserver/forward/error.go
index e9b5e15..63d3768 100644
--- a/internal/dnsserver/forward/error.go
+++ b/internal/dnsserver/forward/error.go
@@ -11,7 +11,8 @@ import (
// Error is the forwarding error.
type Error struct {
Err error
- Upstream Upstream
+ Main Upstream
+ Fallback Upstream
}
// type check
@@ -19,7 +20,18 @@ var _ error = (*Error)(nil)
// Error implements the error interface for *Error.
func (err *Error) Error() (msg string) {
- return fmt.Sprintf("forwarding to %s: %s", err.Upstream, err.Err)
+ if err.Fallback == nil {
+ return fmt.Sprintf("forwarding to %s: %s", err.Main, err.Err)
+ } else if err.Main == nil {
+ return fmt.Sprintf("forwarding to fallback %s: %s", err.Fallback, err.Err)
+ }
+
+ return fmt.Sprintf(
+ "forwarding to %s with fallback %s: %s",
+ err.Main,
+ err.Fallback,
+ err.Err,
+ )
}
// type check
@@ -31,13 +43,14 @@ func (err *Error) Unwrap() (unwrapped error) {
}
// annotate is a deferrable helper for forwarding errors.
-func annotate(err error, ups Upstream) (wrapped error) {
+func annotate(err error, ups, fallbackUps Upstream) (wrapped error) {
if err == nil {
return nil
}
return &Error{
Err: err,
- Upstream: ups,
+ Main: ups,
+ Fallback: fallbackUps,
}
}
diff --git a/internal/dnsserver/forward/example_test.go b/internal/dnsserver/forward/example_test.go
index b22035c..e0c5b8b 100644
--- a/internal/dnsserver/forward/example_test.go
+++ b/internal/dnsserver/forward/example_test.go
@@ -15,11 +15,16 @@ func ExampleNewHandler() {
Name: "srv",
Addr: "127.0.0.1:0",
Handler: forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort("8.8.8.8:53"),
- Network: forward.NetworkAny,
- FallbackAddresses: []netip.AddrPort{
- netip.MustParseAddrPort("1.1.1.1:53"),
- },
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("8.8.8.8:53"),
+ Timeout: testTimeout,
+ }},
+ FallbackAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("1.1.1.1:53"),
+ Timeout: testTimeout,
+ }},
}),
},
}
diff --git a/internal/dnsserver/forward/forward.go b/internal/dnsserver/forward/forward.go
index b6c11da..22b9c93 100644
--- a/internal/dnsserver/forward/forward.go
+++ b/internal/dnsserver/forward/forward.go
@@ -6,11 +6,16 @@ The easiest way to use it is to create a new handler using NewHandler and then
use it in your DNS server:
conf.Handler = forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort("8.8.8.8:53"),
- FallbackAddresses: []netip.AddrPort{
- netip.MustParseAddrPort("1.1.1.1:53"),
- },
- Timeout: 5 * time.Second,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("94.140.14.140:53"),
+ Timeout: 5 * time.Second,
+ }},
+ FallbackAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("1.1.1.1:53"),
+ Timeout: 5 * time.Second,
+ }},
})
srv := dnsserver.NewServerDNS(conf)
err := srv.Start(context.Background())
@@ -24,8 +29,7 @@ import (
"fmt"
"io"
"net"
- "net/netip"
- "sync/atomic"
+ "sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
@@ -41,36 +45,42 @@ type Handler struct {
// metrics is a listener for the handler events.
metrics MetricsListener
- // upstream is the main upstream where this handler forwards DNS queries.
- upstream Upstream
-
// rand is a random-number generator that is not cryptographically secure
// and is used for randomized upstream selection and other non-sensitive
// tasks.
rand *rand.Rand
+ // activeUpstreamsMu protects activeUpstreams.
+ activeUpstreamsMu *sync.RWMutex
+
// hcDomainTmpl is the template for domains used to perform healthcheck
// requests.
hcDomainTmpl string
+ // upstreams is a list of all upstreams where this handler can forward DNS
+ // queries with its last failed healthcheck timestamps.
+ upstreams []*upstreamStatus
+
+ // activeUpstreams is a list of active upstreams where this handler forwards
+ // DNS queries. This slice is updated by healthcheck mechanics.
+ activeUpstreams []Upstream
+
// fallbacks is a list of fallback DNS servers.
fallbacks []Upstream
- // lastFailedHealthcheck contains the Unix time of the last time of failed
- // healthcheck.
- lastFailedHealthcheck atomic.Int64
-
- // timeout specifies the query timeout for upstreams and fallbacks.
- timeout time.Duration
-
// hcBackoffTime specifies the delay before returning to the main upstream
// after failed healthcheck probe.
hcBackoff time.Duration
+}
- // useFallbacks is true if the main upstream server failed health check
- // probes and therefore the fallback upstream servers should be used for
- // resolving.
- useFallbacks atomic.Bool
+// upstreamStatus contains upstream with its last failed healthcheck time.
+type upstreamStatus struct {
+ // upstream is an upstream where the handler can forward DNS queries.
+ upstream Upstream
+
+ // lastFailedHealthcheck contains the time of the last failed healthcheck
+ // or zero if the last healthcheck succeeded.
+ lastFailedHealthcheck time.Time
}
// ErrNoResponse is returned from Handler's methods when the desired response
@@ -80,13 +90,6 @@ const ErrNoResponse = errors.Error("no response")
// HandlerConfig is the configuration structure for [NewHandler].
type HandlerConfig struct {
- // Address is the address of the main upstream to which the handler forwards
- // all DNS queries.
- Address netip.AddrPort
-
- // Network is the network protocol for the main upstream address.
- Network Network
-
// MetricsListener is the optional listener for the handler events. Set it
// if you want to keep track of what the handler does and record performance
// metrics. If not set, EmptyMetricsListener is used.
@@ -99,14 +102,15 @@ type HandlerConfig struct {
// return a NOERROR response.
HealthcheckDomainTmpl string
- // FallbackAddresses are the optional fallback DNS servers. A fallback
- // server is used either the main upstream returns an error or when the main
- // upstream returns a SERVFAIL response.
- FallbackAddresses []netip.AddrPort
+ // UpstreamsAddresses is a list of upstream configurations of the main
+ // upstreams where the handler forwards all DNS queries. Items must no be
+ // nil.
+ UpstreamsAddresses []*UpstreamPlainConfig
- // Timeout is the optional query timeout for upstreams and fallbacks. If
- // not set, there is no timeout.
- Timeout time.Duration
+ // FallbackAddresses are the optional fallback upstream configurations. A
+ // fallback server is used either the main upstream returns an error or when
+ // the main upstream returns a SERVFAIL response.
+ FallbackAddresses []*UpstreamPlainConfig
// HealthcheckBackoffDuration is the healthcheck query backoff duration. If
// the main upstream is down, queries will not be routed back to the main
@@ -124,14 +128,10 @@ type HandlerConfig struct {
// handler only support plain DNS upstreams. c must not be nil.
func NewHandler(c *HandlerConfig) (h *Handler) {
h = &Handler{
- upstream: NewUpstreamPlain(&UpstreamPlainConfig{
- Network: c.Network,
- Address: c.Address,
- }),
- rand: rand.New(&rand.LockedSource{}),
- hcDomainTmpl: c.HealthcheckDomainTmpl,
- timeout: c.Timeout,
- hcBackoff: c.HealthcheckBackoffDuration,
+ rand: rand.New(&rand.LockedSource{}),
+ activeUpstreamsMu: &sync.RWMutex{},
+ hcDomainTmpl: c.HealthcheckDomainTmpl,
+ hcBackoff: c.HealthcheckBackoffDuration,
}
h.rand.Seed(uint64(time.Now().UnixNano()))
@@ -142,14 +142,22 @@ func NewHandler(c *HandlerConfig) (h *Handler) {
h.metrics = &EmptyMetricsListener{}
}
- h.fallbacks = make([]Upstream, len(c.FallbackAddresses))
- for i, addr := range c.FallbackAddresses {
- h.fallbacks[i] = NewUpstreamPlain(&UpstreamPlainConfig{
- Network: NetworkAny,
- Address: addr,
+ h.upstreams = make([]*upstreamStatus, 0, len(c.UpstreamsAddresses))
+ h.activeUpstreams = make([]Upstream, 0, len(c.UpstreamsAddresses))
+ for _, upsConf := range c.UpstreamsAddresses {
+ u := NewUpstreamPlain(upsConf)
+ h.activeUpstreams = append(h.activeUpstreams, u)
+ h.upstreams = append(h.upstreams, &upstreamStatus{
+ upstream: u,
+ lastFailedHealthcheck: time.Time{},
})
}
+ h.fallbacks = make([]Upstream, 0, len(c.FallbackAddresses))
+ for _, upsConf := range c.FallbackAddresses {
+ h.fallbacks = append(h.fallbacks, NewUpstreamPlain(upsConf))
+ }
+
if c.HealthcheckInitDuration > 0 {
ctx, cancel := context.WithTimeout(context.Background(), c.HealthcheckInitDuration)
defer cancel()
@@ -167,10 +175,14 @@ var _ io.Closer = &Handler{}
// Close implements the [io.Closer] interface for *Handler.
func (h *Handler) Close() (err error) {
- errs := make([]error, len(h.fallbacks)+1)
- errs[0] = h.upstream.Close()
- for i, f := range h.fallbacks {
- errs[i+1] = f.Close()
+ errs := make([]error, 0, len(h.upstreams)+len(h.fallbacks))
+
+ for _, u := range h.upstreams {
+ errs = append(errs, u.upstream.Close())
+ }
+
+ for _, f := range h.fallbacks {
+ errs = append(errs, f.Close())
}
err = errors.Join(errs...)
@@ -184,18 +196,21 @@ func (h *Handler) Close() (err error) {
// type check
var _ dnsserver.Handler = &Handler{}
-// ServeDNS implements the dnsserver.Handler interface for *Handler.
+// ServeDNS implements the [dnsserver.Handler] interface for *Handler.
func (h *Handler) ServeDNS(
ctx context.Context,
rw dnsserver.ResponseWriter,
req *dns.Msg,
) (err error) {
- defer func() { err = annotate(err, h.upstream) }()
+ var ups, fallbackUps Upstream
+ defer func() { err = annotate(err, ups, fallbackUps) }()
+
+ ups = h.pickActiveUpstream()
+ useFallbacks := ups == nil
- useFallbacks := h.useFallbacks.Load()
var resp *dns.Msg
if !useFallbacks {
- resp, err = h.exchange(ctx, h.upstream, req)
+ resp, err = h.exchange(ctx, ups, req)
var netErr net.Error
// Network error means that something is wrong with the upstream, we
@@ -205,8 +220,8 @@ func (h *Handler) ServeDNS(
if useFallbacks && len(h.fallbacks) > 0 {
i := h.rand.Intn(len(h.fallbacks))
- f := h.fallbacks[i]
- resp, err = h.exchange(ctx, f, req)
+ fallbackUps = h.fallbacks[i]
+ resp, err = h.exchange(ctx, fallbackUps, req)
}
if err != nil {
@@ -232,22 +247,39 @@ func (h *Handler) exchange(
req *dns.Msg,
) (resp *dns.Msg, err error) {
startTime := time.Now()
+ nw := NetworkAny
defer func() {
- h.metrics.OnForwardRequest(ctx, u, req, resp, startTime, err)
+ h.metrics.OnForwardRequest(ctx, u, req, resp, nw, startTime, err)
}()
- if h.timeout > 0 {
- var cancel func()
- ctx, cancel = context.WithTimeout(ctx, h.timeout)
- defer cancel()
- }
+ resp, nw, err = u.Exchange(ctx, req)
- return u.Exchange(ctx, req)
+ return resp, err
}
-// Refresh makes sure that the main upstream is accessible. In case the
-// upstream is down, requests are redirected to fallbacks. When the upstream is
-// detected to be up again, requests are redirected back to it.
+// 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.
func (h *Handler) Refresh(ctx context.Context) (err error) {
return h.refresh(ctx, false)
}
+
+// pickActiveUpstream returns an active upstream randomly picked from the slice
+// of active main upstream servers. Returns nil when active upstreams list is
+// empty and fallbacks should be used.
+func (h *Handler) pickActiveUpstream() (u Upstream) {
+ h.activeUpstreamsMu.RLock()
+ defer h.activeUpstreamsMu.RUnlock()
+
+ if len(h.activeUpstreams) == 0 {
+ return nil
+ }
+
+ 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 7c3221e..352b32e 100644
--- a/internal/dnsserver/forward/forward_test.go
+++ b/internal/dnsserver/forward/forward_test.go
@@ -22,8 +22,8 @@ func TestMain(m *testing.M) {
const testTimeout = 1 * time.Second
// newTimeoutCtx is a test helper that returns a context with a timeout of
-// [testTimeout] and its cancel function being called in the test cleanup.
-// It should not be used where cancellation is expected sooner.
+// [testTimeout] and its cancel function being called in the test cleanup. It
+// should not be used where cancelation is expected sooner.
func newTimeoutCtx(tb testing.TB, parent context.Context) (ctx context.Context) {
tb.Helper()
@@ -38,9 +38,11 @@ func TestHandler_ServeDNS(t *testing.T) {
// No-fallbacks handler.
handler := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort(addr),
- Network: forward.NetworkAny,
- Timeout: testTimeout,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort(addr),
+ Timeout: testTimeout,
+ }},
})
req := dnsservertest.CreateMessage("example.org.", dns.TypeA)
@@ -58,12 +60,16 @@ func TestHandler_ServeDNS(t *testing.T) {
func TestHandler_ServeDNS_fallbackNetError(t *testing.T) {
srv, _ := dnsservertest.RunDNSServer(t, dnsservertest.DefaultHandler())
handler := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort("127.0.0.1:0"),
- Network: forward.NetworkAny,
- FallbackAddresses: []netip.AddrPort{
- netip.MustParseAddrPort(srv.LocalUDPAddr().String()),
- },
- Timeout: testTimeout,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort("127.0.0.1:0"),
+ Timeout: testTimeout,
+ }},
+ FallbackAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort(srv.LocalUDPAddr().String()),
+ Timeout: testTimeout,
+ }},
})
req := dnsservertest.CreateMessage("example.org.", dns.TypeA)
diff --git a/internal/dnsserver/forward/healthcheck.go b/internal/dnsserver/forward/healthcheck.go
index 4cf1282..71cb751 100644
--- a/internal/dnsserver/forward/healthcheck.go
+++ b/internal/dnsserver/forward/healthcheck.go
@@ -12,65 +12,36 @@ import (
"github.com/miekg/dns"
)
-// Healthcheck Logic
-
// refresh is an internal method used in Refresh. It allows to enforce the
// metrics report regardless of the upstream status change.
-func (h *Handler) refresh(ctx context.Context, shouldReport bool) (err error) {
+func (h *Handler) refresh(ctx context.Context, mustReport bool) (err error) {
if len(h.fallbacks) == 0 {
log.Debug("forward: healthcheck: no fallbacks specified")
return nil
}
- var useFallbacks bool
- lastFailed := h.lastFailedHealthcheck.Load()
- shouldReturnToMain := time.Since(time.Unix(lastFailed, 0)) >= h.hcBackoff
- if !shouldReturnToMain {
- // Make sure that useFallbacks is left true if the main upstream is
- // still in the backoff mode.
- useFallbacks = true
- log.Debug("forward: healthcheck: in backoff, will not return to main on success")
- }
+ err = h.healthcheck(ctx, mustReport)
- err = h.healthcheck(ctx)
- if err != nil {
- h.lastFailedHealthcheck.Store(time.Now().Unix())
- useFallbacks = true
- }
-
- statusChanged := h.useFallbacks.CompareAndSwap(!useFallbacks, useFallbacks)
- if statusChanged || shouldReport {
- h.setUpstreamStatus(!useFallbacks)
+ // Set the status metrics for fallbacks depending on whether or not all main
+ // upstream are up.
+ //
+ // TODO(a.meshkov): Enhance the health check mechanism to report metrics for
+ // each fallback separately. See AGDNS-941.
+ for _, fb := range h.fallbacks {
+ h.metrics.OnUpstreamStatusChanged(fb, false, err != nil)
}
return errors.Annotate(err, "forward: %w")
}
-// setUpstreamStatus sets the status metrics for all upstreams depending on
-// whether or not the main upstream is up.
-//
-// TODO(a.meshkov): Enhance the health check mechanism to report metrics for
-// each upstream separately. See AGDNS-941.
-func (h *Handler) setUpstreamStatus(isUp bool) {
- if isUp {
- log.Info("forward: healthcheck: upstream got up")
- } else {
- log.Info("forward: healthcheck: negative probe")
- }
-
- h.metrics.OnUpstreamStatusChanged(h.upstream, true, isUp)
- for _, fb := range h.fallbacks {
- h.metrics.OnUpstreamStatusChanged(fb, false, !isUp)
- }
-}
-
// randomPlaceholder is the placeholder replaced with a random string in
// healthcheck domain names.
const randomPlaceholder = "${RANDOM}"
-// healthcheck returns an error if the handler's main upstream is not up.
-func (h *Handler) healthcheck(ctx context.Context) (err error) {
+// healthcheck returns an error if all of handler's main upstreams are down.
+// Updates handler's activeUpstreams slice.
+func (h *Handler) healthcheck(ctx context.Context, mustReport bool) (err error) {
domain := h.hcDomainTmpl
if strings.Contains(domain, randomPlaceholder) {
randStr := strconv.FormatUint(h.rand.Uint64(), 16)
@@ -79,7 +50,38 @@ func (h *Handler) healthcheck(ctx context.Context) (err error) {
defer func() { err = errors.Annotate(err, "healthcheck: querying %q: %w", domain) }()
- req := &dns.Msg{
+ req := newProbeReq(domain)
+
+ var activeUps []Upstream
+ var errs []error
+ for _, status := range h.upstreams {
+ inBackoff, ckErr := h.healthcheckUpstream(ctx, status, req, mustReport)
+ if inBackoff {
+ continue
+ } else if ckErr != nil {
+ errs = append(errs, ckErr)
+ } else {
+ activeUps = append(activeUps, status.upstream)
+ }
+ }
+
+ h.activeUpstreamsMu.Lock()
+ defer h.activeUpstreamsMu.Unlock()
+
+ h.activeUpstreams = activeUps
+
+ if len(activeUps) == 0 {
+ errs = append(errs, errors.Error("all main upstreams are down"))
+
+ return errors.Join(errs...)
+ }
+
+ return nil
+}
+
+// newProbeReq returns a new request message for given domain.
+func newProbeReq(domain string) (req *dns.Msg) {
+ return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: true,
@@ -90,8 +92,60 @@ func (h *Handler) healthcheck(ctx context.Context) (err error) {
Qclass: dns.ClassINET,
}},
}
+}
- resp, err := h.upstream.Exchange(ctx, req)
+// healthcheckUpstream returns an error if the upstream is down, sets last
+// failed healthcheck timestamp and updates metrics for given upstream.
+func (h *Handler) healthcheckUpstream(
+ ctx context.Context,
+ upsStatus *upstreamStatus,
+ req *dns.Msg,
+ mustReport bool,
+) (inBackoff bool, err error) {
+ lastFailed := upsStatus.lastFailedHealthcheck
+ ups := upsStatus.upstream
+
+ if time.Since(lastFailed) < h.hcBackoff {
+ // Make sure that this main upstream is not in the backoff mode.
+ log.Debug("forward: healthcheck: upstream %s: in backoff", ups)
+
+ return true, nil
+ }
+
+ err = checkUpstream(ctx, ups, req)
+ if err != nil {
+ upsStatus.lastFailedHealthcheck = time.Now()
+ } else {
+ upsStatus.lastFailedHealthcheck = time.Time{}
+ }
+
+ h.reportChange(ups, err, lastFailed.IsZero(), mustReport)
+
+ return false, errors.Annotate(err, "%s: upstream is down: %w", ups)
+}
+
+// reportChange updates the metrics if the status of upstream has changed or an
+// update is required. It also writes to the log if the status has changed.
+func (h *Handler) reportChange(ups Upstream, err error, wasUp, mustReport bool) {
+ isUp := err == nil
+ if wasUp != isUp || mustReport {
+ h.metrics.OnUpstreamStatusChanged(ups, true, isUp)
+ }
+
+ if wasUp == isUp {
+ return
+ }
+
+ if wasUp {
+ log.Error("forward: healthcheck: upstream %s: went down: %s", ups, err)
+ } else {
+ log.Info("forward: healthcheck: upstream %s: got up", ups)
+ }
+}
+
+// checkUpstream returns an error if the given upstream is not up.
+func checkUpstream(ctx context.Context, ups Upstream, req *dns.Msg) (err error) {
+ resp, _, err := ups.Exchange(ctx, req)
if err != nil {
return err
} else if resp == nil {
diff --git a/internal/dnsserver/forward/healthcheck_test.go b/internal/dnsserver/forward/healthcheck_test.go
index 7c224f9..d0dbab2 100644
--- a/internal/dnsserver/forward/healthcheck_test.go
+++ b/internal/dnsserver/forward/healthcheck_test.go
@@ -44,13 +44,17 @@ func TestHandler_Refresh(t *testing.T) {
upstream, _ := dnsservertest.RunDNSServer(t, handlerFunc)
fallback, _ := dnsservertest.RunDNSServer(t, defaultHandler)
handler := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort(upstream.LocalUDPAddr().String()),
- Network: forward.NetworkAny,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort(upstream.LocalUDPAddr().String()),
+ Timeout: testTimeout,
+ }},
HealthcheckDomainTmpl: "${RANDOM}.upstream-check.example",
- FallbackAddresses: []netip.AddrPort{
- netip.MustParseAddrPort(fallback.LocalUDPAddr().String()),
- },
- Timeout: testTimeout,
+ FallbackAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort(fallback.LocalUDPAddr().String()),
+ Timeout: testTimeout,
+ }},
// Make sure that the handler routes queries back to the main upstream
// immediately.
HealthcheckBackoffDuration: 0,
diff --git a/internal/dnsserver/forward/metrics.go b/internal/dnsserver/forward/metrics.go
index 2495021..67d55fa 100644
--- a/internal/dnsserver/forward/metrics.go
+++ b/internal/dnsserver/forward/metrics.go
@@ -7,22 +7,23 @@ import (
"github.com/miekg/dns"
)
-// MetricsListener is an interface that is used for monitoring the
-// forward.Handler state. The handler user may opt to supply a metrics
-// interface implementation that would increment different kinds of metrics
-// (for instance, prometheus metrics).
+// MetricsListener is an interface that is used for monitoring the [Handler]
+// state. The handler user may opt to supply the metrics interface
+// implementation that would increment different kinds of metrics (for instance,
+// prometheus metrics).
type MetricsListener interface {
// OnForwardRequest is called when an upstream has finished processing a
- // request.
- // ctx is the context that has been passed to the handler's ServeDNS
- // function, ups is the Upstream that has been used for that, req and resp
- // are the DNS query and response (response can be nil), startTime is the
- // time when the upstream started processing the request, err is the
- // error if it happened.
+ // request. ctx is the context that has been passed to the handler's
+ // ServeDNS function, ups is the [Upstream] that has been used for that, req
+ // and resp are the DNS request and response (response can be nil), nw is
+ // the network type over which the upstream has finished processing request,
+ // startTime is the timestamp when the upstream has started processing the
+ // request, err is the error if it happened.
OnForwardRequest(
ctx context.Context,
ups Upstream,
req, resp *dns.Msg,
+ nw Network,
startTime time.Time,
err error,
)
@@ -44,6 +45,7 @@ func (e *EmptyMetricsListener) OnForwardRequest(
_ context.Context,
_ Upstream,
_, _ *dns.Msg,
+ _ Network,
_ time.Time,
_ error,
) {
diff --git a/internal/dnsserver/forward/upstream.go b/internal/dnsserver/forward/upstream.go
index da40fe4..e3b538a 100644
--- a/internal/dnsserver/forward/upstream.go
+++ b/internal/dnsserver/forward/upstream.go
@@ -10,7 +10,13 @@ import (
// Upstream is the interface for a DNS client.
type Upstream interface {
- Exchange(ctx context.Context, req *dns.Msg) (resp *dns.Msg, err error)
+ // Exchange processes the given request. Returns a response, network type
+ // over which the request has been processed and an error if happened.
+ //
+ // TODO(a.garipov): Make it more extensible. Either metrics through context,
+ // or returning some interface value, similar to [netext.PacketSession].
+ Exchange(ctx context.Context, req *dns.Msg) (resp *dns.Msg, nw Network, err error)
+
io.Closer
fmt.Stringer
}
diff --git a/internal/dnsserver/forward/upstreamplain.go b/internal/dnsserver/forward/upstreamplain.go
index 41fb49f..e0bb0de 100644
--- a/internal/dnsserver/forward/upstreamplain.go
+++ b/internal/dnsserver/forward/upstreamplain.go
@@ -8,11 +8,11 @@ import (
"net"
"net/netip"
"strings"
- "sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/pool"
"github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
@@ -46,13 +46,13 @@ const (
poolIdleTimeout = time.Second * 30
// minDNSMessageSize is a minimum theoretical size of a DNS message.
minDNSMessageSize = 12 + 5
- // udpBuffSize is the size of buffers we use for UDP messages. We use
+ // udpBufSize is the size of buffers we use for UDP messages. We use
// 4096 since it's highly unlikely that a UDP message can be larger.
//
// TODO(ameshkov): consider making it configurable in the future.
- udpBuffSize = 4096
- // tcpBuffSize is the size of buffers we use for TCP messages.
- tcpBuffSize = dns.MaxMsgSize
+ udpBufSize = 4096
+ // tcpBufSize is the size of buffers we use for TCP messages.
+ tcpBufSize = dns.MaxMsgSize
)
// UpstreamPlain is a simple plain DNS client.
@@ -61,13 +61,16 @@ type UpstreamPlain struct {
connsPoolUDP *pool.Pool
connsPoolTCP *pool.Pool
- // sync.Pool instances we use for TCP and UDP messages buffers
- // in order to avoid extra allocations.
- udpBuffs sync.Pool
- tcpBuffs sync.Pool
+ // Pools used for TCP and UDP messages buffers in order to avoid extra
+ // allocations.
+ udpBufs *syncutil.Pool[[]byte]
+ tcpBufs *syncutil.Pool[[]byte]
addr netip.AddrPort
network Network
+
+ // timeout is the query timeout for this upstream.
+ timeout time.Duration
}
// type check
@@ -80,14 +83,23 @@ type UpstreamPlainConfig struct {
// Address is the address of the upstream DNS server.
Address netip.AddrPort
+
+ // Timeout is the optional query timeout for upstreams. If not set, the
+ // context timeout or [defaultUDPTimeout] is used in case of UDP network.
+ Timeout time.Duration
}
// NewUpstreamPlain returns a new properly initialized *UpstreamPlain. c must
// not be nil.
func NewUpstreamPlain(c *UpstreamPlainConfig) (ups *UpstreamPlain) {
ups = &UpstreamPlain{
+ udpBufs: syncutil.NewSlicePool[byte](udpBufSize),
+ tcpBufs: syncutil.NewSlicePool[byte](tcpBufSize),
+
addr: c.Address,
network: c.Network,
+
+ timeout: c.Timeout,
}
ups.connsPoolUDP = pool.NewPool(poolMaxCapacity, makeConnsPoolFactory(ups, NetworkUDP))
@@ -95,27 +107,37 @@ func NewUpstreamPlain(c *UpstreamPlainConfig) (ups *UpstreamPlain) {
ups.connsPoolTCP = pool.NewPool(poolMaxCapacity, makeConnsPoolFactory(ups, NetworkTCP))
ups.connsPoolTCP.IdleTimeout = poolIdleTimeout
- ups.udpBuffs.New = makeBuffsFactory(udpBuffSize)
- ups.tcpBuffs.New = makeBuffsFactory(tcpBuffSize)
-
return ups
}
-// Exchange implements the Upstream interface for *UpstreamPlain.
-// It handles gracefully the situation with truncated responses and fallbacks
-// to TCP when needed. Use context.WithDeadline to specify timeouts. Ignores
-// net.Error and io.EOF errors that occur when writing response.
-func (u *UpstreamPlain) Exchange(ctx context.Context, req *dns.Msg) (resp *dns.Msg, err error) {
+// Exchange implements the [Upstream] interface for [*UpstreamPlain]. It
+// handles gracefully the situation with truncated responses and fallbacks to
+// TCP when needed. Uses the first of context's deadline and the configured
+// timeout specify exchange deadline. Ignores [net.Error] and [io.EOF] errors
+// that occur when writing response. Returns response, network type over which
+// the request has been processed and error if happened.
+func (u *UpstreamPlain) Exchange(
+ ctx context.Context,
+ req *dns.Msg,
+) (resp *dns.Msg, nw Network, err error) {
defer func() { err = errors.Annotate(err, "upstreamplain: %w") }()
+ if u.timeout > 0 {
+ var cancel func()
+ ctx, cancel = context.WithTimeout(ctx, u.timeout)
+ defer cancel()
+ }
+
// First, we should try sending a DNS query over UDP.
var fallbackToTCP bool
fallbackToTCP, resp, err = u.exchangeUDP(ctx, req)
if !fallbackToTCP {
- return resp, err
+ return resp, NetworkUDP, err
}
- return u.exchangeNet(ctx, req, NetworkTCP)
+ resp, err = u.exchangeNet(ctx, req, NetworkTCP)
+
+ return resp, NetworkTCP, err
}
// Close implements the io.Closer interface for *UpstreamPlain.
@@ -188,12 +210,13 @@ func (u *UpstreamPlain) exchangeNet(
// Get the buffer to use for packing the request and reading the response.
// This buffer needs to be returned back to the pool once we're done.
- buf := u.getBuffer(network)
- defer u.putBuffer(network, buf)
+ bufPtr := u.getBuffer(network)
+ defer u.putBuffer(network, bufPtr)
+
+ buf := (*bufPtr)
// Pack the query into the specified buffer.
- var bufLen int
- bufLen, err = u.packReq(network, buf, req)
+ bufReqLen, err := u.packReq(network, buf, req)
if err != nil {
return nil, fmt.Errorf("packing request: %w", err)
}
@@ -206,7 +229,7 @@ func (u *UpstreamPlain) exchangeNet(
}
// err is already wrapped inside processConn.
- resp, err = u.processConn(ctx, conn, connsPool, network, req, buf, bufLen)
+ resp, err = u.processConn(ctx, conn, connsPool, network, req, buf, bufReqLen)
if isExpectedConnErr(err) {
conn, err = connsPool.Create(ctx)
if err != nil {
@@ -214,7 +237,7 @@ func (u *UpstreamPlain) exchangeNet(
}
// err is already wrapped inside processConn.
- resp, err = u.processConn(ctx, conn, connsPool, network, req, buf, bufLen)
+ resp, err = u.processConn(ctx, conn, connsPool, network, req, buf, bufReqLen)
}
return resp, err
@@ -247,7 +270,7 @@ func validatePlainResponse(req, resp *dns.Msg) (err error) {
}
// defaultUDPTimeout is the default timeout for waiting a valid DNS message or
-// network error.
+// network error over UDP protocol.
const defaultUDPTimeout = 1 * time.Minute
// processConn writes the query to the connection and then reads the response
@@ -255,7 +278,7 @@ const defaultUDPTimeout = 1 * time.Minute
// a network error here, we'll attempt to open a new connection and call this
// function again.
//
-// TODO(ameshkov): 7 parameters in the method is not okay, rework this.
+// TODO(ameshkov): 7 parameters in a method is not okay, rework this.
func (u *UpstreamPlain) processConn(
ctx context.Context,
conn *pool.Conn,
@@ -263,7 +286,7 @@ func (u *UpstreamPlain) processConn(
network Network,
req *dns.Msg,
buf []byte,
- bufLen int,
+ bufReqLen int,
) (resp *dns.Msg, err error) {
// Make sure that we return the connection to the pool in the end or close
// if there was any error.
@@ -289,7 +312,7 @@ func (u *UpstreamPlain) processConn(
}
// Write the request to the connection.
- _, err = conn.Write(buf[:bufLen])
+ _, err = conn.Write(buf[:bufReqLen])
if err != nil {
return nil, fmt.Errorf("writing request: %w", err)
}
@@ -354,50 +377,55 @@ func (u *UpstreamPlain) readMsg(network Network, conn net.Conn, buf []byte) (*dn
return ret, nil
}
-// packReq packs the DNS query to the specified buffer. Returns the error if
-// the query cannot be packed or if it's too large.
+// packReq packs the DNS query to the specified buffer.
func (u *UpstreamPlain) packReq(network Network, buf []byte, req *dns.Msg) (n int, err error) {
reqLen := req.Len()
- if reqLen > len(buf) || reqLen > dns.MaxMsgSize {
+ if reqLen > dns.MaxMsgSize {
return 0, dns.ErrBuf
}
if network == NetworkTCP {
+ if reqLen > len(buf)-2 {
+ return 0, dns.ErrBuf
+ }
+
binary.BigEndian.PutUint16(buf, uint16(reqLen))
_, err = req.PackBuffer(buf[2:])
+
return reqLen + 2, err
}
+ if reqLen > len(buf) {
+ return 0, dns.ErrBuf
+ }
+
_, err = req.PackBuffer(buf)
return reqLen, err
}
-// getBuffer gets a bytes buffer that we'll use for packing the request and
-// then for reading the response.
-func (u *UpstreamPlain) getBuffer(network Network) (b []byte) {
- var bPtr *[]byte
+// getBuffer gets a bytes buffer that used for packing the request and then for
+// reading the response.
+func (u *UpstreamPlain) getBuffer(network Network) (bufPtr *[]byte) {
switch network {
case NetworkTCP:
- bPtr = u.tcpBuffs.Get().(*[]byte)
+ return u.tcpBufs.Get()
case NetworkUDP:
- bPtr = u.udpBuffs.Get().(*[]byte)
+ return u.udpBufs.Get()
default:
- panic("invalid network passed to getBuffer")
+ panic(fmt.Errorf("no bufs for network %q in get", network))
}
-
- return *bPtr
}
-// putBuffer puts the buffer back to the sync.Pool.
-func (u *UpstreamPlain) putBuffer(network Network, b []byte) {
+// putBuffer puts the buffer back to the corresponding pool.
+func (u *UpstreamPlain) putBuffer(network Network, bufPtr *[]byte) {
switch network {
case NetworkTCP:
- u.tcpBuffs.Put(&b)
+ u.tcpBufs.Put(bufPtr)
case NetworkUDP:
- u.udpBuffs.Put(&b)
+ u.udpBufs.Put(bufPtr)
default:
- panic("invalid network passed to putBuffer")
+ panic(fmt.Errorf("no bufs for network %q in put", network))
}
}
@@ -425,15 +453,6 @@ func makeConnsPoolFactory(u *UpstreamPlain, network Network) (f pool.Factory) {
}
}
-// makeBuffsFactory returns a function that is used for sync.Pool.New.
-func makeBuffsFactory(size int) (f func() any) {
- return func() any {
- b := make([]byte, size)
-
- return &b
- }
-}
-
// isExpectedConnErr returns true if the error is expected. In this case,
// we will make a second attempt to process the request.
func isExpectedConnErr(err error) (is bool) {
diff --git a/internal/dnsserver/forward/upstreamplain_test.go b/internal/dnsserver/forward/upstreamplain_test.go
index f08e348..ddf4895 100644
--- a/internal/dnsserver/forward/upstreamplain_test.go
+++ b/internal/dnsserver/forward/upstreamplain_test.go
@@ -19,15 +19,19 @@ func TestUpstreamPlain_Exchange(t *testing.T) {
testCases := []struct {
name string
network forward.Network
+ want forward.Network
}{{
name: "any",
network: forward.NetworkAny,
+ want: forward.NetworkUDP,
}, {
name: "udp",
network: forward.NetworkUDP,
+ want: forward.NetworkUDP,
}, {
name: "tcp",
network: forward.NetworkTCP,
+ want: forward.NetworkTCP,
}}
for _, tc := range testCases {
@@ -40,10 +44,12 @@ func TestUpstreamPlain_Exchange(t *testing.T) {
defer log.OnCloserError(u, log.DEBUG)
req := dnsservertest.CreateMessage("example.org.", dns.TypeA)
- res, err := u.Exchange(newTimeoutCtx(t, context.Background()), req)
+ res, nw, err := u.Exchange(newTimeoutCtx(t, context.Background()), req)
require.NoError(t, err)
require.NotNil(t, res)
dnsservertest.RequireResponse(t, req, res, 1, dns.RcodeSuccess, false)
+
+ assert.Equal(t, tc.want, nw)
})
}
}
@@ -91,10 +97,12 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) {
ctx := context.Background()
- res, err := uUDP.Exchange(newTimeoutCtx(t, ctx), req)
+ res, nw, err := uUDP.Exchange(newTimeoutCtx(t, ctx), req)
require.NoError(t, err)
dnsservertest.RequireResponse(t, req, res, 0, dns.RcodeSuccess, true)
+ assert.Equal(t, forward.NetworkUDP, nw)
+
// Second, check that nothing is truncated over TCP.
uTCP := forward.NewUpstreamPlain(&forward.UpstreamPlainConfig{
Network: forward.NetworkTCP,
@@ -102,10 +110,12 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) {
})
defer log.OnCloserError(uTCP, log.DEBUG)
- res, err = uTCP.Exchange(newTimeoutCtx(t, ctx), req)
+ res, nw, err = uTCP.Exchange(newTimeoutCtx(t, ctx), req)
require.NoError(t, err)
dnsservertest.RequireResponse(t, req, res, 1, dns.RcodeSuccess, false)
+ assert.Equal(t, forward.NetworkTCP, nw)
+
// Now with NetworkANY response is also not truncated since the upstream
// fallbacks to TCP.
uAny := forward.NewUpstreamPlain(&forward.UpstreamPlainConfig{
@@ -114,9 +124,11 @@ func TestUpstreamPlain_Exchange_truncated(t *testing.T) {
})
defer log.OnCloserError(uAny, log.DEBUG)
- res, err = uAny.Exchange(newTimeoutCtx(t, ctx), req)
+ res, nw, err = uAny.Exchange(newTimeoutCtx(t, ctx), req)
require.NoError(t, err)
dnsservertest.RequireResponse(t, req, res, 1, dns.RcodeSuccess, false)
+
+ assert.Equal(t, forward.NetworkTCP, nw)
}
func TestUpstreamPlain_Exchange_fallbackFail(t *testing.T) {
@@ -153,7 +165,7 @@ func TestUpstreamPlain_Exchange_fallbackFail(t *testing.T) {
var resp *dns.Msg
var err error
go func() {
- resp, err = u.Exchange(newTimeoutCtx(t, context.Background()), req)
+ resp, _, err = u.Exchange(newTimeoutCtx(t, context.Background()), req)
testutil.RequireSend(pt, respCh, struct{}{}, testTimeout)
}()
@@ -207,7 +219,7 @@ func TestUpstreamPlain_Exchange_fallbackSuccess(t *testing.T) {
name: "wrong_id",
}, {
udpResp: badQNumResp,
- name: "wrong_question)_number",
+ name: "wrong_question_number",
}, {
udpResp: badQnameResp,
name: "wrong_qname",
@@ -254,7 +266,7 @@ func TestUpstreamPlain_Exchange_fallbackSuccess(t *testing.T) {
var actualResp *dns.Msg
var err error
go func() {
- actualResp, err = u.Exchange(newTimeoutCtx(t, context.Background()), clonedReq)
+ actualResp, _, err = u.Exchange(newTimeoutCtx(t, context.Background()), clonedReq)
testutil.RequireSend(pt, respCh, struct{}{}, testTimeout)
}()
diff --git a/internal/dnsserver/go.mod b/internal/dnsserver/go.mod
index 2ff270a..8b529f7 100644
--- a/internal/dnsserver/go.mod
+++ b/internal/dnsserver/go.mod
@@ -1,21 +1,21 @@
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
-go 1.20
+go 1.21.5
require (
- github.com/AdguardTeam/golibs v0.14.0
+ github.com/AdguardTeam/golibs v0.18.1
github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/ameshkov/dnsstamps v1.0.3
github.com/bluele/gcache v0.0.2
- github.com/miekg/dns v1.1.55
- github.com/panjf2000/ants/v2 v2.7.5
+ github.com/miekg/dns v1.1.56
+ github.com/panjf2000/ants/v2 v2.8.2
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
- github.com/prometheus/client_golang v1.15.1
- github.com/quic-go/quic-go v0.38.0
+ github.com/prometheus/client_golang v1.17.0
+ github.com/quic-go/quic-go v0.39.0
github.com/stretchr/testify v1.8.4
- golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
- golang.org/x/net v0.14.0
- golang.org/x/sys v0.11.0
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+ golang.org/x/net v0.17.0
+ golang.org/x/sys v0.13.0
)
require (
@@ -25,22 +25,22 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
- github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
- github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
+ github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
- github.com/onsi/ginkgo/v2 v2.11.0 // indirect
+ github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
- github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
- github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
- golang.org/x/crypto v0.12.0 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/text v0.12.0 // indirect
- golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
- google.golang.org/protobuf v1.30.0 // indirect
+ github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
+ go.uber.org/mock v0.3.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/mod v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ golang.org/x/tools v0.14.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/internal/dnsserver/go.sum b/internal/dnsserver/go.sum
index 400bc57..42abc22 100644
--- a/internal/dnsserver/go.sum
+++ b/internal/dnsserver/go.sum
@@ -1,5 +1,4 @@
-github.com/AdguardTeam/golibs v0.14.0 h1:/vfJshXBVaevMuBgzAIr+F64XdNqZL+j9F33GXJmgeQ=
-github.com/AdguardTeam/golibs v0.14.0/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk=
+github.com/AdguardTeam/golibs v0.18.1 h1:6u0fvrIj2qjUsRdbIGJ9AR0g5QRSWdKIo/DYl3tp5aM=
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=
@@ -19,94 +18,86 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
+github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
-github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
-github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
-github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU=
-github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
+github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
+github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
+github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
+github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
+github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ=
+github.com/panjf2000/ants/v2 v2.8.2/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
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=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
-github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
-github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
-github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
-github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
-github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc=
+github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
+github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
+github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
-golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
-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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/metrics.go b/internal/dnsserver/metrics.go
index 2e90363..220ec03 100644
--- a/internal/dnsserver/metrics.go
+++ b/internal/dnsserver/metrics.go
@@ -6,28 +6,30 @@ import (
"github.com/miekg/dns"
)
-// MetricsListener is an interface that is used for monitoring the server's
-// state. The dnsserver package user may supply MetricsListener implementation
-// that would increment different kinds of metrics (for instance, prometheus
+// MetricsListener is an interface that is used for monitoring a server's state.
+// The dnsserver package user may supply MetricsListener implementation that
+// would increment different kinds of metrics (for instance, prometheus
// metrics).
+//
// Every function accepts context.Context as a parameter. This context must have
-// server information attached to it. It can be retrieved using functions
-// ServerInfoFromContext or MustServerInfoFromContext.
-// N.B. The implementation must be thread-safe.
+// server information attached to it. It can be retrieved using functions
+// [ServerInfoFromContext] or [MustServerInfoFromContext].
+//
+// NOTE: Implementations must be thread-safe.
type MetricsListener interface {
// OnRequest called when we finished processing a request, and we know what
// response has been written.
//
// ctx is the context of the DNS request. Besides the server info, it also
- // must contain request's start time (retrieved by MustStartTimeFromContext)
- // and request info (MustRequestInfoFromContext).
+ // must contain request info (retrieved by [MustRequestInfoFromContext]).
//
- // req is a DNS request. Note, that if the request was discarded (BadFormat
- // or NotImplemented) this method will still be called so the request
- // message may be incorrect (i.e. no Question section or whatever). res is
- // a DNS response, will always be present. rw is the ResponseWriter that was
- // used to write the response.
- OnRequest(ctx context.Context, req, resp *dns.Msg, rw ResponseWriter)
+ // info contains DNS request and response data. rw is the [ResponseWriter]
+ // that was used to write the response.
+ //
+ // Note, that if the request was discarded (BadFormat or NotImplemented)
+ // this method will still be called so the request message may be incorrect
+ // (i.e. no Question section or whatever).
+ OnRequest(ctx context.Context, info *QueryInfo, rw ResponseWriter)
// OnInvalidMsg called when the server encounters an invalid DNS message.
// It may be simply crap bytes that cannot be unpacked or a message that the
@@ -51,31 +53,43 @@ type MetricsListener interface {
OnQUICAddressValidation(hit bool)
}
-// EmptyMetricsListener implements MetricsListener with empty functions.
-// This implementation is used by default if the user does not supply a custom
-// one.
+// QueryInfo contains the request with its size, and the response with its size.
+type QueryInfo struct {
+ // Request is the DNS request.
+ Request *dns.Msg
+
+ // Response is the DNS response. The response will always be present.
+ Response *dns.Msg
+
+ // Request size is the size of DNS request in bytes.
+ RequestSize int
+
+ // ResponseSize is the size of DNS response in bytes. May be 0 if no
+ // response was sent.
+ ResponseSize int
+}
+
+// EmptyMetricsListener implements [MetricsListener] with empty functions. This
+// implementation is used by default if the user does not supply a custom one.
type EmptyMetricsListener struct{}
// type check
-var _ MetricsListener = (*EmptyMetricsListener)(nil)
+var _ MetricsListener = EmptyMetricsListener{}
-// OnRequest implements the MetricsListener interface for *EmptyMetricsListener.
-func (e *EmptyMetricsListener) OnRequest(_ context.Context, _, _ *dns.Msg, _ ResponseWriter) {
-}
+// OnRequest implements the [MetricsListener] interface for
+// EmptyMetricsListener.
+func (e EmptyMetricsListener) OnRequest(_ context.Context, _ *QueryInfo, _ ResponseWriter) {}
-// OnInvalidMsg implements the MetricsListener interface for *EmptyMetricsListener.
-func (e *EmptyMetricsListener) OnInvalidMsg(_ context.Context) {
-}
+// OnInvalidMsg implements the [MetricsListener] interface for
+// EmptyMetricsListener.
+func (e EmptyMetricsListener) OnInvalidMsg(_ context.Context) {}
-// OnError implements the MetricsListener interface for *EmptyMetricsListener.
-func (e *EmptyMetricsListener) OnError(_ context.Context, _ error) {
-}
+// OnError implements the [MetricsListener] interface for EmptyMetricsListener.
+func (e EmptyMetricsListener) OnError(_ context.Context, _ error) {}
-// OnPanic implements the MetricsListener interface for *EmptyMetricsListener.
-func (e *EmptyMetricsListener) OnPanic(_ context.Context, _ any) {
-}
+// OnPanic implements the [MetricsListener] interface for EmptyMetricsListener.
+func (e EmptyMetricsListener) OnPanic(_ context.Context, _ any) {}
-// OnQUICAddressValidation implements the MetricsListener interface for
-// *EmptyMetricsListener.
-func (e *EmptyMetricsListener) OnQUICAddressValidation(_ bool) {
-}
+// OnQUICAddressValidation implements the [MetricsListener] interface for
+// EmptyMetricsListener.
+func (e EmptyMetricsListener) OnQUICAddressValidation(_ bool) {}
diff --git a/internal/dnsserver/msg.go b/internal/dnsserver/msg.go
index 0dccc4f..ac19813 100644
--- a/internal/dnsserver/msg.go
+++ b/internal/dnsserver/msg.go
@@ -3,6 +3,7 @@ package dnsserver
import (
"encoding/binary"
"fmt"
+ "slices"
"github.com/miekg/dns"
)
@@ -33,18 +34,18 @@ func questionData(m *dns.Msg) (hostname, qType string) {
}
// packWithPrefix packs a DNS message with a 2-byte prefix with the message
-// length.
-func packWithPrefix(m *dns.Msg) (b []byte, err error) {
- var data []byte
- data, err = m.Pack()
+// length by appending it into buf and returns it.
+func packWithPrefix(m *dns.Msg, buf []byte) (packed []byte, err error) {
+ buf, err = m.PackBuffer(buf)
if err != nil {
return nil, err
}
- msg := make([]byte, 2+len(data))
+ // Try to reuse the slice if there is already space there.
+ packed = slices.Grow(buf, 2)[:len(buf)+2]
- // *dns.Msg.Pack guarantees that data is less or equal to math.MaxUint16.
- binary.BigEndian.PutUint16(msg, uint16(len(data)))
- copy(msg[2:], data)
- return msg, nil
+ copy(packed[2:], buf)
+ binary.BigEndian.PutUint16(packed[:2], uint16(len(buf)))
+
+ return packed, nil
}
diff --git a/internal/dnsserver/netext/packetconn_linux.go b/internal/dnsserver/netext/packetconn_linux.go
index 8ffa687..0f68cd9 100644
--- a/internal/dnsserver/netext/packetconn_linux.go
+++ b/internal/dnsserver/netext/packetconn_linux.go
@@ -8,8 +8,8 @@ package netext
import (
"fmt"
"net"
- "sync"
+ "github.com/AdguardTeam/golibs/syncutil"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
@@ -49,13 +49,7 @@ type sessionPacketConn struct {
}
// oobPool is the pool of byte slices for out-of-band data.
-var oobPool = &sync.Pool{
- New: func() (v any) {
- b := make([]byte, IPDstOOBSize)
-
- return &b
- },
-}
+var oobPool = syncutil.NewSlicePool[byte](IPDstOOBSize)
// IPDstOOBSize is the required size of the control-message buffer for
// [net.UDPConn.ReadMsgUDP] to read the original destination on Linux.
@@ -65,7 +59,7 @@ const IPDstOOBSize = 40
// ReadFromSession implements the [SessionPacketConn] interface for *packetConn.
func (c *sessionPacketConn) ReadFromSession(b []byte) (n int, s PacketSession, err error) {
- oobPtr := oobPool.Get().(*[]byte)
+ oobPtr := oobPool.Get()
defer oobPool.Put(oobPtr)
var oobn int
diff --git a/internal/dnsserver/normalize.go b/internal/dnsserver/normalize.go
index 76a2170..6d4870d 100644
--- a/internal/dnsserver/normalize.go
+++ b/internal/dnsserver/normalize.go
@@ -14,25 +14,33 @@ const responsePaddingMaxSize = 32
// respPadBuf is a fixed buffer to draw on for padding.
var respPadBuf [responsePaddingMaxSize]byte
-// normalize adds an OPT record that the reflects the intent from request.
-// It also truncates the response and pads response if needed.
+// normalizeTCP adds an OPT record that reflects the intent from request over
+// TCP. It also truncates and pads the response if needed. When the request
+// was over TCP, we set the maximum allowed response size at 64K.
+func normalizeTCP(proto Protocol, req, resp *dns.Msg) {
+ normalize(NetworkTCP, proto, req, resp, dns.MaxMsgSize)
+}
+
+// normalize adds an OPT record that reflects the intent from request. It also
+// truncates and pads the response if needed.
//
// TODO(ameshkov): Consider adding EDNS0COOKIE support.
-func normalize(network Network, proto Protocol, req, resp *dns.Msg) {
+func normalize(network Network, proto Protocol, req, resp *dns.Msg, maxMsgSize uint16) {
reqOpt := req.IsEdns0()
if reqOpt == nil {
- truncate(resp, dnsSize(network, req))
+ truncate(resp, maxDNSSize(network, 0, maxMsgSize))
resp.Compress = true
return
}
var respOpt *dns.OPT
+ ednsUDPSize := reqOpt.UDPSize()
if respOpt = resp.IsEdns0(); respOpt != nil {
respOpt.Hdr.Name = "."
respOpt.Hdr.Rrtype = dns.TypeOPT
respOpt.SetVersion(0)
- respOpt.SetUDPSize(reqOpt.UDPSize())
+ respOpt.SetUDPSize(ednsUDPSize)
// OPT record allows storing additional info in the TTL field:
// https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.3
@@ -57,7 +65,7 @@ func normalize(network Network, proto Protocol, req, resp *dns.Msg) {
}
// Make sure that we don't send messages larger than the protocol supports.
- truncate(resp, dnsSize(network, req))
+ truncate(resp, maxDNSSize(network, ednsUDPSize, maxMsgSize))
// Always compress the response.
resp.Compress = true
@@ -80,25 +88,16 @@ func truncate(resp *dns.Msg, size int) {
}
}
-// dnsSize returns the buffer size *advertised* in the requests OPT record.
-// Or when the request was over TCP, we return the maximum allowed size of 64K.
-// network can be either "tcp" or "udp".
-func dnsSize(network Network, r *dns.Msg) (n int) {
- var size uint16
- if o := r.IsEdns0(); o != nil {
- size = o.UDPSize()
- }
-
+// maxDNSSize returns the maximum buffer size for this network. For
+// [NetworkTCP], it returns [dns.MaxMsgSize]. For [NetworkUDP], it takes into
+// account the advertised size in the requests EDNS(0) OPT record, if any, and
+// the given maximum value.
+func maxDNSSize(network Network, ednsUDPSize, maxMsgSize uint16) (n int) {
if network != NetworkUDP {
return dns.MaxMsgSize
}
- if size < dns.MinMsgSize {
- return dns.MinMsgSize
- }
-
- // normalize size
- return int(size)
+ return int(max(min(ednsUDPSize, maxMsgSize), dns.MinMsgSize))
}
// filterUnsupportedOptions filters out unsupported EDNS0 options. The
diff --git a/internal/dnsserver/prometheus/cache_test.go b/internal/dnsserver/prometheus/cache_test.go
index 5c30a6b..49a5d6a 100644
--- a/internal/dnsserver/prometheus/cache_test.go
+++ b/internal/dnsserver/prometheus/cache_test.go
@@ -31,8 +31,9 @@ func TestCacheMetricsListener_integration_cache(t *testing.T) {
// set both hits and misses.
for i := 0; i < 10; i++ {
ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo)
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{})
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ })
nrw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr)
diff --git a/internal/dnsserver/prometheus/forward.go b/internal/dnsserver/prometheus/forward.go
index e832406..08d416e 100644
--- a/internal/dnsserver/prometheus/forward.go
+++ b/internal/dnsserver/prometheus/forward.go
@@ -42,6 +42,7 @@ func (f *ForwardMetricsListener) OnForwardRequest(
_ context.Context,
ups forward.Upstream,
_, resp *dns.Msg,
+ nw forward.Network,
startTime time.Time,
err error,
) {
@@ -50,7 +51,7 @@ func (f *ForwardMetricsListener) OnForwardRequest(
to := ups.String()
- forwardRequestTotal.WithLabelValues(to).Inc()
+ forwardRequestTotal.WithLabelValues(to, string(nw)).Inc()
elapsed := time.Since(startTime).Seconds()
forwardRequestDuration.WithLabelValues(to).Observe(elapsed)
@@ -124,7 +125,7 @@ var (
Namespace: namespace,
Subsystem: subsystemForward,
Help: "The number of processed DNS requests.",
- }, []string{"to"})
+ }, []string{"to", "network"})
forwardResponseRCode = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "response_rcode_total",
diff --git a/internal/dnsserver/prometheus/forward_test.go b/internal/dnsserver/prometheus/forward_test.go
index 928b5b4..a27599c 100644
--- a/internal/dnsserver/prometheus/forward_test.go
+++ b/internal/dnsserver/prometheus/forward_test.go
@@ -21,8 +21,10 @@ func TestForwardMetricsListener_integration_request(t *testing.T) {
// Initialize a new forward.Handler and set the metrics listener.
handler := forward.NewHandler(&forward.HandlerConfig{
- Address: netip.MustParseAddrPort(addr),
- Network: forward.NetworkAny,
+ UpstreamsAddresses: []*forward.UpstreamPlainConfig{{
+ Network: forward.NetworkAny,
+ Address: netip.MustParseAddrPort(addr),
+ }},
MetricsListener: prometheus.NewForwardMetricsListener(0),
})
diff --git a/internal/dnsserver/prometheus/helper.go b/internal/dnsserver/prometheus/helper.go
index 2c86f14..3886997 100644
--- a/internal/dnsserver/prometheus/helper.go
+++ b/internal/dnsserver/prometheus/helper.go
@@ -28,7 +28,7 @@ func newReqLabelMetricKey(
network: string(dnsserver.NetworkFromAddr(rw.LocalAddr())),
qType: typeToString(req),
family: raddrToFamily(rw.RemoteAddr()),
- srvInfo: dnsserver.MustServerInfoFromContext(ctx),
+ srvInfo: *dnsserver.MustServerInfoFromContext(ctx),
}
}
diff --git a/internal/dnsserver/prometheus/prometheus_test.go b/internal/dnsserver/prometheus/prometheus_test.go
index 3539b04..c1f85f3 100644
--- a/internal/dnsserver/prometheus/prometheus_test.go
+++ b/internal/dnsserver/prometheus/prometheus_test.go
@@ -18,7 +18,7 @@ func TestMain(m *testing.M) {
const testReqDomain = "request-domain.example"
// testServerInfo is the common server information structure for tests.
-var testServerInfo = dnsserver.ServerInfo{
+var testServerInfo = &dnsserver.ServerInfo{
Name: "test_server",
Addr: "127.0.0.1:80",
Proto: dnsserver.ProtoDNS,
diff --git a/internal/dnsserver/prometheus/ratelimit_test.go b/internal/dnsserver/prometheus/ratelimit_test.go
index aff617c..bc9cdd0 100644
--- a/internal/dnsserver/prometheus/ratelimit_test.go
+++ b/internal/dnsserver/prometheus/ratelimit_test.go
@@ -20,7 +20,7 @@ import (
func TestRateLimiterMetricsListener_integration_cache(t *testing.T) {
rps := 5
- rl := ratelimit.NewBackOff(&ratelimit.BackOffConfig{
+ rl := ratelimit.NewBackoff(&ratelimit.BackoffConfig{
Allowlist: ratelimit.NewDynamicAllowlist([]netip.Prefix{}, []netip.Prefix{}),
Period: time.Minute,
Duration: time.Minute,
@@ -42,8 +42,9 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) {
// Pass 10 requests through the middleware.
for i := 0; i < 10; i++ {
ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo)
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{})
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ })
nrw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr)
diff --git a/internal/dnsserver/prometheus/server.go b/internal/dnsserver/prometheus/server.go
index 49a04fa..94a575c 100644
--- a/internal/dnsserver/prometheus/server.go
+++ b/internal/dnsserver/prometheus/server.go
@@ -5,7 +5,6 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
@@ -64,25 +63,23 @@ func NewServerMetricsListener() (l *ServerMetricsListener) {
}),
invalidMsgCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) {
- // TODO(a.garipov): Here and below, remove explicit type
- // declarations in Go 1.21.
- return withSrvInfoLabelValues[prometheus.Counter](invalidMsgTotal, k)
+ return withSrvInfoLabelValues(invalidMsgTotal, k)
}),
errorCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) {
- return withSrvInfoLabelValues[prometheus.Counter](errorTotal, k)
+ return withSrvInfoLabelValues(errorTotal, k)
}),
panicCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) {
- return withSrvInfoLabelValues[prometheus.Counter](panicTotal, k)
+ return withSrvInfoLabelValues(panicTotal, k)
}),
reqDurationHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) {
- return withSrvInfoLabelValues[prometheus.Observer](requestDuration, k)
+ return withSrvInfoLabelValues(requestDuration, k)
}),
reqSizeHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) {
- return withSrvInfoLabelValues[prometheus.Observer](requestSize, k)
+ return withSrvInfoLabelValues(requestSize, k)
}),
respSizeHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) {
- return withSrvInfoLabelValues[prometheus.Observer](responseSize, k)
+ return withSrvInfoLabelValues(responseSize, k)
}),
}
}
@@ -94,27 +91,25 @@ var _ dnsserver.MetricsListener = (*ServerMetricsListener)(nil)
// [*ServerMetricsListener].
func (l *ServerMetricsListener) OnRequest(
ctx context.Context,
- req *dns.Msg,
- resp *dns.Msg,
+ info *dnsserver.QueryInfo,
rw dnsserver.ResponseWriter,
) {
- serverInfo := dnsserver.MustServerInfoFromContext(ctx)
- startTime := dnsserver.MustStartTimeFromContext(ctx)
+ serverInfo := *dnsserver.MustServerInfoFromContext(ctx)
// Increment total requests count metrics.
- l.reqTotalCounters.get(newReqLabelMetricKey(ctx, req, rw)).Inc()
-
- // Increment request duration histogram.
- elapsed := time.Since(startTime).Seconds()
- l.reqDurationHistograms.get(serverInfo).Observe(elapsed)
+ l.reqTotalCounters.get(newReqLabelMetricKey(ctx, info.Request, rw)).Inc()
// Increment request size.
ri := dnsserver.MustRequestInfoFromContext(ctx)
- l.reqSizeHistograms.get(serverInfo).Observe(float64(ri.RequestSize))
+ l.reqSizeHistograms.get(serverInfo).Observe(float64(info.RequestSize))
+
+ // Increment request duration histogram.
+ elapsed := time.Since(ri.StartTime).Seconds()
+ l.reqDurationHistograms.get(serverInfo).Observe(elapsed)
// If resp is not nil, increment response-related metrics.
- if resp != nil {
- l.respSizeHistograms.get(serverInfo).Observe(float64(ri.ResponseSize))
+ if resp := info.Response; resp != nil {
+ l.respSizeHistograms.get(serverInfo).Observe(float64(info.ResponseSize))
l.respRCodeCounters.get(srvInfoRCode{
ServerInfo: serverInfo,
rCode: rCodeToString(resp.Rcode),
@@ -132,19 +127,19 @@ func (l *ServerMetricsListener) OnRequest(
// OnInvalidMsg implements the [dnsserver.MetricsListener] interface for
// [*ServerMetricsListener].
func (l *ServerMetricsListener) OnInvalidMsg(ctx context.Context) {
- l.invalidMsgCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc()
+ l.invalidMsgCounters.get(*dnsserver.MustServerInfoFromContext(ctx)).Inc()
}
// OnError implements the [dnsserver.MetricsListener] interface for
// [*ServerMetricsListener].
func (l *ServerMetricsListener) OnError(ctx context.Context, _ error) {
- l.errorCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc()
+ l.errorCounters.get(*dnsserver.MustServerInfoFromContext(ctx)).Inc()
}
// OnPanic implements the [dnsserver.MetricsListener] interface for
// [*ServerMetricsListener].
func (l *ServerMetricsListener) OnPanic(ctx context.Context, _ any) {
- l.panicCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc()
+ l.panicCounters.get(*dnsserver.MustServerInfoFromContext(ctx)).Inc()
}
// OnQUICAddressValidation implements the [dnsserver.MetricsListener] interface
diff --git a/internal/dnsserver/prometheus/server_test.go b/internal/dnsserver/prometheus/server_test.go
index aed94ed..72d4715 100644
--- a/internal/dnsserver/prometheus/server_test.go
+++ b/internal/dnsserver/prometheus/server_test.go
@@ -70,22 +70,34 @@ func BenchmarkServerMetricsListener(b *testing.B) {
l := prometheus.NewServerMetricsListener()
ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo)
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ })
req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA)
+ reqSize := req.Len()
+
resp := (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess)
- ctx = dnsserver.ContextWithRequestInfo(ctx, dnsserver.RequestInfo{
- RequestSize: req.Len(),
- ResponseSize: resp.Len(),
+ respSize := resp.Len()
+
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
})
+ info := &dnsserver.QueryInfo{
+ Request: req,
+ Response: resp,
+ RequestSize: reqSize,
+ ResponseSize: respSize,
+ }
+
rw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr)
b.Run("OnRequest", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- l.OnRequest(ctx, req, resp, rw)
+ l.OnRequest(ctx, info, rw)
}
})
diff --git a/internal/dnsserver/protocol.go b/internal/dnsserver/protocol.go
index 41597c9..183c338 100644
--- a/internal/dnsserver/protocol.go
+++ b/internal/dnsserver/protocol.go
@@ -3,8 +3,7 @@ package dnsserver
import (
"fmt"
"net"
-
- "golang.org/x/exp/slices"
+ "slices"
)
// Protocol is a DNS protocol.
diff --git a/internal/dnsserver/querylog/querylog.go b/internal/dnsserver/querylog/querylog.go
index 7997931..a5af854 100644
--- a/internal/dnsserver/querylog/querylog.go
+++ b/internal/dnsserver/querylog/querylog.go
@@ -71,7 +71,7 @@ func (l *LogMiddleware) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) {
req.Id,
qTypeStr,
hostname,
- requestInfo.RequestSize,
+ req.Len(),
),
)
@@ -80,13 +80,14 @@ func (l *LogMiddleware) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) {
rsize := 0
if recW.Resp != nil {
rcode = recW.Resp.Rcode
- rsize = requestInfo.ResponseSize
+ // TODO(a.garipov): Count bytes written to the socket only once with
+ // [dnsserver.ResponseWriter].
+ rsize = recW.Resp.Len()
}
sb.WriteString(fmt.Sprintf("%d %d ", rcode, rsize))
// Duration
- startTime := dnsserver.MustStartTimeFromContext(ctx)
- elapsed := time.Since(startTime)
+ elapsed := time.Since(requestInfo.StartTime)
sb.WriteString(fmt.Sprintf("%s\n", elapsed))
// Suppress errors, it's not that important for a query log
diff --git a/internal/dnsserver/querylog/querylog_test.go b/internal/dnsserver/querylog/querylog_test.go
index 88748ce..82ea9d2 100644
--- a/internal/dnsserver/querylog/querylog_test.go
+++ b/internal/dnsserver/querylog/querylog_test.go
@@ -46,15 +46,13 @@ func TestLogMiddleware_Wrap(t *testing.T) {
// Add server and request info to the context
ctx := dnsserver.ContextWithServerInfo(
context.Background(),
- dnsserver.ServerInfo{
+ &dnsserver.ServerInfo{
Name: "test",
Addr: "0.0.0.0:53",
Proto: dnsserver.ProtoDNS,
})
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now().Add(-time.Second))
- ctx = dnsserver.ContextWithRequestInfo(ctx, dnsserver.RequestInfo{
- RequestSize: req.Len(),
- ResponseSize: req.Len(),
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now().Add(-time.Second),
})
// Init response writer with test data
diff --git a/internal/dnsserver/ratelimit/backoff.go b/internal/dnsserver/ratelimit/backoff.go
index c2cc6ce..8259fcb 100644
--- a/internal/dnsserver/ratelimit/backoff.go
+++ b/internal/dnsserver/ratelimit/backoff.go
@@ -12,24 +12,24 @@ import (
cache "github.com/patrickmn/go-cache"
)
-// Back-Off Rate Limiter
+// Backoff Rate Limiter
-// BackOffConfig is the configuration structure for a back-off rate limiter.
-type BackOffConfig struct {
+// BackoffConfig is the configuration structure for a backoff rate limiter.
+type BackoffConfig struct {
// Allowlist defines which IP networks are excluded from rate limiting.
Allowlist Allowlist
// Period is the time during which the rate limiter counts the number of
// times a client make more requests than RPS allows to increment the
- // back-off count for the client.
+ // backoff count for the client.
Period time.Duration
- // Duration is how much a client that has hit the back-off count stays in
- // the back-off state.
+ // Duration is how much a client that has hit the backoff count stays in the
+ // backoff state.
Duration time.Duration
// Count is how many requests a client makes above the RPS before it is
- // counted as a back-off hit.
+ // counted as a backoff hit.
Count int
// ResponseSizeEstimate is the estimate of the size of one DNS response for
@@ -39,8 +39,7 @@ type BackOffConfig struct {
// IPv4RPS is the maximum number of requests per second allowed from a
// single subnet for IPv4 addresses. Any requests above this rate are
- // counted as the client's back-off count. RPS must be greater than
- // zero.
+ // counted as the client's backoff count. RPS must be greater than zero.
IPv4RPS int
// IPv4SubnetKeyLen is the length of the subnet prefix used to calculate
@@ -49,8 +48,7 @@ type BackOffConfig struct {
// IPv6RPS is the maximum number of requests per second allowed from a
// single subnet for IPv6 addresses. Any requests above this rate are
- // counted as the client's back-off count. RPS must be greater than
- // zero.
+ // counted as the client's backoff count. RPS must be greater than zero.
IPv6RPS int
// IPv6SubnetKeyLen is the length of the subnet prefix used to calculate
@@ -62,7 +60,7 @@ type BackOffConfig struct {
RefuseANY bool
}
-// BackOff is the back-off rate limiter which supports allowlists and DNS
+// Backoff is the backoff rate limiter which supports allowlists and DNS
// amplification prevention.
//
// TODO(a.garipov): Consider merging this into ratelimit.Middleware. The
@@ -72,7 +70,7 @@ type BackOffConfig struct {
//
// TODO(ameshkov): Consider splitting rps and other properties by protocol
// family.
-type BackOff struct {
+type Backoff struct {
rpsCounters *cache.Cache
hitCounters *cache.Cache
allowlist Allowlist
@@ -85,11 +83,11 @@ type BackOff struct {
refuseANY bool
}
-// NewBackOff returns a new default rate limiter.
-func NewBackOff(c *BackOffConfig) (l *BackOff) {
+// NewBackoff returns a new default rate limiter.
+func NewBackoff(c *BackoffConfig) (l *Backoff) {
// TODO(ameshkov, a.garipov): Consider adding a job or an endpoint for
// purging the caches to free the map bucket space in the caches.
- return &BackOff{
+ return &Backoff{
// TODO(ameshkov): Consider running the janitor more often.
rpsCounters: cache.New(c.Period, c.Period),
hitCounters: cache.New(c.Duration, c.Duration),
@@ -105,11 +103,11 @@ func NewBackOff(c *BackOffConfig) (l *BackOff) {
}
// type check
-var _ Interface = (*BackOff)(nil)
+var _ Interface = (*Backoff)(nil)
-// IsRateLimited implements the Interface interface for *BackOff. req must not
+// IsRateLimited implements the Interface interface for *Backoff. req must not
// be nil.
-func (l *BackOff) IsRateLimited(
+func (l *Backoff) IsRateLimited(
ctx context.Context,
req *dns.Msg,
ip netip.Addr,
@@ -132,7 +130,7 @@ func (l *BackOff) IsRateLimited(
}
key := l.subnetKey(ip)
- if l.isBackOff(key) {
+ if l.isBackoff(key) {
return true, false, nil
}
@@ -158,8 +156,8 @@ func validateAddr(addr netip.Addr) (err error) {
return nil
}
-// CountResponses implements the Interface interface for *BackOff.
-func (l *BackOff) CountResponses(ctx context.Context, resp *dns.Msg, ip netip.Addr) {
+// CountResponses implements the Interface interface for *Backoff.
+func (l *Backoff) CountResponses(ctx context.Context, resp *dns.Msg, ip netip.Addr) {
respLimit := l.respSzEst
respSize := resp.Len()
for i := 0; i < respSize/respLimit; i++ {
@@ -170,7 +168,7 @@ func (l *BackOff) CountResponses(ctx context.Context, resp *dns.Msg, ip netip.Ad
// subnetKey returns the cache key for the subnet of ip. The key is the string
// representation of ip masked with a specified prefix. ip is assumed to be
// valid.
-func (l *BackOff) subnetKey(ip netip.Addr) (key string) {
+func (l *Backoff) subnetKey(ip netip.Addr) (key string) {
var subnet netip.Prefix
var err error
if ip.Is4() {
@@ -187,8 +185,8 @@ func (l *BackOff) subnetKey(ip netip.Addr) (key string) {
return subnet.String()
}
-// incBackOff increments the number of requests above the RPS for a client.
-func (l *BackOff) incBackOff(key string) {
+// incBackoff increments the number of requests above the RPS for a client.
+func (l *Backoff) incBackoff(key string) {
counterVal, ok := l.hitCounters.Get(key)
if ok {
counterVal.(*atomic.Int64).Add(1)
@@ -203,7 +201,7 @@ func (l *BackOff) incBackOff(key string) {
// hasHitRateLimit checks value for a subnet with rps as a maximum number
// requests per second.
-func (l *BackOff) hasHitRateLimit(subnetIPStr string, rps int) (ok bool) {
+func (l *Backoff) hasHitRateLimit(subnetIPStr string, rps int) (ok bool) {
var r *rpsCounter
rVal, ok := l.rpsCounters.Get(subnetIPStr)
if ok {
@@ -215,14 +213,14 @@ func (l *BackOff) hasHitRateLimit(subnetIPStr string, rps int) (ok bool) {
above := r.add(time.Now())
if above {
- l.incBackOff(subnetIPStr)
+ l.incBackoff(subnetIPStr)
}
return above
}
-// isBackOff returns true if the specified client has hit the RPS too often.
-func (l *BackOff) isBackOff(key string) (ok bool) {
+// isBackoff returns true if the specified client has hit the RPS too often.
+func (l *Backoff) isBackoff(key string) (ok bool) {
counterVal, ok := l.hitCounters.Get(key)
if !ok {
return false
diff --git a/internal/dnsserver/ratelimit/ratelimit.go b/internal/dnsserver/ratelimit/ratelimit.go
index 7933e30..5469004 100644
--- a/internal/dnsserver/ratelimit/ratelimit.go
+++ b/internal/dnsserver/ratelimit/ratelimit.go
@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"net/netip"
+ "slices"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/netutil"
@@ -29,7 +30,7 @@ type Middleware struct {
Metrics MetricsListener
// rateLimit is defines whether the query should be dropped or not. The
- // default implementation of it is [*BackOff].
+ // default implementation of it is [*Backoff].
rateLimit Interface
// protos is a list of protocols this middleware applies rate-limiting logic
@@ -67,13 +68,7 @@ func (mw *Middleware) isEnabledForProto(ctx context.Context) (enabled bool) {
si := dnsserver.MustServerInfoFromContext(ctx)
- for _, proto := range mw.protos {
- if proto == si.Proto {
- return true
- }
- }
-
- return enabled
+ return slices.Contains(mw.protos, si.Proto)
}
// mwHandler implements the [dnsserver.Handler] interface and will be used as a
diff --git a/internal/dnsserver/ratelimit/ratelimit_test.go b/internal/dnsserver/ratelimit/ratelimit_test.go
index efa3ba4..7552ed9 100644
--- a/internal/dnsserver/ratelimit/ratelimit_test.go
+++ b/internal/dnsserver/ratelimit/ratelimit_test.go
@@ -92,7 +92,7 @@ func TestRatelimitMiddleware(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- rl := ratelimit.NewBackOff(&ratelimit.BackOffConfig{
+ rl := ratelimit.NewBackoff(&ratelimit.BackoffConfig{
Allowlist: ratelimit.NewDynamicAllowlist(persistent, nil),
Period: time.Minute,
Duration: time.Minute,
@@ -114,13 +114,14 @@ func TestRatelimitMiddleware(t *testing.T) {
rlMw,
)
- ctx := dnsserver.ContextWithServerInfo(context.Background(), dnsserver.ServerInfo{
+ ctx := dnsserver.ContextWithServerInfo(context.Background(), &dnsserver.ServerInfo{
Name: "test",
Addr: "127.0.0.1",
Proto: dnsserver.ProtoDNS,
})
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{})
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ })
n := 0
for i := 0; i < tc.reqsNum; i++ {
diff --git a/internal/dnsserver/serverbase.go b/internal/dnsserver/serverbase.go
index 720e16e..f23e09f 100644
--- a/internal/dnsserver/serverbase.go
+++ b/internal/dnsserver/serverbase.go
@@ -6,7 +6,6 @@ import (
"os"
"runtime/debug"
"sync"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/log"
@@ -16,35 +15,38 @@ import (
// ConfigBase contains the necessary minimum that every Server needs to
// be initialized.
type ConfigBase struct {
- // Name is used for logging, and it may be used for perf counters reporting.
- Name string
-
- // Addr is the address the server listens to. See [net.Dial] for the
- // documentation on the address format.
- Addr string
-
// Network is the network this server listens to. If empty, the server will
// listen to all networks that are supposed to be used by the server's
// protocol. Note, that it only makes sense for [ServerDNS],
// [ServerDNSCrypt], and [ServerHTTPS].
Network Network
- // Handler is a handler that processes incoming DNS messages.
- // If not set, we'll use the default handler that returns error response
- // to any query.
+ // Handler is a handler that processes incoming DNS messages. If not set,
+ // the default handler, which returns error response to any query, is used.
Handler Handler
- // Metrics is the object we use for collecting performance metrics.
- // This field is optional.
+ // Metrics is the object we use for collecting performance metrics. If not
+ // set, [EmptyMetricsListener] is used.
Metrics MetricsListener
- // BaseContext is a function that should return the base context. If not
- // set, we'll be using context.Background().
- BaseContext func() (ctx context.Context)
+ // Disposer is used to help module users reuse parts of DNS responses. If
+ // 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
// ListenConfig, when set, is used to set options of connections used by the
// DNS server. If nil, an appropriate default ListenConfig is used.
ListenConfig netext.ListenConfig
+
+ // Name is used for logging, and it may be used for perf counters reporting.
+ Name string
+
+ // Addr is the address the server listens to. See [net.Dial] for the
+ // documentation on the address format.
+ Addr string
}
// ServerBase implements base methods that every Server implementation uses.
@@ -65,12 +67,15 @@ type ServerBase struct {
// handler is a handler that processes incoming DNS messages.
handler Handler
- // baseContext is a function that should return the base context.
- baseContext func() (ctx context.Context)
+ // reqCtx is a function that should return the base context.
+ reqCtx ContextConstructor
// metrics is the object we use for collecting performance metrics.
metrics MetricsListener
+ // disposer is used to help module users reuse parts of DNS responses.
+ disposer Disposer
+
// listenConfig is used to set tcpListener and udpListener.
listenConfig netext.ListenConfig
@@ -109,18 +114,23 @@ func newServerBase(proto Protocol, conf ConfigBase) (s *ServerBase) {
network: conf.Network,
handler: conf.Handler,
metrics: conf.Metrics,
+ disposer: conf.Disposer,
listenConfig: conf.ListenConfig,
- baseContext: conf.BaseContext,
+ reqCtx: conf.RequestContext,
}
- if s.baseContext == nil {
- s.baseContext = context.Background
+ 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
}
@@ -176,27 +186,25 @@ func (s *ServerBase) LocalUDPAddr() (addr net.Addr) {
return nil
}
-// requestContext returns a context for one request. It adds the start time and
-// the server information.
-func (s *ServerBase) requestContext() (ctx context.Context) {
- ctx = s.baseContext()
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+// 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()
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
})
- ctx = ContextWithStartTime(ctx, time.Now())
- return ctx
+ return ctx, cancel
}
// serveDNS processes the incoming DNS query and writes the response to the
// specified ResponseWriter. written is false if no response was written.
-func (s *ServerBase) serveDNS(ctx context.Context, m []byte, rw ResponseWriter) (written bool) {
- req := new(dns.Msg)
- if err := req.Unpack(m); err != nil {
- // Ignore the incoming message and let the connection hang as
- // it may be used to amplify
+func (s *ServerBase) serveDNS(ctx context.Context, buf []byte, rw ResponseWriter) (written bool) {
+ req := &dns.Msg{}
+ if err := req.Unpack(buf); err != nil {
+ // Ignore the incoming message and let the connection hang as it may be
+ // used to amplify.
s.metrics.OnInvalidMsg(ctx)
return false
@@ -218,21 +226,47 @@ func (s *ServerBase) serveDNSMsg(
recW := NewRecorderResponseWriter(rw)
s.serveDNSMsgInternal(ctx, req, recW)
- ri := RequestInfo{RequestSize: req.Len()}
resp := recW.Resp
written = resp != nil
+
+ var respLen int
if written {
- ri.ResponseSize = resp.Len()
+ // TODO(a.garipov): Use the real number of bytes written by
+ // [ResponseWriter] to the socket.
+ respLen = resp.Len()
}
- ctx = ContextWithRequestInfo(ctx, ri)
- s.metrics.OnRequest(ctx, req, resp, rw)
+ s.metrics.OnRequest(ctx, &QueryInfo{
+ Request: req,
+ RequestSize: req.Len(),
+ Response: resp,
+ ResponseSize: respLen,
+ }, rw)
log.Debug("[%d]: finished processing \"%s %s\"", req.Id, qType, hostname)
+ s.dispose(rw, resp)
+
return written
}
+// 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
+// and DoT at this point in the code.
+//
+// TODO(a.garipov): Add DoQ as well once the legacy format is removed.
+func (s *ServerBase) dispose(rw ResponseWriter, resp *dns.Msg) {
+ switch rw.(type) {
+ case
+ *tcpResponseWriter,
+ *udpResponseWriter:
+ s.disposer.Dispose(resp)
+ default:
+ // Go on.
+ }
+}
+
// 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.
@@ -273,12 +307,13 @@ func (s *ServerBase) serveDNSMsgInternal(
err := s.handler.ServeDNS(ctx, rw, req)
if err != nil {
- log.Debug("[%d]: handler returned an error: %v", req.Id, err)
+ log.Debug("[%d]: handler returned an error: %s", req.Id, err)
s.metrics.OnError(ctx, err)
+
resp = genErrorResponse(req, dns.RcodeServerFailure)
err = rw.WriteMsg(ctx, req, resp)
if err != nil {
- log.Debug("[%d]: error writing a response: %v", req.Id, err)
+ log.Debug("[%d]: error writing a response: %s", req.Id, err)
}
}
}
diff --git a/internal/dnsserver/serverbench_test.go b/internal/dnsserver/serverbench_test.go
index 2e0d14c..4d05ea5 100644
--- a/internal/dnsserver/serverbench_test.go
+++ b/internal/dnsserver/serverbench_test.go
@@ -21,6 +21,8 @@ import (
"github.com/stretchr/testify/require"
)
+// TODO(ameshkov, a.garipov): Move into the corresponding files.
+
func BenchmarkServeDNS(b *testing.B) {
testCases := []struct {
name string
@@ -150,37 +152,29 @@ func BenchmarkServeTLS(b *testing.B) {
func BenchmarkServeDoH(b *testing.B) {
testCases := []struct {
+ tlsConfig *tls.Config
name string
https bool
http3Enabled bool
}{{
+ tlsConfig: dnsservertest.CreateServerTLSConfig("example.org"),
name: "doh2",
- https: true,
http3Enabled: false,
}, {
+ tlsConfig: dnsservertest.CreateServerTLSConfig("example.org"),
name: "doh3",
- https: true,
http3Enabled: true,
}, {
+ tlsConfig: nil,
name: "plain_http",
- https: true,
http3Enabled: true,
}}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
- proto := "https"
- if !tc.https {
- proto = "http"
- }
-
- var tlsConfig *tls.Config
- if tc.https {
- tlsConfig = dnsservertest.CreateServerTLSConfig("example.org")
- }
srv, err := dnsservertest.RunLocalHTTPSServer(
dnsservertest.DefaultHandler(),
- tlsConfig,
+ tc.tlsConfig,
nil,
)
require.NoError(b, err)
@@ -190,9 +184,10 @@ func BenchmarkServeDoH(b *testing.B) {
})
// Prepare a test message.
- m := new(dns.Msg)
- m.SetQuestion("example.org.", dns.TypeA)
- data, _ := m.Pack()
+ m := (&dns.Msg{}).SetQuestion("example.org.", dns.TypeA)
+ data, err := m.Pack()
+ require.NoError(b, err)
+
msg := make([]byte, 2+len(data))
binary.BigEndian.PutUint16(msg, uint16(len(data)))
copy(msg[2:], data)
@@ -203,11 +198,11 @@ func BenchmarkServeDoH(b *testing.B) {
addr = srv.LocalUDPAddr()
}
- client, err := createDoHClient(addr, tlsConfig)
+ client, err := newDoHClient(addr, tc.tlsConfig)
require.NoError(b, err)
// Prepare http.Request.
- req, err := createDoHRequest(proto, http.MethodPost, m)
+ req, err := newDoHRequest(http.MethodPost, m, tc.tlsConfig != nil)
require.NoError(b, err)
b.ReportAllocs()
@@ -219,11 +214,12 @@ func BenchmarkServeDoH(b *testing.B) {
var buf []byte
buf, err = io.ReadAll(res.Body)
- _ = res.Body.Close()
+ require.NoError(b, err)
+
+ err = res.Body.Close()
require.NoError(b, err)
require.GreaterOrEqual(b, len(buf), dnsserver.DNSHeaderSize)
}
- b.StopTimer()
})
}
}
@@ -317,9 +313,7 @@ func BenchmarkServeQUIC(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- var resp *dns.Msg
- resp, err = sendQUICMessage(sess, req, false)
- require.NoError(b, err)
+ resp := sendQUICMessage(b, sess, req, false)
require.NotNil(b, resp)
require.True(b, resp.Response)
}
diff --git a/internal/dnsserver/serverdns.go b/internal/dnsserver/serverdns.go
index 1e568a6..3734f1a 100644
--- a/internal/dnsserver/serverdns.go
+++ b/internal/dnsserver/serverdns.go
@@ -9,6 +9,7 @@ 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"
"github.com/panjf2000/ants/v2"
)
@@ -42,26 +43,33 @@ type ConfigDNS struct {
// not set it defaults to DefaultWriteTimeout.
WriteTimeout time.Duration
- // UDPSize is the default buffer size to use to read incoming UDP messages.
- // If not set it defaults to dns.MinMsgSize (512 B).
+ // UDPSize is the size of the buffers used to read incoming UDP messages.
+ // If not set it defaults to [dns.MinMsgSize], 512 B.
UDPSize int
- // TCPSize is the default buffer size to use to read incoming TCP messages.
- // If not set it defaults to dns.MinMsgSize (512 B).
- // Note that there's a difference between TCP and UDP - incoming message
- // may be bigger than TCPSize. In this case, we'll process it, but we
- // won't use tcpPool.
+ // TCPSize is the initial size of the buffers used to read incoming TCP
+ // messages. If not set it defaults to [dns.MinMsgSize], 512 B.
TCPSize int
- // TCP idle timeout for multiple queries.
- // If not set it defaults to DefaultTCPIdleTimeout.
+ // MaxUDPRespSize is the maximum size of DNS response over UDP protocol.
+ MaxUDPRespSize uint16
+
+ // TCPIdleTimeout is the timeout for waiting between multiple queries. If
+ // not set it defaults to [DefaultTCPIdleTimeout].
TCPIdleTimeout time.Duration
+
+ // MaxPipelineCount is the maximum number of simultaneously processing TCP
+ // messages per one connection. If MaxPipelineEnabled is true, it must be
+ // greater than zero.
+ MaxPipelineCount uint
+
+ // MaxPipelineEnabled, if true, enables TCP pipeline limiting.
+ MaxPipelineEnabled bool
}
// ServerDNS is a plain DNS server (e.g. it supports UDP and TCP protocols).
type ServerDNS struct {
*ServerBase
- conf ConfigDNS
// workerPool is a goroutine workerPool we use to process DNS queries.
// Complicated logic may require growing the goroutine's stack, and we
@@ -69,15 +77,22 @@ type ServerDNS struct {
// time on this is to reuse already existing goroutines.
workerPool *ants.Pool
- // udpPool is a pool for UDP message buffers.
- udpPool sync.Pool
+ // udpPool is a pool for UDP request buffers.
+ udpPool *syncutil.Pool[[]byte]
- // tcpPool is a pool for TCP message buffers.
- tcpPool sync.Pool
+ // tcpPool is a pool for TCP request buffers.
+ tcpPool *syncutil.Pool[[]byte]
+
+ // respPool is a pool for response buffers.
+ respPool *syncutil.Pool[[]byte]
// tcpConns is a set that is used to track active connections.
tcpConns map[net.Conn]struct{}
- tcpConnsMu sync.Mutex
+ tcpConnsMu *sync.Mutex
+
+ // TODO(ameshkov, a.garipov): Only save the parameters a server actually
+ // needs.
+ conf ConfigDNS
}
// type check
@@ -102,6 +117,7 @@ func newServerDNS(proto Protocol, conf ConfigDNS) (s *ServerDNS) {
if conf.TCPIdleTimeout == 0 {
conf.TCPIdleTimeout = DefaultTCPIdleTimeout
}
+
// Use dns.MinMsgSize since 99% of DNS queries fit this size, so this is
// a sensible default.
if conf.UDPSize == 0 {
@@ -117,14 +133,17 @@ func newServerDNS(proto Protocol, conf ConfigDNS) (s *ServerDNS) {
s = &ServerDNS{
ServerBase: newServerBase(proto, conf.ConfigBase),
- conf: conf,
workerPool: newPoolNonblocking(),
- }
- // Initialize internal properties.
- s.tcpConns = map[net.Conn]struct{}{}
- s.udpPool.New = makePacketBuffer(conf.UDPSize)
- s.tcpPool.New = makePacketBuffer(conf.TCPSize)
+ udpPool: syncutil.NewSlicePool[byte](conf.UDPSize),
+ tcpPool: syncutil.NewSlicePool[byte](conf.TCPSize),
+ respPool: syncutil.NewSlicePool[byte](dns.MinMsgSize),
+
+ tcpConns: map[net.Conn]struct{}{},
+ tcpConnsMu: &sync.Mutex{},
+
+ conf: conf,
+ }
return s
}
@@ -139,11 +158,10 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) {
if s.started {
return ErrServerAlreadyStarted
}
- s.started = true
log.Info("[%s]: Starting the server", s.Name())
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
@@ -175,6 +193,8 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) {
go s.startServeTCP(ctx)
}
+ s.started = true
+
log.Info("[%s]: Server has been started", s.Name())
return nil
@@ -259,48 +279,44 @@ func (s *ServerDNS) unblockTCPConns() {
}
}
-// makePacketBuffer returns a function that we use for byte buffer pools.
-func makePacketBuffer(size int) (f func() any) {
- return func() any {
- b := make([]byte, size)
- return &b
- }
-}
-
// writeDeadlineSetter is an interface for connections that can set write
// deadlines.
type writeDeadlineSetter interface {
SetWriteDeadline(t time.Time) (err error)
}
-// withWriteDeadline is a helper that takes the deadline of the context and the
-// write timeout into account. It sets the write deadline on conn before
-// calling f and resets it once f is done.
+// withWriteDeadline is a helper that takes the deadline of the context and
+// timeout into account. It sets the write deadline on conn before calling f
+// and resets it once f is done.
func withWriteDeadline(
ctx context.Context,
- writeTimeout time.Duration,
+ timeout time.Duration,
conn writeDeadlineSetter,
f func(),
) {
- dl, hasDeadline := ctx.Deadline()
- if !hasDeadline {
- dl = time.Now().Add(writeTimeout)
- }
+ // Add the given timeout and let context.WithTimeout decide which one is
+ // sooner.
+ ctx, cancel := context.WithTimeout(ctx, timeout)
defer func() {
+ cancel()
+
err := conn.SetWriteDeadline(time.Time{})
if err != nil && !errors.Is(err, net.ErrClosed) {
- // Consider deadline errors non-critical. Ignore net.ErrClosed as
+ // Consider deadline errors non-critical. Ignore [net.ErrClosed] as
// it is expected to happen when the client closes connections.
- log.Error("removing write deadline: %s", err)
+ log.Error("dnsserver: removing deadlines: %s", err)
}
}()
+ // Since context.WithTimeout has been called, this should return a non-empty
+ // deadline.
+ dl, _ := ctx.Deadline()
err := conn.SetWriteDeadline(dl)
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("setting write deadline: %s", err)
+ // 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)
}
f()
diff --git a/internal/dnsserver/serverdns_test.go b/internal/dnsserver/serverdns_test.go
index c522870..fd4cd76 100644
--- a/internal/dnsserver/serverdns_test.go
+++ b/internal/dnsserver/serverdns_test.go
@@ -25,50 +25,43 @@ func TestServerDNS_StartShutdown(t *testing.T) {
func TestServerDNS_integration_query(t *testing.T) {
testCases := []struct {
- name string
- network dnsserver.Network
- req *dns.Msg
- // if nil, use defaultTestHandler
- handler dnsserver.Handler
- expectedRecordsCount int
- expectedRCode int
- expectedTruncated bool
- expectedMsg func(t *testing.T, m *dns.Msg)
+ name string
+ network dnsserver.Network
+ req *dns.Msg
+ handler dnsserver.Handler
+ wantMsg func(t *testing.T, m *dns.Msg)
+ wantRecordsCount int
+ wantRCode int
+ wantTruncated bool
}{{
- name: "valid_udp_msg",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
+ name: "valid_udp_msg",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}, {
- name: "valid_tcp_msg",
- network: dnsserver.NetworkTCP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
+ name: "valid_tcp_msg",
+ network: dnsserver.NetworkTCP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}, {
// This test checks that we remove unsupported EDNS0 options from
// the response.
- name: "udp_edns0_supported_options",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
- expectedMsg: func(t *testing.T, m *dns.Msg) {
- opt := m.IsEdns0()
- require.NotNil(t, opt)
- require.Len(t, opt.Option, 1)
- require.Equal(t, uint16(dns.EDNS0EXPIRE), opt.Option[0].Option())
- },
+ name: "udp_edns0_supported_options",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
@@ -96,12 +89,19 @@ func TestServerDNS_integration_query(t *testing.T) {
},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantMsg: func(t *testing.T, m *dns.Msg) {
+ opt := m.IsEdns0()
+ require.NotNil(t, opt)
+ require.Len(t, opt.Option, 1)
+ require.Equal(t, uint16(dns.EDNS0EXPIRE), opt.Option[0].Option())
+ },
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}, {
// Check that we reject invalid DNS messages (like having two questions)
- name: "reject_msg",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 0,
- expectedRCode: dns.RcodeFormatError,
+ name: "reject_msg",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
@@ -109,38 +109,47 @@ func TestServerDNS_integration_query(t *testing.T) {
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 0,
+ wantRCode: dns.RcodeFormatError,
}, {
- // Checks that we handle mixed case domain names
- name: "udp_mixed_case",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
+ // Check that we handle mixed case domain names.
+ name: "udp_mixed_case",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: "eXaMplE.oRg.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}, {
// Checks that we respond with NotImplemented to requests with OpcodeStatus
// also checks that Opcode is unchanged in the response
- name: "not_implemented_msg",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 0,
- expectedRCode: dns.RcodeNotImplemented,
+ name: "not_implemented_msg",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true, Opcode: dns.OpcodeStatus},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 0,
+ wantRCode: dns.RcodeNotImplemented,
}, {
// Checks that we respond with SERVFAIL in case if the handler
// returns an error
- name: "handler_failure",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 0,
- expectedRCode: dns.RcodeServerFailure,
+ name: "handler_failure",
+ network: dnsserver.NetworkUDP,
+ req: &dns.Msg{
+ MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
+ Question: []dns.Question{
+ {Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
+ },
+ },
handler: dnsserver.HandlerFunc(func(
_ context.Context,
_ dnsserver.ResponseWriter,
@@ -148,51 +157,43 @@ func TestServerDNS_integration_query(t *testing.T) {
) (err error) {
return errors.Error("something went wrong")
}),
- req: &dns.Msg{
- MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
- Question: []dns.Question{
- {Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
- },
- },
+ wantRecordsCount: 0,
+ wantRCode: dns.RcodeServerFailure,
}, {
// Checks that Z flag is set to zero even when the query has it
// See https://github.com/miekg/dns/issues/975
- name: "msg_with_zflag",
- network: dnsserver.NetworkUDP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
+ name: "msg_with_zflag",
+ network: dnsserver.NetworkUDP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true, Zero: true},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}, {
// Checks that large responses are getting truncated when
// sent over UDP
name: "udp_truncate_response",
network: dnsserver.NetworkUDP,
- // Set a handler that generates a large response
- handler: dnsservertest.CreateTestHandler(64),
- expectedRecordsCount: 0,
- expectedRCode: dns.RcodeSuccess,
- expectedTruncated: true,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ // Set a handler that generates a large response
+ handler: dnsservertest.CreateTestHandler(64),
+ wantRecordsCount: 0,
+ wantRCode: dns.RcodeSuccess,
+ wantTruncated: true,
}, {
// Checks that if UDP size is large enough there would be no
// truncated responses
name: "udp_edns0_no_truncate",
network: dnsserver.NetworkUDP,
- // Set a handler that generates a large response
- handler: dnsservertest.CreateTestHandler(64),
- expectedRecordsCount: 64,
- expectedRCode: dns.RcodeSuccess,
- expectedTruncated: false,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
@@ -208,30 +209,33 @@ func TestServerDNS_integration_query(t *testing.T) {
},
},
},
+ // Set a handler that generates a large response
+ handler: dnsservertest.CreateTestHandler(64),
+ wantRecordsCount: 64,
+ wantRCode: dns.RcodeSuccess,
+ wantTruncated: false,
}, {
// Checks that large responses are NOT truncated when
// sent over UDP
name: "tcp_no_truncate_response",
network: dnsserver.NetworkTCP,
- // Set a handler that generates a large response
- handler: dnsservertest.CreateTestHandler(64),
- // No truncate
- expectedRecordsCount: 64,
- expectedRCode: dns.RcodeSuccess,
- expectedTruncated: false,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
+ // Set a handler that generates a large response
+ handler: dnsservertest.CreateTestHandler(64),
+ // No truncate
+ wantRecordsCount: 64,
+ wantRCode: dns.RcodeSuccess,
+ wantTruncated: false,
}, {
// Check that the server adds keep alive option when the client
// indicates that supports it.
- name: "tcp_edns0_tcp_keep-alive",
- network: dnsserver.NetworkTCP,
- expectedRecordsCount: 1,
- expectedRCode: dns.RcodeSuccess,
+ name: "tcp_edns0_tcp_keep-alive",
+ network: dnsserver.NetworkTCP,
req: &dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
@@ -253,15 +257,14 @@ func TestServerDNS_integration_query(t *testing.T) {
},
},
},
+ handler: dnsservertest.DefaultHandler(),
+ wantRecordsCount: 1,
+ wantRCode: dns.RcodeSuccess,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- handler := dnsservertest.DefaultHandler()
- if tc.handler != nil {
- handler = tc.handler
- }
- _, addr := dnsservertest.RunDNSServer(t, handler)
+ _, addr := dnsservertest.RunDNSServer(t, tc.handler)
// Send this test message to our server over UDP
c := new(dns.Client)
@@ -271,17 +274,17 @@ func TestServerDNS_integration_query(t *testing.T) {
resp, _, err := c.Exchange(tc.req, addr)
require.NoError(t, err)
require.NotNil(t, resp)
- if tc.expectedMsg != nil {
- tc.expectedMsg(t, resp)
+ if tc.wantMsg != nil {
+ tc.wantMsg(t, resp)
}
dnsservertest.RequireResponse(
t,
tc.req,
resp,
- tc.expectedRecordsCount,
- tc.expectedRCode,
- tc.expectedTruncated,
+ tc.wantRecordsCount,
+ tc.wantRCode,
+ tc.wantTruncated,
)
reqKeepAliveOpt := dnsservertest.FindEDNS0Option[*dns.EDNS0_TCP_KEEPALIVE](tc.req)
@@ -380,12 +383,14 @@ func TestServerDNS_integration_udpMsgIgnore(t *testing.T) {
require.NoError(t, err)
// Try reading the response and make sure that it times out
- _ = conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
+ err = conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
+ require.NoError(t, err)
+
buf := make([]byte, 500)
n, err := conn.Read(buf)
-
require.Error(t, err)
require.Equal(t, 0, n)
+
var netErr net.Error
require.ErrorAs(t, err, &netErr)
require.True(t, netErr.Timeout())
diff --git a/internal/dnsserver/serverdnscrypt.go b/internal/dnsserver/serverdnscrypt.go
index 4297882..e6c3125 100644
--- a/internal/dnsserver/serverdnscrypt.go
+++ b/internal/dnsserver/serverdnscrypt.go
@@ -3,6 +3,7 @@ package dnsserver
import (
"context"
"net"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/errors"
@@ -65,11 +66,10 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) {
if s.started {
return ErrServerAlreadyStarted
}
- s.started = true
log.Info("[%s]: Starting the server", s.Name())
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
@@ -89,6 +89,8 @@ func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) {
return err
}
+ s.started = true
+
log.Info("[%s]: Server has been started", s.Name())
return nil
@@ -202,8 +204,10 @@ 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 := h.srv.requestContext()
- ctx = ContextWithClientInfo(ctx, ClientInfo{})
+ ctx, cancel := h.srv.requestContext()
+ defer cancel()
+
+ ctx = ContextWithRequestInfo(ctx, &RequestInfo{StartTime: time.Now()})
nrw := NewNonWriterResponseWriter(rw.LocalAddr(), rw.RemoteAddr())
written := h.srv.serveDNSMsg(ctx, r, nrw)
@@ -214,7 +218,7 @@ func (h *dnsCryptHandler) ServeDNS(rw dnscrypt.ResponseWriter, r *dns.Msg) (err
network := NetworkFromAddr(rw.LocalAddr())
msg := nrw.Msg()
- normalize(network, ProtoDNSCrypt, r, msg)
+ normalize(network, ProtoDNSCrypt, r, msg, dns.MaxMsgSize)
return rw.WriteMsg(msg)
}
diff --git a/internal/dnsserver/serverdnstcp.go b/internal/dnsserver/serverdnstcp.go
index b003e26..b11721e 100644
--- a/internal/dnsserver/serverdnstcp.go
+++ b/internal/dnsserver/serverdnstcp.go
@@ -7,12 +7,14 @@ import (
"fmt"
"io"
"net"
+ "slices"
"strings"
"sync"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
@@ -21,38 +23,12 @@ func (s *ServerDNS) serveTCP(ctx context.Context, l net.Listener) (err error) {
defer log.OnCloserError(l, log.DEBUG)
for s.isStarted() {
- var conn net.Conn
- conn, err = l.Accept()
- // Check the error code and exit loop if necessary.
+ err = s.acceptTCPConn(ctx, l)
if err != nil {
if !s.isStarted() {
return nil
}
- if isNonCriticalNetError(err) {
- // Non-critical errors, do not register in the metrics or log
- // anywhere.
- continue
- }
-
- return err
- }
-
- s.tcpConnsMu.Lock()
- // Track the connection to allow unblocking reads on shutdown.
- s.tcpConns[conn] = struct{}{}
- s.tcpConnsMu.Unlock()
-
- s.wg.Add(1)
-
- err = s.workerPool.Submit(func() {
- s.serveTCPConn(ctx, conn)
- })
- 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.
- _ = conn.Close()
-
return err
}
}
@@ -60,71 +36,163 @@ func (s *ServerDNS) serveTCP(ctx context.Context, l net.Listener) (err error) {
return nil
}
+// acceptTCPConn reads and starts processing a single TCP connection.
+//
+// NOTE: Any error returned from this method stops handling on l.
+func (s *ServerDNS) acceptTCPConn(ctx context.Context, l net.Listener) (err error) {
+ conn, err := l.Accept()
+ if err != nil {
+ if isNonCriticalNetError(err) {
+ // Non-critical errors, do not register in the metrics or log
+ // anywhere.
+ return nil
+ }
+
+ return err
+ }
+ // Don't defer the close because it's deferred in serveTCPConn.
+
+ func() {
+ s.tcpConnsMu.Lock()
+ defer s.tcpConnsMu.Unlock()
+
+ // Track the connection to allow unblocking reads on shutdown.
+ s.tcpConns[conn] = struct{}{}
+ }()
+
+ s.wg.Add(1)
+
+ return s.workerPool.Submit(func() {
+ s.serveTCPConn(ctx, conn)
+ })
+}
+
+// handshaker is the interface for connections that can perform handshake.
+type handshaker interface {
+ net.Conn
+
+ HandshakeContext(ctx context.Context) (err error)
+}
+
+// handshake performs a TLS handshake if the connection is a [handshaker]. This
+// is useful to prevent writes during reads and reads during writes for TLS
+// connections.
+func handshake(conn net.Conn, timeout time.Duration) (err error) {
+ shaker, ok := conn.(handshaker)
+ if !ok {
+ return nil
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+
+ return shaker.HandshakeContext(ctx)
+}
+
// serveTCPConn serves a single TCP connection.
func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) {
- // we use this to wait until all queries from this connection
- // has been processed before closing the connection
- tcpWg := sync.WaitGroup{}
+ // Use this to wait until all queries from this connection has been
+ // processed before closing it.
+ wg := &sync.WaitGroup{}
+
defer func() {
- tcpWg.Wait()
+ defer s.wg.Done()
+
+ wg.Wait()
+
log.OnCloserError(conn, log.DEBUG)
+
s.tcpConnsMu.Lock()
+ defer s.tcpConnsMu.Unlock()
+
delete(s.tcpConns, conn)
- s.tcpConnsMu.Unlock()
- s.wg.Done()
}()
+
defer s.handlePanicAndRecover(ctx)
+ var msgSema syncutil.Semaphore = syncutil.EmptySemaphore{}
+ if s.conf.MaxPipelineEnabled {
+ msgSema = syncutil.NewChanSemaphore(s.conf.MaxPipelineCount)
+ }
+
+ // writeMu serializes write deadline setting and writing to conn.
+ writeMu := &sync.Mutex{}
+
timeout := s.conf.ReadTimeout
idleTimeout := s.conf.TCPIdleTimeout
+ err := handshake(conn, timeout)
+ if err != nil {
+ s.logReadErr("handshaking", err)
+
+ return
+ }
+
for s.isStarted() {
- m, err := s.readTCPMsg(conn, timeout)
+ err = s.acceptTCPMsg(conn, wg, writeMu, timeout, msgSema)
if err != nil {
- if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
- // Don't even log these.
- return
- }
-
- // No need to read further.
- log.Debug(
- "[%s]: Failed to read message from a NetworkTCP connection: %v",
- s.Name(),
- err,
- )
+ s.logReadErr("reading from conn", err)
return
}
- // RFC 7766 recommends implementing query pipelining, i.e.
- // process all incoming queries concurrently and write responses
- // out of order.
- tcpWg.Add(1)
-
- reqCtx := s.requestContext()
-
- ci := ClientInfo{}
- if cs, ok := conn.(tlsConnectionStater); ok {
- ci.TLSServerName = strings.ToLower(cs.ConnectionState().ServerName)
- }
- reqCtx = ContextWithClientInfo(reqCtx, ci)
-
- err = s.workerPool.Submit(func() {
- s.serveTCPMessage(reqCtx, &tcpWg, m, conn)
- })
- if err != nil {
- // No need for additional handling, the workerPool is most likely to
- // be closed. Log with Info as this is not expected behavior.
- log.Info("[%s]: Failed to submit task: %v", s.Name(), err)
-
- return
- }
-
- // use idle timeout for next queries
+ // Use idle timeout for further queries.
timeout = idleTimeout
}
}
+// logReadErr logs err on debug level unless it's trivial ([io.EOF] or
+// [net.ErrClosed]).
+func (s *ServerDNS) logReadErr(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)
+}
+
+// acceptTCPMsg reads and starts processing a single TCP message. If conn is a
+// TLS connection, the handshake must have already been performed.
+func (s *ServerDNS) acceptTCPMsg(
+ conn net.Conn,
+ wg *sync.WaitGroup,
+ writeMu *sync.Mutex,
+ timeout time.Duration,
+ msgSema syncutil.Semaphore,
+) (err error) {
+ bufPtr, err := s.readTCPMsg(conn, timeout)
+ if err != nil {
+ return err
+ }
+
+ // RFC 7766 recommends implementing query pipelining, i.e. process all
+ // incoming queries concurrently and write responses out of order.
+ wg.Add(1)
+
+ ri := &RequestInfo{
+ StartTime: time.Now(),
+ }
+ if cs, ok := conn.(tlsConnectionStater); ok {
+ ri.TLSServerName = strings.ToLower(cs.ConnectionState().ServerName)
+ }
+
+ reqCtx, reqCancel := s.requestContext()
+ reqCtx = ContextWithRequestInfo(reqCtx, ri)
+
+ err = msgSema.Acquire(reqCtx)
+ if err != nil {
+ return fmt.Errorf("waiting for sema: %w", err)
+ }
+
+ return s.workerPool.Submit(func() {
+ defer reqCancel()
+ defer msgSema.Release()
+
+ s.serveTCPMessage(reqCtx, wg, writeMu, *bufPtr, conn)
+ s.tcpPool.Put(bufPtr)
+ })
+}
+
// tlsConnectionStater is a common interface for connections that can return
// a TLS connection state.
type tlsConnectionStater interface {
@@ -135,19 +203,21 @@ type tlsConnectionStater interface {
func (s *ServerDNS) serveTCPMessage(
ctx context.Context,
wg *sync.WaitGroup,
- m []byte,
+ writeMu *sync.Mutex,
+ buf []byte,
conn net.Conn,
) {
defer wg.Done()
defer s.handlePanicAndRecover(ctx)
rw := &tcpResponseWriter{
+ respPool: s.respPool,
+ writeMu: writeMu,
conn: conn,
writeTimeout: s.conf.WriteTimeout,
idleTimeout: s.conf.TCPIdleTimeout,
}
- written := s.serveDNS(ctx, m, rw)
- s.putTCPBuffer(m)
+ written := s.serveDNS(ctx, buf, rw)
if !written {
// Nothing has been written, we should close the connection in order to
@@ -158,9 +228,12 @@ func (s *ServerDNS) serveTCPMessage(
}
}
-// readTCPMsg reads the next incoming DNS message.
-func (s *ServerDNS) readTCPMsg(conn net.Conn, timeout time.Duration) ([]byte, error) {
- err := conn.SetReadDeadline(time.Now().Add(timeout))
+// readTCPMsg reads the next incoming DNS message. If conn is a TLS connection,
+// the handshake must have already been performed.
+func (s *ServerDNS) readTCPMsg(conn net.Conn, timeout time.Duration) (bufPtr *[]byte, err error) {
+ // Use SetReadDeadline as opposed to SetDeadline, since the TLS handshake
+ // has already been performed, so conn.Read shouldn't perform writes.
+ err = conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
return nil, err
}
@@ -170,49 +243,41 @@ func (s *ServerDNS) readTCPMsg(conn net.Conn, timeout time.Duration) ([]byte, er
return nil, err
}
- m := s.getTCPBuffer(int(length))
- if _, err = io.ReadFull(conn, m); err != nil {
- s.putTCPBuffer(m)
+ bufPtr = s.getTCPBuffer(int(length))
+ _, err = io.ReadFull(conn, *bufPtr)
+ if err != nil {
+ s.tcpPool.Put(bufPtr)
+
return nil, err
}
- return m, nil
+ return bufPtr, nil
}
-// getTCPBuffer gets a TCP buffer to be used to read the incoming DNS query
-// length - the desired TCP buffer length.
-func (s *ServerDNS) getTCPBuffer(length int) (buff []byte) {
- if length > s.conf.TCPSize {
- // If the query is larger than the buffer size
- // don't use sync.Pool at all, just allocate a new array
- return make([]byte, length)
+// getTCPBuffer returns a TCP buffer to be used to read the incoming DNS query
+// with the given length.
+func (s *ServerDNS) getTCPBuffer(length int) (bufPtr *[]byte) {
+ bufPtr = s.tcpPool.Get()
+
+ buf := *bufPtr
+ if l := len(buf); l < length {
+ buf = slices.Grow(buf, length-l)
}
- m := *s.tcpPool.Get().(*[]byte)
+ buf = buf[:length]
+ *bufPtr = buf
- return m[:length]
-}
-
-// putTCPBuffer puts the TCP buffer back to pool.
-func (s *ServerDNS) putTCPBuffer(m []byte) {
- if cap(m) != s.conf.TCPSize {
- // This slice was not got from pool, ignore it
- return
- }
-
- if len(m) != s.conf.TCPSize {
- // Means a new slice was created (see ServerDNS.getTCPBuffer)
- // We should create a new slice with the proper size before
- // putting it back to pool
- m = m[:s.conf.TCPSize]
- }
-
- s.tcpPool.Put(&m)
+ return bufPtr
}
// tcpResponseWriter implements ResponseWriter interface for a DNS-over-TCP or
// a DNS-over-TLS server.
type tcpResponseWriter struct {
+ respPool *syncutil.Pool[[]byte]
+ // writeMu is used to serialize the sequence of setting the write deadline,
+ // writing to a connection, and resetting the write deadline, across
+ // multiple goroutines in the pipeline.
+ writeMu *sync.Mutex
conn net.Conn
writeTimeout time.Duration
idleTimeout time.Duration
@@ -234,17 +299,32 @@ func (r *tcpResponseWriter) RemoteAddr() (addr net.Addr) {
// WriteMsg implements the ResponseWriter interface for *tcpResponseWriter.
func (r *tcpResponseWriter) WriteMsg(ctx context.Context, req, resp *dns.Msg) (err error) {
si := MustServerInfoFromContext(ctx)
- normalize(NetworkTCP, si.Proto, req, resp)
+ normalizeTCP(si.Proto, req, resp)
r.addTCPKeepAlive(req, resp)
- var msg []byte
- msg, err = packWithPrefix(resp)
+ bufPtr := r.respPool.Get()
+ defer func() {
+ if err != nil {
+ r.respPool.Put(bufPtr)
+ }
+ }()
+
+ b, err := packWithPrefix(resp, *bufPtr)
if err != nil {
return fmt.Errorf("tcp: packing response: %w", err)
}
+ *bufPtr = b
+
+ // Serialize the write deadline setting on the shared connection, since
+ // messages accepted over TCP are processed out of order.
+ r.writeMu.Lock()
+ defer r.writeMu.Unlock()
+
+ // Use SetWriteDeadline as opposed to SetDeadline, since the TLS handshake
+ // has already been performed, so conn.Write shouldn't perform reads.
withWriteDeadline(ctx, r.writeTimeout, r.conn, func() {
- _, err = r.conn.Write(msg)
+ _, err = r.conn.Write(b)
})
if err != nil {
diff --git a/internal/dnsserver/serverdnsudp.go b/internal/dnsserver/serverdnsudp.go
index ea0aecc..a44ff34 100644
--- a/internal/dnsserver/serverdnsudp.go
+++ b/internal/dnsserver/serverdnsudp.go
@@ -9,6 +9,7 @@ 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"
)
@@ -17,9 +18,7 @@ func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) (err erro
defer log.OnCloserError(conn, log.DEBUG)
for s.isStarted() {
- var m []byte
- var sess netext.PacketSession
- m, sess, err = s.readUDPMsg(ctx, conn)
+ err = s.acceptUDPMsg(ctx, conn)
if err != nil {
// TODO(ameshkov): Consider the situation where the server is shut
// down and restarted between the two calls to isStarted.
@@ -27,25 +26,6 @@ func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) (err erro
return nil
}
- if isNonCriticalNetError(err) || errors.Is(err, dns.ErrShortRead) {
- // Non-critical errors, do not register in the metrics or log
- // anywhere.
- continue
- }
-
- return err
- }
-
- s.wg.Add(1)
-
- reqCtx := s.requestContext()
- reqCtx = ContextWithClientInfo(reqCtx, ClientInfo{})
-
- err = s.workerPool.Submit(func() {
- s.serveUDPPacket(reqCtx, m, conn, sess)
- })
- if err != nil {
- // The workerPool is probably closed, we should exit.
return err
}
}
@@ -53,10 +33,39 @@ func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) (err erro
return nil
}
+// acceptUDPMsg reads and starts processing a single UDP message.
+func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err error) {
+ bufPtr := s.udpPool.Get()
+ n, sess, err := s.readUDPMsg(ctx, conn, *bufPtr)
+ if err != nil {
+ s.udpPool.Put(bufPtr)
+
+ if isNonCriticalNetError(err) || errors.Is(err, dns.ErrShortRead) {
+ // Non-critical errors, do not register in the metrics or log
+ // anywhere.
+ return nil
+ }
+
+ return err
+ }
+
+ s.wg.Add(1)
+
+ reqCtx, reqCancel := s.requestContext()
+ reqCtx = ContextWithRequestInfo(reqCtx, &RequestInfo{StartTime: time.Now()})
+
+ return s.workerPool.Submit(func() {
+ defer reqCancel()
+
+ s.serveUDPPacket(reqCtx, (*bufPtr)[:n], conn, sess)
+ s.udpPool.Put(bufPtr)
+ })
+}
+
// serveUDPPacket serves a new UDP request.
func (s *ServerDNS) serveUDPPacket(
ctx context.Context,
- m []byte,
+ buf []byte,
conn net.PacketConn,
sess netext.PacketSession,
) {
@@ -64,68 +73,47 @@ func (s *ServerDNS) serveUDPPacket(
defer s.handlePanicAndRecover(ctx)
rw := &udpResponseWriter{
+ respPool: s.respPool,
udpSession: sess,
conn: conn,
writeTimeout: s.conf.WriteTimeout,
+ maxRespSize: s.conf.MaxUDPRespSize,
}
- s.serveDNS(ctx, m, rw)
- s.putUDPBuffer(m)
+ s.serveDNS(ctx, buf, rw)
}
// readUDPMsg reads the next incoming DNS message.
func (s *ServerDNS) readUDPMsg(
ctx context.Context,
conn net.PacketConn,
-) (msg []byte, sess netext.PacketSession, err error) {
+ buf []byte,
+) (n int, sess netext.PacketSession, err error) {
err = conn.SetReadDeadline(time.Now().Add(s.conf.ReadTimeout))
if err != nil {
- return nil, nil, err
+ return 0, nil, err
}
- m := s.getUDPBuffer()
-
- n, sess, err := netext.ReadFromSession(conn, m)
+ n, sess, err = netext.ReadFromSession(conn, buf)
if err != nil {
- s.putUDPBuffer(m)
-
- return nil, nil, err
+ return 0, nil, err
}
if n < DNSHeaderSize {
s.metrics.OnInvalidMsg(ctx)
- s.putUDPBuffer(m)
- return nil, nil, dns.ErrShortRead
+ return 0, nil, dns.ErrShortRead
}
- // Change the slice size to the message size since the one we got
- // from the buffer is always of UDPSize
- m = m[:n]
-
- return m, sess, nil
-}
-
-// getUDPBuffer gets a buffer to use for reading UDP messages.
-func (s *ServerDNS) getUDPBuffer() (buff []byte) {
- return *s.udpPool.Get().(*[]byte)
-}
-
-// putUDPBuffer puts the buffer back to pool.
-func (s *ServerDNS) putUDPBuffer(m []byte) {
- if len(m) != s.conf.UDPSize {
- // Means a new slice was created (see ServerDNS.readUDPMsg)
- // We should create a new slice with the proper size before
- // putting it back to pool
- m = m[:s.conf.UDPSize]
- }
- s.udpPool.Put(&m)
+ return n, sess, nil
}
// udpResponseWriter is a ResponseWriter implementation for DNS-over-UDP.
type udpResponseWriter struct {
+ respPool *syncutil.Pool[[]byte]
udpSession netext.PacketSession
conn net.PacketConn
writeTimeout time.Duration
+ maxRespSize uint16
}
// type check
@@ -147,16 +135,24 @@ func (r *udpResponseWriter) RemoteAddr() (addr net.Addr) {
// WriteMsg implements the ResponseWriter interface for *udpResponseWriter.
func (r *udpResponseWriter) WriteMsg(ctx context.Context, req, resp *dns.Msg) (err error) {
- normalize(NetworkUDP, ProtoDNS, req, resp)
+ normalize(NetworkUDP, ProtoDNS, req, resp, r.maxRespSize)
- var data []byte
- data, err = resp.Pack()
+ bufPtr := r.respPool.Get()
+ defer func() {
+ if err != nil {
+ r.respPool.Put(bufPtr)
+ }
+ }()
+
+ b, err := resp.PackBuffer(*bufPtr)
if err != nil {
return fmt.Errorf("udp: packing response: %w", err)
}
+ *bufPtr = b
+
withWriteDeadline(ctx, r.writeTimeout, r.conn, func() {
- _, err = netext.WriteToSession(r.conn, data, r.udpSession)
+ _, err = netext.WriteToSession(r.conn, b, r.udpSession)
})
if err != nil {
diff --git a/internal/dnsserver/serverhttps.go b/internal/dnsserver/serverhttps.go
index 0bc8fa2..3684466 100644
--- a/internal/dnsserver/serverhttps.go
+++ b/internal/dnsserver/serverhttps.go
@@ -68,6 +68,13 @@ type ConfigHTTPS struct {
// NonDNSHandler handles requests with the path not equal to /dns-query.
// If it is empty, the server will return 404 for requests like that.
NonDNSHandler http.Handler
+
+ // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer
+ // is allowed to open.
+ MaxStreamsPerPeer int
+
+ // QUICLimitsEnabled, if true, enables QUIC limiting.
+ QUICLimitsEnabled bool
}
// ServerHTTPS is a DoH server implementation. It supports both DNS Wireformat
@@ -120,11 +127,10 @@ func (s *ServerHTTPS) Start(ctx context.Context) (err error) {
if s.started {
return ErrServerAlreadyStarted
}
- s.started = true
log.Info("[%s]: Starting the server", s.addr)
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
@@ -148,6 +154,8 @@ func (s *ServerHTTPS) Start(ctx context.Context) (err error) {
}
}
+ s.started = true
+
log.Info("[%s]: Server has been started", s.Name())
return nil
@@ -339,21 +347,22 @@ func (h *httpHandler) remoteAddr(r *http.Request) (addr net.Addr) {
if NetworkFromAddr(h.localAddr) == NetworkUDP {
// This means that we're extracting remoteAddr from an HTTP/3 request.
- return &net.UDPAddr{IP: ip, Port: port}
+ return &net.UDPAddr{IP: ip, Port: int(port)}
}
- return &net.TCPAddr{IP: ip, Port: port}
+ return &net.TCPAddr{IP: ip, Port: int(port)}
}
// 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 cancellation. To add values to the
+// 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 := h.srv.requestContext()
+ ctx, cancel := h.srv.requestContext()
+ defer cancel()
+
if dl, ok := r.Context().Deadline(); ok {
- var cancel func()
ctx, cancel = context.WithDeadline(ctx, dl)
defer cancel()
}
@@ -394,7 +403,7 @@ func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *ht
rAddr := h.remoteAddr(r)
lAddr := h.localAddr
rw := NewNonWriterResponseWriter(lAddr, rAddr)
- ctx = httpContextWithClientInfo(ctx, r)
+ ctx = addRequestInfo(ctx, r)
// Serve the query
written := h.srv.serveDNS(ctx, m, rw)
@@ -418,7 +427,11 @@ func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *ht
// Try writing an error response just in case.
http.Error(w, "Internal error", http.StatusInternalServerError)
+
+ return
}
+
+ h.srv.disposer.Dispose(resp)
}
// writeResponse writes the actual DNS response to the client and takes care of
@@ -431,7 +444,7 @@ func (h *httpHandler) writeResponse(
w http.ResponseWriter,
) (err error) {
// normalize the response
- normalize(NetworkTCP, ProtoDoH, req, resp)
+ normalizeTCP(ProtoDoH, req, resp)
isDNS, _, ct := isDoH(r)
if !isDNS {
@@ -447,9 +460,8 @@ func (h *httpHandler) writeResponse(
buf, err = dnsMsgToJSON(resp)
w.Header().Set(httphdr.ContentType, MimeTypeJSON)
default:
- return fmt.Errorf("invalid content type: %s", ct)
+ err = fmt.Errorf("invalid content type: %q", ct)
}
-
if err != nil {
return err
}
@@ -466,6 +478,7 @@ func (h *httpHandler) writeResponse(
// Write the actual response
log.Debug("[%d] Writing HTTP response", req.Id)
_, err = w.Write(buf)
+
return err
}
@@ -505,7 +518,7 @@ func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) {
return err
}
- qConf := newServerQUICConfig(s.metrics)
+ qConf := newServerQUICConfig(s.metrics, s.conf.QUICLimitsEnabled, s.conf.MaxStreamsPerPeer)
ql, err := quic.ListenEarly(conn, tlsConf, qConf)
if err != nil {
return err
@@ -517,19 +530,24 @@ func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) {
return nil
}
-// httpContextWithClientInfo adds client info to the context.
-func httpContextWithClientInfo(parent context.Context, r *http.Request) (ctx context.Context) {
+// addRequestInfo adds request info to the context.
+func addRequestInfo(parent context.Context, r *http.Request) (ctx context.Context) {
ctx = parent
- ci := ClientInfo{
- URL: netutil.CloneURL(r.URL),
+ ri := &RequestInfo{
+ StartTime: time.Now(),
+ URL: netutil.CloneURL(r.URL),
}
if r.TLS != nil {
- ci.TLSServerName = strings.ToLower(r.TLS.ServerName)
+ ri.TLSServerName = strings.ToLower(r.TLS.ServerName)
}
- return ContextWithClientInfo(ctx, ci)
+ if username, pass, ok := r.BasicAuth(); ok {
+ ri.Userinfo = url.UserPassword(username, pass)
+ }
+
+ return ContextWithRequestInfo(ctx, ri)
}
// httpRequestToMsg reads the DNS message from http.Request.
diff --git a/internal/dnsserver/serverhttps_test.go b/internal/dnsserver/serverhttps_test.go
index ad32426..f258a40 100644
--- a/internal/dnsserver/serverhttps_test.go
+++ b/internal/dnsserver/serverhttps_test.go
@@ -357,7 +357,7 @@ func TestServerHTTPS_0RTT(t *testing.T) {
return srv.Shutdown(context.Background())
})
- quicTracer := &dnsservertest.QUICTracer{}
+ quicTracer := dnsservertest.NewQUICTracer()
// quicConfig with TokenStore set so that 0-RTT was enabled.
quicConfig := &quic.Config{
@@ -398,7 +398,7 @@ func testDoH3Exchange(
req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
req.RecursionDesired = true
- httpReq, err := createDoHRequest("https", http.MethodGet, req)
+ httpReq, err := newDoHRequest(http.MethodGet, req, true)
require.NoError(t, err)
// Send the request and check the response.
@@ -429,7 +429,7 @@ func mustDoHReq(
) (resp *dns.Msg) {
t.Helper()
- client, err := createDoHClient(httpsAddr, tlsConfig)
+ client, err := newDoHClient(httpsAddr, tlsConfig)
require.NoError(t, err)
proto := "https"
@@ -441,7 +441,7 @@ func mustDoHReq(
if json {
httpReq, err = createJSONRequest(proto, method, requestWireformat, req)
} else {
- httpReq, err = createDoHRequest(proto, method, req)
+ httpReq, err = newDoHRequest(method, req, tlsConfig != nil)
}
require.NoError(t, err)
@@ -467,7 +467,8 @@ func mustDoHReq(
return resp
}
-func createDoHClient(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) {
+// newDoHClient is a helper that creates a DoH client for a test or a benchmark.
+func newDoHClient(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) {
if dnsserver.NetworkFromAddr(httpsAddr) == dnsserver.NetworkUDP {
return createDoH3Client(httpsAddr, tlsConfig, nil)
}
@@ -534,7 +535,9 @@ func createDoH3Client(
}, nil
}
-func createDoHRequest(proto, method string, msg *dns.Msg) (r *http.Request, err error) {
+// newDoHRequest is a helper that creates a DoH request for a test or a
+// benchmark.
+func newDoHRequest(method string, msg *dns.Msg, isSecure bool) (r *http.Request, err error) {
// Prepare message
var buf []byte
buf, err = msg.Pack()
@@ -542,16 +545,27 @@ func createDoHRequest(proto, method string, msg *dns.Msg) (r *http.Request, err
return nil, err
}
- // Prepare the *http.Request with the DNS message.
- requestURL := proto + "://test.local" + dnsserver.PathDoH
- if method == http.MethodPost {
- bb := bytes.NewBuffer(buf)
- r, err = http.NewRequest(method, requestURL, bb)
- } else {
- requestURL = requestURL + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
- r, err = http.NewRequest(method, requestURL, nil)
+ proto := "https"
+ if !isSecure {
+ proto = "http"
}
+ // Prepare the *http.Request with the DNS message.
+ requestURL := &url.URL{
+ Scheme: proto,
+ Host: "test.local",
+ Path: dnsserver.PathDoH,
+ }
+
+ if method == http.MethodPost {
+ r, err = http.NewRequest(method, requestURL.String(), bytes.NewBuffer(buf))
+ } else {
+ requestURL.RawQuery = url.Values{
+ "dns": []string{base64.RawURLEncoding.EncodeToString(buf)},
+ }.Encode()
+
+ r, err = http.NewRequest(method, requestURL.String(), nil)
+ }
if err != nil {
return nil, err
}
diff --git a/internal/dnsserver/serverquic.go b/internal/dnsserver/serverquic.go
index ad0fb93..7e0d744 100644
--- a/internal/dnsserver/serverquic.go
+++ b/internal/dnsserver/serverquic.go
@@ -6,7 +6,6 @@ import (
"encoding/binary"
"fmt"
"io"
- "math"
"net"
"strings"
"sync"
@@ -15,6 +14,7 @@ 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/bluele/gcache"
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
@@ -27,21 +27,25 @@ const (
// token "doq" in the crypto handshake.
nextProtoDoQ = "doq"
- // maxQUICIdleTimeout is the maximum QUIC idle timeout. The default
- // value in quic-go is 30, but our internal tests show that a higher
- // value works better for clients written with ngtcp2.
+ // maxQUICIdleTimeout is the maximum QUIC idle timeout. The default value
+ // in quic-go is 30, but our internal tests show that a higher value works
+ // better for clients written with ngtcp2.
maxQUICIdleTimeout = 5 * time.Minute
- // quicAddrValidatorCacheSize is the size of the cache that we use in the QUIC
- // address validator. The value is chosen arbitrarily and we should consider
- // making it configurable.
+ // quicDefaultMaxStreamsPerPeer is the default maximum number of QUIC
+ // concurrent streams that a peer is allowed to open.
+ quicDefaultMaxStreamsPerPeer = 100
+
+ // quicAddrValidatorCacheSize is the size of the cache that we use in the
+ // QUIC address validator. The value is chosen arbitrarily and we should
+ // consider making it configurable.
//
// TODO(ameshkov): make it configurable after we analyze stats.
quicAddrValidatorCacheSize = 10000
- // quicAddrValidatorCacheTTL is time-to-live for cache items in the QUIC address
- // validator. The value is chosen arbitrarily and we should consider making it
- // configurable.
+ // quicAddrValidatorCacheTTL is time-to-live for cache items in the QUIC
+ // address validator. The value is chosen arbitrarily and we should
+ // consider making it configurable.
//
// TODO(ameshkov): make it configurable after we analyze stats.
quicAddrValidatorCacheTTL = 30 * time.Minute
@@ -66,6 +70,13 @@ type ConfigQUIC struct {
// TLSConfig is the TLS configuration for QUIC.
TLSConfig *tls.Config
+
+ // MaxStreamsPerPeer is the maximum number of concurrent streams that a peer
+ // is allowed to open.
+ MaxStreamsPerPeer int
+
+ // QUICLimitsEnabled, if true, enables QUIC limiting.
+ QUICLimitsEnabled bool
}
// ServerQUIC is a DNS-over-QUIC server implementation.
@@ -80,16 +91,19 @@ type ServerQUIC struct {
// to reuse already existing goroutines.
pool *ants.Pool
+ // reqPool is a pool to avoid unnecessary allocations when reading
+ // DNS packets.
+ reqPool *syncutil.Pool[[]byte]
+
+ // respPool is a pool for response buffers.
+ respPool *syncutil.Pool[[]byte]
+
// quicListener is a listener that we use to accept DoQ connections.
quicListener *quic.Listener
-
- // bytesPool is a pool to avoid unnecessary allocations when reading
- // DNS packets.
- bytesPool sync.Pool
}
-// type check
-var _ Server = (*ServerQUIC)(nil)
+// 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) {
@@ -108,11 +122,16 @@ func NewServerQUIC(conf ConfigQUIC) (s *ServerQUIC) {
ServerBase: newServerBase(ProtoDoQ, conf.ConfigBase),
conf: conf,
pool: newPoolNonblocking(),
+ reqPool: syncutil.NewSlicePool[byte](quicBytePoolSize),
+ respPool: syncutil.NewSlicePool[byte](quicBytePoolSize),
}
return s
}
+// type check
+var _ Server = (*ServerQUIC)(nil)
+
// Start implements the dnsserver.Server interface for *ServerQUIC.
func (s *ServerQUIC) Start(ctx context.Context) (err error) {
defer func() { err = errors.Annotate(err, "starting doq server: %w") }()
@@ -127,19 +146,15 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) {
if s.started {
return ErrServerAlreadyStarted
}
- s.started = true
log.Info("[%s]: Starting the server", s.name)
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
})
- // Prepare the bytes pool.
- s.bytesPool.New = makePacketBuffer(dns.MaxMsgSize)
-
// Start the QUIC listener.
err = s.listenQUIC(ctx)
if err != nil {
@@ -150,6 +165,8 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) {
s.wg.Add(1)
go s.startServeQUIC(ctx)
+ s.started = true
+
log.Info("[%s]: Server has been started", s.Name())
return nil
@@ -221,51 +238,59 @@ func (s *ServerQUIC) startServeQUIC(ctx context.Context) {
// serveQUIC listens for incoming QUIC connections.
func (s *ServerQUIC) serveQUIC(ctx context.Context, l *quic.Listener) (err error) {
- connWg := &sync.WaitGroup{}
+ wg := &sync.WaitGroup{}
// Wait until all conns are processed before exiting this method
- defer connWg.Wait()
+ defer wg.Wait()
for s.isStarted() {
- var conn quic.Connection
- conn, err = acceptQUICConn(ctx, l)
+ err = s.acceptQUICConn(ctx, l, wg)
if err != nil {
if !s.isStarted() {
return nil
}
- if isNonCriticalNetError(err) {
- // Non-critical errors, do not register in the metrics or log
- // anywhere.
- continue
- }
-
return err
}
- connWg.Add(1)
-
- err = s.pool.Submit(func() {
- s.serveQUICConnAsync(ctx, conn, connWg)
- })
- 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)
-
- return err
- }
}
return nil
}
-// acceptQUICConn is a wrapper around quic.Listener.Accept that makes sure that the
-// timeout is handled properly.
-func acceptQUICConn(ctx context.Context, l *quic.Listener) (conn quic.Connection, err error) {
- ctx, cancel := context.WithDeadline(ctx, time.Now().Add(DefaultReadTimeout))
+// acceptQUICConn reads and starts processing a single QUIC connection.
+func (s *ServerQUIC) acceptQUICConn(
+ ctx context.Context,
+ l *quic.Listener,
+ wg *sync.WaitGroup,
+) (err error) {
+ acceptCtx, cancel := context.WithDeadline(ctx, time.Now().Add(DefaultReadTimeout))
defer cancel()
- return l.Accept(ctx)
+ conn, err := l.Accept(acceptCtx)
+ if err != nil {
+ if isNonCriticalNetError(err) {
+ // Non-critical errors, do not register in the metrics or log
+ // anywhere.
+ return nil
+ }
+
+ return err
+ }
+
+ wg.Add(1)
+
+ err = s.pool.Submit(func() {
+ s.serveQUICConnAsync(ctx, conn, wg)
+ })
+ 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)
+
+ return err
+ }
+
+ return nil
}
// serveQUICConnAsync wraps serveQUICConn call and handles all possible errors
@@ -315,14 +340,17 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn quic.Connection) (e
streamWg.Add(1)
- reqCtx := s.requestContext()
-
- ci := ClientInfo{
+ ri := &RequestInfo{
+ StartTime: time.Now(),
TLSServerName: strings.ToLower(conn.ConnectionState().TLS.ServerName),
}
- reqCtx = ContextWithClientInfo(reqCtx, ci)
+
+ reqCtx, reqCancel := s.requestContext()
+ reqCtx = ContextWithRequestInfo(reqCtx, ri)
err = s.pool.Submit(func() {
+ defer reqCancel()
+
s.serveQUICStreamAsync(reqCtx, stream, conn, streamWg)
})
if err != nil {
@@ -401,26 +429,32 @@ func (s *ServerQUIC) serveQUICStream(
}
// Normalize before writing the response. Note that for QUIC we can
- // normalize as if it was tcp.
- normalize(NetworkTCP, ProtoDoQ, msg, resp)
+ // normalize as if it was TCP.
+ normalizeTCP(ProtoDoQ, msg, resp)
+
+ bufPtr := s.respPool.Get()
+ defer s.respPool.Put(bufPtr)
// Depending on the DoQ version we either write a 2-bytes prefixed message
// or just write the message (for old draft versions).
- var buf []byte
+ var b []byte
if doqDraft {
// TODO(ameshkov): remove draft support in the late 2023.
- buf, err = resp.Pack()
+ b, err = resp.PackBuffer(*bufPtr)
} else {
- buf, err = packWithPrefix(resp)
+ b, err = packWithPrefix(resp, *bufPtr)
}
-
if err != nil {
closeQUICConn(conn, DOQCodeProtocolError)
return err
}
- _, err = stream.Write(buf)
+ *bufPtr = b
+
+ _, err = stream.Write(b)
+
+ s.disposer.Dispose(resp)
return err
}
@@ -431,8 +465,11 @@ func (s *ServerQUIC) readQUICMsg(
ctx context.Context,
stream quic.Stream,
) (m *dns.Msg, doqDraft bool, err error) {
- buf := s.getBuffer()
- defer s.putBuffer(buf)
+ bufPtr := s.reqPool.Get()
+ defer s.reqPool.Put(bufPtr)
+
+ buf := *bufPtr
+ buf = buf[:quicBytePoolSize]
// One query - one stream.
// The client MUST send the DNS query over the selected stream, and MUST
@@ -441,8 +478,7 @@ func (s *ServerQUIC) readQUICMsg(
_ = stream.SetReadDeadline(time.Now().Add(DefaultReadTimeout))
// Read the stream data until io.EOF, i.e. until FIN is received.
- var n int
- n, err = readAll(stream, buf)
+ n, err := readAll(stream, buf)
// err is not checked here because STREAM FIN sent by the client is
// indicated as an error here. instead, we should check the number of bytes
@@ -514,7 +550,7 @@ func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) {
return err
}
- qConf := newServerQUICConfig(s.metrics)
+ qConf := newServerQUICConfig(s.metrics, s.conf.QUICLimitsEnabled, s.conf.MaxStreamsPerPeer)
// Do not change to quic.ListenEarly, see quicNotEarlyListener to know why.
ql, err := quic.Listen(conn, s.conf.TLSConfig, qConf)
@@ -528,22 +564,6 @@ func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) {
return nil
}
-// getBuffer gets a buffer to use for reading DNS messages.
-func (s *ServerQUIC) getBuffer() (buff []byte) {
- return *s.bytesPool.Get().(*[]byte)
-}
-
-// putBuffer puts the buffer back to the pool.
-func (s *ServerQUIC) putBuffer(m []byte) {
- if len(m) != dns.MaxMsgSize {
- // Means a new slice was created
- // We should create a new slice with the proper size before
- // putting it back to pool
- m = m[:dns.MaxMsgSize]
- }
- s.bytesPool.Put(&m)
-}
-
// isExpectedQUICErr checks if this error signals about closing QUIC connection,
// stream, or server and if it's expected and does not require any recovery or
// additional processing.
@@ -652,13 +672,24 @@ func closeQUICConn(conn quic.Connection, code quic.ApplicationErrorCode) {
// newServerQUICConfig creates *quic.Config populated with the default settings.
// This function is supposed to be used for both DoQ and DoH3 server.
-func newServerQUICConfig(metrics MetricsListener) (conf *quic.Config) {
- v := newQUICAddrValidator(quicAddrValidatorCacheSize, quicAddrValidatorCacheTTL, metrics)
+func newServerQUICConfig(
+ metrics MetricsListener,
+ quicLimitsEnabled bool,
+ maxStreamsPerPeer int,
+) (conf *quic.Config) {
+ v := newQUICAddrValidator(quicAddrValidatorCacheSize, metrics, quicAddrValidatorCacheTTL)
+
+ maxIncStreams := quicDefaultMaxStreamsPerPeer
+ maxIncUniStreams := quicDefaultMaxStreamsPerPeer
+ if quicLimitsEnabled {
+ maxIncStreams = maxStreamsPerPeer
+ maxIncUniStreams = maxStreamsPerPeer
+ }
return &quic.Config{
MaxIdleTimeout: maxQUICIdleTimeout,
- MaxIncomingStreams: math.MaxUint16,
- MaxIncomingUniStreams: math.MaxUint16,
+ MaxIncomingStreams: int64(maxIncStreams),
+ MaxIncomingUniStreams: int64(maxIncUniStreams),
RequireAddressValidation: v.requiresValidation,
// Enable 0-RTT by default for all addresses, it's beneficial for the
// performance.
@@ -670,20 +701,20 @@ func newServerQUICConfig(metrics MetricsListener) (conf *quic.Config) {
// addresses for which we do not require address validation.
type quicAddrValidator struct {
cache gcache.Cache
- ttl time.Duration
metrics MetricsListener
+ ttl time.Duration
}
// newQUICAddrValidator initializes a new instance of *quicAddrValidator.
func newQUICAddrValidator(
cacheSize int,
- ttl time.Duration,
metrics MetricsListener,
+ ttl time.Duration,
) (v *quicAddrValidator) {
return &quicAddrValidator{
cache: gcache.New(cacheSize).LRU().Build(),
- ttl: ttl,
metrics: metrics,
+ ttl: ttl,
}
}
diff --git a/internal/dnsserver/serverquic_test.go b/internal/dnsserver/serverquic_test.go
index 5415c4a..10e9879 100644
--- a/internal/dnsserver/serverquic_test.go
+++ b/internal/dnsserver/serverquic_test.go
@@ -56,8 +56,7 @@ func TestServerQUIC_integration_query(t *testing.T) {
go func() {
defer wg.Done()
- resp, qerr := sendQUICMessage(conn, req, doqDraft)
- assert.NoError(t, qerr)
+ resp := sendQUICMessage(t, conn, req, doqDraft)
assert.NotNil(t, resp)
assert.True(t, resp.Response)
@@ -93,8 +92,7 @@ func TestServerQUIC_integration_ENDS0Padding(t *testing.T) {
req := dnsservertest.CreateMessage("example.org.", dns.TypeA)
req.Extra = []dns.RR{dnsservertest.NewEDNS0Padding(req.Len(), dns.DefaultMsgSize)}
- resp, qerr := sendQUICMessage(conn, req, false)
- require.NoError(t, qerr)
+ resp := sendQUICMessage(t, conn, req, false)
require.NotNil(t, resp)
require.Equal(t, dns.RcodeSuccess, resp.Rcode)
require.True(t, resp.Response)
@@ -117,7 +115,7 @@ func TestServerQUIC_integration_0RTT(t *testing.T) {
return srv.Shutdown(context.Background())
})
- quicTracer := &dnsservertest.QUICTracer{}
+ quicTracer := dnsservertest.NewQUICTracer()
// quicConfig with TokenStore set so that 0-RTT was enabled.
quicConfig := &quic.Config{
@@ -176,8 +174,7 @@ func TestServerQUIC_integration_largeQuery(t *testing.T) {
},
}
- resp, err := sendQUICMessage(conn, req, false)
- require.NoError(t, err)
+ resp := sendQUICMessage(t, conn, req, false)
require.NotNil(t, resp)
require.True(t, resp.Response)
}
@@ -205,25 +202,26 @@ func testQUICExchange(
req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
req.RecursionDesired = true
- resp, err := sendQUICMessage(conn, req, false)
- require.NoError(t, err)
+ resp := sendQUICMessage(t, conn, req, false)
require.NotNil(t, resp)
}
-// sendQUICMessage sends a test QUIC message.
-func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Msg, error) {
- // Open stream.
+// sendQUICMessage is a test helper that sends a test QUIC message.
+func sendQUICMessage(
+ t testing.TB,
+ conn quic.Connection,
+ req *dns.Msg,
+ doqDraft bool,
+) (resp *dns.Msg) {
+ t.Helper()
+
stream, err := conn.OpenStreamSync(context.Background())
- if err != nil {
- return nil, err
- }
+ require.NoError(t, err)
+
defer log.OnCloserError(stream, log.DEBUG)
- // Prepare a message to be written.
data, err := req.Pack()
- if err != nil {
- return nil, err
- }
+ require.NoError(t, err)
var buf []byte
if doqDraft {
@@ -235,26 +233,22 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
}
err = writeQUICStream(buf, stream)
- if err != nil {
- return nil, err
- }
+ require.NoError(t, err)
// Closes the write-direction of the stream and sends a STREAM FIN packet.
// A DoQ client MUST send a FIN packet to indicate that the query is
// finished.
- _ = stream.Close()
+ err = stream.Close()
+ require.NoError(t, err)
// Now read the response.
respBytes := make([]byte, dns.MaxMsgSize)
n, err := stream.Read(respBytes)
- if err != nil && !errors.Is(err, io.EOF) {
- // Ignore EOF, this is just server sending FIN alongside the data
- return nil, err
+ if !errors.Is(err, io.EOF) {
+ require.NoError(t, err)
}
- if n < dnsserver.DNSHeaderSize {
- return nil, dns.ErrShortRead
- }
+ require.GreaterOrEqual(t, n, dnsserver.DNSHeaderSize)
// Unpack the response.
reply := &dns.Msg{}
@@ -263,11 +257,9 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
} else {
err = reply.Unpack(respBytes[2:n])
}
- if err != nil {
- return nil, err
- }
+ require.NoError(t, err)
- return reply, nil
+ return reply
}
// writeQUICStream writes buf to the specified QUIC stream in chunks. This way
diff --git a/internal/dnsserver/servertls.go b/internal/dnsserver/servertls.go
index 2659f5a..df433cb 100644
--- a/internal/dnsserver/servertls.go
+++ b/internal/dnsserver/servertls.go
@@ -50,16 +50,13 @@ func (s *ServerTLS) Start(ctx context.Context) (err error) {
return errors.Error("tls config is required")
}
- // TODO(ameshkov): Consider only setting s.started to true once the
- // listeners are up.
if s.started {
return ErrServerAlreadyStarted
}
- s.started = true
log.Info("[%s]: Starting the server", s.name)
- ctx = ContextWithServerInfo(ctx, ServerInfo{
+ ctx = ContextWithServerInfo(ctx, &ServerInfo{
Name: s.name,
Addr: s.addr,
Proto: s.proto,
@@ -76,6 +73,10 @@ func (s *ServerTLS) Start(ctx context.Context) (err error) {
go s.startServeTCP(ctx)
}
+ // TODO(ameshkov): Consider only setting s.started to true once the
+ // listeners are up.
+ s.started = true
+
log.Info("[%s]: Server has been started", s.Name())
return nil
diff --git a/internal/dnsserver/ttl.go b/internal/dnsserver/ttl.go
index 6680fc3..96ed6b3 100644
--- a/internal/dnsserver/ttl.go
+++ b/internal/dnsserver/ttl.go
@@ -39,26 +39,22 @@ func isEmptyMessage(m *dns.Msg) (empty bool) {
// minimalTTLMsgRRs gets minimal TTL from all message RRs.
func minimalTTLMsgRRs(m *dns.Msg) (d time.Duration) {
- minTTL := maximumDefaultTTL
+ minTTL32 := uint32(maximumDefaultTTL.Seconds())
+
for _, r := range m.Answer {
- if r.Header().Ttl < uint32(minTTL.Seconds()) {
- minTTL = time.Duration(r.Header().Ttl) * time.Second
- }
+ minTTL32 = min(minTTL32, r.Header().Ttl)
}
+
for _, r := range m.Ns {
- if r.Header().Ttl < uint32(minTTL.Seconds()) {
- minTTL = time.Duration(r.Header().Ttl) * time.Second
- }
+ minTTL32 = min(minTTL32, r.Header().Ttl)
}
for _, r := range m.Extra {
- if r.Header().Rrtype == dns.TypeOPT {
- // OPT records use TTL field for extended rcode and flags
- continue
- }
- if r.Header().Ttl < uint32(minTTL.Seconds()) {
- minTTL = time.Duration(r.Header().Ttl) * time.Second
+ // OPT records use TTL field for extended rcode and flags.
+ if h := r.Header(); h.Rrtype != dns.TypeOPT {
+ minTTL32 = min(minTTL32, h.Ttl)
}
}
- return minTTL
+
+ return time.Duration(minTTL32) * time.Second
}
diff --git a/internal/dnssvc/dnssvc.go b/internal/dnssvc/dnssvc.go
index 6768d06..1e97bdc 100644
--- a/internal/dnssvc/dnssvc.go
+++ b/internal/dnssvc/dnssvc.go
@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
@@ -23,6 +24,10 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/accessmw"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/mainmw"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/preservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/preupstream"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
@@ -32,18 +37,16 @@ import (
"github.com/miekg/dns"
)
-// DNS Service Definition
-//
-// Note that the definition of a “server” differs between AdGuard DNS and the
-// dnsserver module. In the latter, a server is a listener bound to a single
-// address, while in AGDNS, it's a collection of these listeners.
-
// Config is the configuration of the AdGuard DNS service.
type Config struct {
// Messages is the message constructor used to create blocked and other
// messages for this DNS service.
Messages *dnsmsg.Constructor
+ // Cloner is used to clone messages more efficiently by disposing of parts
+ // of DNS responses for later reuse.
+ Cloner *dnsmsg.Cloner
+
// ControlConf is the configuration of socket options.
ControlConf *netext.ControlConfig
@@ -75,7 +78,7 @@ type Config struct {
// ErrColl is the error collector that is used to collect critical and
// non-critical errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// FilterStorage is the storage of all filters.
FilterStorage filter.Storage
@@ -115,6 +118,10 @@ type Config struct {
// ServerGroups are the DNS server groups. Each element must be non-nil.
ServerGroups []*agd.ServerGroup
+ // HandleTimeout defines the timeout for the entire handling of a single
+ // query.
+ HandleTimeout time.Duration
+
// CacheSize is the size of the DNS cache for domain names that don't
// support ECS.
//
@@ -165,30 +172,22 @@ func New(c *Config) (svc *Service, err error) {
// Configure the pre-upstream middleware common for all servers of all
// groups.
- preUps := &preUpstreamMw{
- db: c.DNSDB,
- geoIP: c.GeoIP,
- cacheSize: c.CacheSize,
- ecsCacheSize: c.ECSCacheSize,
- useECSCache: c.UseECSCache,
- cacheMinTTL: c.CacheMinTTL,
- useCacheTTLOverride: c.UseCacheTTLOverride,
- }
+ preUps := preupstream.New(&preupstream.Config{
+ Cloner: c.Cloner,
+ DB: c.DNSDB,
+ GeoIP: c.GeoIP,
+ CacheSize: c.CacheSize,
+ ECSCacheSize: c.ECSCacheSize,
+ UseECSCache: c.UseECSCache,
+ CacheMinTTL: c.CacheMinTTL,
+ UseCacheTTLOverride: c.UseCacheTTLOverride,
+ })
handler = preUps.Wrap(handler)
// Configure the service itself.
groups := make([]*serverGroup, len(c.ServerGroups))
svc = &Service{
- messages: c.Messages,
- billStat: c.BillStat,
- errColl: c.ErrColl,
- fltStrg: c.FilterStorage,
- geoIP: c.GeoIP,
- queryLog: c.QueryLog,
- ruleStat: c.RuleStat,
- groups: groups,
- researchMetrics: c.ResearchMetrics,
- researchLog: c.ResearchLogs,
+ groups: groups,
}
for i, srvGrp := range c.ServerGroups {
@@ -196,17 +195,26 @@ func New(c *Config) (svc *Service, err error) {
//
// These are middlewares common to all filtering and server groups.
// They change the flow of request handling, so they are separated.
- //
- // TODO(a.garipov): Merge with some other middlewares.
dnsHdlr := dnsserver.WithMiddlewares(
handler,
- &preServiceMw{
- messages: c.Messages,
- hashMatcher: c.SafeBrowsing,
- checker: c.DNSCheck,
- },
- svc,
+ preservice.New(&preservice.Config{
+ Messages: c.Messages,
+ HashMatcher: c.SafeBrowsing,
+ Checker: c.DNSCheck,
+ }),
+ mainmw.New(&mainmw.Config{
+ Messages: c.Messages,
+ Cloner: c.Cloner,
+ BillStat: c.BillStat,
+ ErrColl: c.ErrColl,
+ FilterStorage: c.FilterStorage,
+ GeoIP: c.GeoIP,
+ QueryLog: c.QueryLog,
+ RuleStat: c.RuleStat,
+ ResearchMetrics: c.ResearchMetrics,
+ ResearchLogs: c.ResearchLogs,
+ }),
)
var servers []*server
@@ -225,6 +233,10 @@ func New(c *Config) (svc *Service, err error) {
}
// server is a group of listeners.
+//
+// Note that the definition of a “server” differs between AdGuard DNS and the
+// dnsserver module. In the latter, a server is a listener bound to a single
+// address, while in AGDNS, it's a collection of these listeners.
type server struct {
name agd.ServerName
handler dnsserver.Handler
@@ -237,28 +249,9 @@ type serverGroup struct {
servers []*server
}
-// type check
-var _ agd.Service = (*Service)(nil)
-
// Service is the main DNS service of AdGuard DNS.
type Service struct {
- messages *dnsmsg.Constructor
- billStat billstat.Recorder
- errColl agd.ErrorCollector
- fltStrg filter.Storage
- geoIP geoip.Interface
- queryLog querylog.Interface
- ruleStat rulestat.Interface
- groups []*serverGroup
-
- // researchMetrics enables reporting metrics that may be needed for research
- // purposes.
- researchMetrics bool
-
- // researchLog enables logging of additional information that may be needed
- // for research purposes. It will only be used when researchMetrics is set
- // to true.
- researchLog bool
+ groups []*serverGroup
}
// mustStartListener starts l and panics on any error.
@@ -273,9 +266,12 @@ func mustStartListener(
}
}
-// Start implements the agd.Service interface for *Service. It panics if one of
-// the listeners could not start.
-func (svc *Service) Start() (err error) {
+// type check
+var _ agdservice.Interface = (*Service)(nil)
+
+// Start implements the [agdservice.Interface] interface for *Service. It
+// panics if one of the listeners could not start.
+func (svc *Service) Start(_ context.Context) (err error) {
for _, g := range svc.groups {
for _, s := range g.servers {
for _, l := range s.listeners {
@@ -302,7 +298,7 @@ func shutdownListeners(ctx context.Context, listeners []*listener) (err error) {
return nil
}
-// Shutdown implements the agd.Service interface for *Service.
+// Shutdown implements the [agdservice.Interface] interface for *Service.
func (svc *Service) Shutdown(ctx context.Context) (err error) {
var errs []error
for _, g := range svc.groups {
@@ -323,6 +319,8 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
}
// Handle is a simple helper to test the handling of DNS requests.
+//
+// TODO(a.garipov): Remove once the mainmw refactoring is complete.
func (svc *Service) Handle(
ctx context.Context,
grpName agd.ServerGroupName,
@@ -366,12 +364,8 @@ type Listener = dnsserver.Server
// NewListenerFunc is the type for DNS listener constructors.
type NewListenerFunc func(
s *agd.Server,
- name string,
- addr string,
- h dnsserver.Handler,
+ baseConf dnsserver.ConfigBase,
nonDNS http.Handler,
- errColl agd.ErrorCollector,
- lc netext.ListenConfig,
) (l Listener, err error)
// listener is a Listener along with some of its associated data.
@@ -388,57 +382,61 @@ func listenerName(srvName agd.ServerName, addr string, proto agd.Protocol) (name
// NewListener returns a new Listener. It is the default DNS listener
// constructor.
+//
+// TODO(a.garipov): Replace this in tests with [netext.ListenConfig].
func NewListener(
s *agd.Server,
- name string,
- addr string,
- h dnsserver.Handler,
+ baseConf dnsserver.ConfigBase,
nonDNS http.Handler,
- errColl agd.ErrorCollector,
- lc netext.ListenConfig,
) (l Listener, err error) {
- defer func() { err = errors.Annotate(err, "listener %q: %w", name) }()
-
- dcConf := s.DNSCrypt
-
- metricsListener := &errCollMetricsListener{
- errColl: errColl,
- baseListener: prometheus.NewServerMetricsListener(),
- }
-
- confBase := dnsserver.ConfigBase{
- Name: name,
- Addr: addr,
- Network: dnsserver.NetworkAny,
- Handler: h,
- Metrics: metricsListener,
- BaseContext: ctxWithReqID,
- ListenConfig: lc,
- }
+ defer func() { err = errors.Annotate(err, "listener %q: %w", baseConf.Name) }()
+ tcpConf := s.TCPConf
+ quicConf := s.QUICConf
switch p := s.Protocol; p {
case agd.ProtoDNS:
- l = dnsserver.NewServerDNS(dnsserver.ConfigDNS{ConfigBase: confBase})
+ udpConf := s.UDPConf
+ l = dnsserver.NewServerDNS(dnsserver.ConfigDNS{
+ ConfigBase: baseConf,
+ ReadTimeout: s.ReadTimeout,
+ WriteTimeout: s.WriteTimeout,
+ MaxUDPRespSize: udpConf.MaxRespSize,
+ TCPIdleTimeout: tcpConf.IdleTimeout,
+ MaxPipelineCount: tcpConf.MaxPipelineCount,
+ MaxPipelineEnabled: tcpConf.MaxPipelineEnabled,
+ })
case agd.ProtoDNSCrypt:
+ dcConf := s.DNSCrypt
l = dnsserver.NewServerDNSCrypt(dnsserver.ConfigDNSCrypt{
- ConfigBase: confBase,
+ ConfigBase: baseConf,
DNSCryptProviderName: dcConf.ProviderName,
DNSCryptResolverCert: dcConf.Cert,
})
case agd.ProtoDoH:
l = dnsserver.NewServerHTTPS(dnsserver.ConfigHTTPS{
- ConfigBase: confBase,
- TLSConfig: s.TLS,
- NonDNSHandler: nonDNS,
+ ConfigBase: baseConf,
+ TLSConfig: s.TLS,
+ NonDNSHandler: nonDNS,
+ MaxStreamsPerPeer: quicConf.MaxStreamsPerPeer,
+ QUICLimitsEnabled: quicConf.QUICLimitsEnabled,
})
case agd.ProtoDoQ:
l = dnsserver.NewServerQUIC(dnsserver.ConfigQUIC{
- ConfigBase: confBase,
- TLSConfig: s.TLS,
+ ConfigBase: baseConf,
+ TLSConfig: s.TLS,
+ MaxStreamsPerPeer: quicConf.MaxStreamsPerPeer,
+ QUICLimitsEnabled: quicConf.QUICLimitsEnabled,
})
case agd.ProtoDoT:
l = dnsserver.NewServerTLS(dnsserver.ConfigTLS{
- ConfigDNS: dnsserver.ConfigDNS{ConfigBase: confBase},
+ ConfigDNS: dnsserver.ConfigDNS{
+ ConfigBase: baseConf,
+ ReadTimeout: s.ReadTimeout,
+ WriteTimeout: s.WriteTimeout,
+ MaxPipelineEnabled: tcpConf.MaxPipelineEnabled,
+ MaxPipelineCount: tcpConf.MaxPipelineCount,
+ TCPIdleTimeout: tcpConf.IdleTimeout,
+ },
TLSConfig: s.TLS,
})
default:
@@ -448,9 +446,31 @@ func NewListener(
return l, nil
}
-// ctxWithReqID returns a context with a new request ID added to it.
-func ctxWithReqID() (ctx context.Context) {
- return agd.WithRequestID(context.Background(), agd.NewRequestID())
+// contextConstructor is a [dnsserver.ContextConstructor] implementation that
+// that returns a context with the given timeout as well as a new
+// [agd.RequestID].
+type contextConstructor struct {
+ timeout time.Duration
+}
+
+// newContextConstructor returns a new properly initialized *contextConstructor.
+func newContextConstructor(timeout time.Duration) (c *contextConstructor) {
+ return &contextConstructor{
+ timeout: timeout,
+ }
+}
+
+// type check
+var _ dnsserver.ContextConstructor = (*contextConstructor)(nil)
+
+// New implements the [dnsserver.ContextConstructor] 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)
+ ctx = agd.WithRequestID(ctx, agd.NewRequestID())
+
+ return ctx, cancel
}
// newServers creates a slice of servers.
@@ -535,20 +555,40 @@ func newListeners(
handler dnsserver.Handler,
newListener NewListenerFunc,
) (listeners []*listener, err error) {
- listeners = make([]*listener, 0, len(srv.BindData))
- for i, bindData := range srv.BindData {
- addr := bindData.Address
- if addr == "" {
+ bindData := srv.BindData()
+ listeners = make([]*listener, 0, len(bindData))
+ for i, bindData := range bindData {
+ var addr string
+ if bindData.PrefixAddr == nil {
addr = bindData.AddrPort.String()
+ } else {
+ addr = bindData.PrefixAddr.String()
}
proto := srv.Protocol
- name := listenerName(srv.Name, addr, proto)
- lc := newListenConfig(bindData.ListenConfig, c.ControlConf, c.ConnLimiter, proto)
+ name := listenerName(srv.Name, addr, proto)
+ baseConf := dnsserver.ConfigBase{
+ Network: dnsserver.NetworkAny,
+ Handler: handler,
+ Metrics: &errCollMetricsListener{
+ errColl: c.ErrColl,
+ baseListener: prometheus.NewServerMetricsListener(),
+ },
+ Disposer: c.Cloner,
+ RequestContext: newContextConstructor(c.HandleTimeout),
+ ListenConfig: newListenConfig(
+ bindData.ListenConfig,
+ c.ControlConf,
+ c.ConnLimiter,
+ proto,
+ ),
+ Name: name,
+ Addr: addr,
+ }
var l Listener
- l, err = newListener(srv, name, addr, handler, c.NonDNS, c.ErrColl, lc)
+ l, err = newListener(srv, baseConf, c.NonDNS)
if err != nil {
return nil, fmt.Errorf("bind data at index %d: %w", i, err)
}
diff --git a/internal/dnssvc/dnssvc_test.go b/internal/dnssvc/dnssvc_test.go
index ada8233..4e651f3 100644
--- a/internal/dnssvc/dnssvc_test.go
+++ b/internal/dnssvc/dnssvc_test.go
@@ -2,7 +2,6 @@ package dnssvc_test
import (
"context"
- "crypto/tls"
"net"
"net/http"
"net/netip"
@@ -10,11 +9,13 @@ import (
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"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/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@@ -25,13 +26,14 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
-// type check
-var _ agd.Refresher = (*forward.Handler)(nil)
+const (
-// Test Mocks
+ // testSrvGrpName is the [agd.ServerGroupName] for tests.
+ testSrvGrpName agd.ServerGroupName = "test_group"
+)
// type check
-var _ dnssvc.Listener = (*testListener)(nil)
+var _ agdservice.Refresher = (*forward.Handler)(nil)
// testListener is a [dnssvc.Listener] for tests.
type testListener struct {
@@ -45,6 +47,9 @@ type testListener struct {
onLocalUDPAddr func() (addr net.Addr)
}
+// type check
+var _ dnssvc.Listener = (*testListener)(nil)
+
// Name implements the [dnsserver.Server] interface for *testListener.
func (l *testListener) Name() (name string) {
return l.onName()
@@ -105,12 +110,8 @@ func newTestListener() (tl *testListener) {
func newTestListenerFunc(tl *testListener) (f dnssvc.NewListenerFunc) {
return func(
_ *agd.Server,
- _ string,
- _ string,
- _ dnsserver.Handler,
+ _ dnsserver.ConfigBase,
_ http.Handler,
- _ agd.ErrorCollector,
- _ netext.ListenConfig,
) (l dnssvc.Listener, err error) {
return tl, nil
}
@@ -141,8 +142,6 @@ func (rw *testResponseWriter) WriteMsg(ctx context.Context, req, resp *dns.Msg)
return rw.onWriteMsg(ctx, req, resp)
}
-// Tests
-
func TestService_Start(t *testing.T) {
var numStart, numShutdown atomic.Uint64
@@ -158,13 +157,9 @@ func TestService_Start(t *testing.T) {
return nil
}
- srv := &agd.Server{
- Name: "test_server",
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
- }},
- Protocol: agd.ProtoDNS,
- }
+ srv := dnssvctest.NewServer(dnssvctest.ServerName, agd.ProtoDNS, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
+ })
c := &dnssvc.Config{
NewListener: newTestListenerFunc(tl),
@@ -179,60 +174,36 @@ func TestService_Start(t *testing.T) {
require.NoError(t, err)
require.NotPanics(t, func() {
- err = svc.Start()
+ err = svc.Start(agdtest.ContextWithTimeout(t, dnssvctest.Timeout))
assert.NoError(t, err)
assert.Equal(t, uint64(1), numStart.Load())
})
require.NotPanics(t, func() {
- err = svc.Shutdown(context.Background())
+ err = svc.Shutdown(agdtest.ContextWithTimeout(t, dnssvctest.Timeout))
assert.NoError(t, err)
assert.Equal(t, uint64(1), numShutdown.Load())
})
}
func TestNew(t *testing.T) {
- srvs := []*agd.Server{{
- DNSCrypt: nil,
- TLS: nil,
- Name: "test_server_dns",
- BindData: []*agd.ServerBindData{{
+ srvs := []*agd.Server{
+ dnssvctest.NewServer("test_server_dns", agd.ProtoDNS, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
- }},
- Protocol: agd.ProtoDNS,
- }, {
- DNSCrypt: &agd.DNSCryptConfig{},
- TLS: nil,
- Name: "test_server_dnscrypt_tcp",
- BindData: []*agd.ServerBindData{{
+ }),
+ dnssvctest.NewServer("test_server_dnscrypt_tcp", agd.ProtoDNSCrypt, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("127.0.0.1:8853"),
- }},
- Protocol: agd.ProtoDNSCrypt,
- }, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_doh",
- BindData: []*agd.ServerBindData{{
+ }),
+ dnssvctest.NewServer("test_server_doh", agd.ProtoDoH, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("127.0.0.1:443"),
- }},
- Protocol: agd.ProtoDoH,
- }, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_doq",
- BindData: []*agd.ServerBindData{{
+ }),
+ dnssvctest.NewServer("test_server_doq", agd.ProtoDoQ, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
- }},
- Protocol: agd.ProtoDoQ,
- }, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_dot",
- BindData: []*agd.ServerBindData{{
+ }),
+ dnssvctest.NewServer("test_server_dot", agd.ProtoDoT, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
- }},
- Protocol: agd.ProtoDoT,
- }}
+ }),
+ }
c := &dnssvc.Config{
Handler: dnsservertest.DefaultHandler(),
diff --git a/internal/dnssvc/errcoll.go b/internal/dnssvc/errcoll.go
index 25df6df..39de5f0 100644
--- a/internal/dnssvc/errcoll.go
+++ b/internal/dnssvc/errcoll.go
@@ -4,16 +4,15 @@ import (
"context"
"fmt"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/miekg/dns"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
)
// errCollMetricsListener extends the default prometheus.ServerMetricsListener
// and overrides OnPanic and OnError methods. The point is to collect errors
// from inside the dnsserver.Server in addition to collecting prom metrics.
type errCollMetricsListener struct {
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
baseListener dnsserver.MetricsListener
}
@@ -24,10 +23,10 @@ var _ dnsserver.MetricsListener = (*errCollMetricsListener)(nil)
// *errCollMetricsListener.
func (s *errCollMetricsListener) OnRequest(
ctx context.Context,
- req, resp *dns.Msg,
+ info *dnsserver.QueryInfo,
rw dnsserver.ResponseWriter,
) {
- s.baseListener.OnRequest(ctx, req, resp, rw)
+ s.baseListener.OnRequest(ctx, info, rw)
}
// OnInvalidMsg implements the dnsserver.MetricsListener interface for
diff --git a/internal/dnssvc/middleware_test.go b/internal/dnssvc/integration_test.go
similarity index 80%
rename from internal/dnssvc/middleware_test.go
rename to internal/dnssvc/integration_test.go
index ca51b57..db198dc 100644
--- a/internal/dnssvc/middleware_test.go
+++ b/internal/dnssvc/integration_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@@ -17,6 +18,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
@@ -25,34 +27,18 @@ import (
"github.com/stretchr/testify/require"
)
-const (
- // testFltListID is the [agd.FilterListID] for tests.
- testFltListID agd.FilterListID = "flt1234"
-
- // testSrvName is the [agd.ServerName] for tests.
- testSrvName agd.ServerName = "test_server_dns_tls"
-
- // testSrvGrpName is the [agd.ServerGroupName] for tests.
- testSrvGrpName agd.ServerGroupName = "test_group"
-
- // testDevIDWildcard is the wildcard domain for retrieving [agd.DeviceID] in
- // tests. Use [strings.ReplaceAll] to replace the "*" symbol with the
- // actual [agd.DeviceID].
- testDevIDWildcard string = "*.dns.example.com"
-)
-
// newTestService creates a new [dnssvc.Service] for tests. The service built
// of stubs, that use the following data:
//
-// - A filtering group containing a filter with [testFltListID] and enabled
-// rule lists.
+// - A filtering group containing a filter with [dnssvctest.FilterListID1] and
+// enabled rule lists.
// - A device with [dnssvctest.DeviceID] and enabled filtering.
// - A profile with [dnssvctest.ProfileID] with enabled filtering and query
// logging, containing the device.
// - GeoIP database always returning [agd.CountryAD], [agd.ContinentEU], and
// ASN of 42.
// - A server with [testSrvName] under group with [testSrvGrpName], matching
-// the DeviceID with [testDevIDWildcard].
+// the DeviceID with [dnssvctest.DeviceIDWildcard].
//
// Each stub also uses the corresponding channels to send the data it receives
// from the service. The channels must not be nil. Each sending to a channel
@@ -79,9 +65,10 @@ func newTestService(
}
prof := &agd.Profile{
+ Access: access.EmptyProfile{},
ID: dnssvctest.ProfileID,
DeviceIDs: []agd.DeviceID{dnssvctest.DeviceID},
- RuleListIDs: []agd.FilterListID{testFltListID},
+ RuleListIDs: []agd.FilterListID{dnssvctest.FilterListID1},
FilteredResponseTTL: agdtest.FilteredResponseTTL,
FilteringEnabled: true,
QueryLogEnabled: true,
@@ -127,20 +114,19 @@ func newTestService(
},
}
- loc := &agd.Location{
- Country: agd.CountryAD,
- Continent: agd.ContinentEU,
+ loc := &geoip.Location{
+ Country: geoip.CountryAD,
+ Continent: geoip.ContinentEU,
ASN: 42,
}
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- _ agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {
panic("not implemented")
},
- OnData: func(host string, _ netip.Addr) (l *agd.Location, err error) {
+ OnData: func(host string, _ netip.Addr) (l *geoip.Location, err error) {
testutil.RequireSend(pt, geoIPCh, host, dnssvctest.Timeout)
return loc, nil
@@ -163,15 +149,9 @@ func newTestService(
}
srvAddr = netip.MustParseAddrPort("94.149.14.14:853")
- srvs := []*agd.Server{{
- DNSCrypt: nil,
- TLS: nil,
- Name: testSrvName,
- BindData: []*agd.ServerBindData{{
- AddrPort: srvAddr,
- }},
- Protocol: agd.ProtoDoT,
- }}
+ srv := dnssvctest.NewServer(dnssvctest.ServerName, agd.ProtoDoT, &agd.ServerBindData{
+ AddrPort: srvAddr,
+ })
tl := newTestListener()
tl.onStart = func(_ context.Context) (err error) { return nil }
@@ -221,8 +201,8 @@ func newTestService(
OnRecord: func(
_ context.Context,
_ agd.DeviceID,
- _ agd.Country,
- _ agd.ASN,
+ _ geoip.Country,
+ _ geoip.ASN,
_ time.Time,
_ agd.Protocol,
) {
@@ -243,20 +223,20 @@ func newTestService(
FilteringGroups: map[agd.FilteringGroupID]*agd.FilteringGroup{
testFltGrpID: {
ID: testFltGrpID,
- RuleListIDs: []agd.FilterListID{testFltListID},
+ RuleListIDs: []agd.FilterListID{dnssvctest.FilterListID1},
RuleListsEnabled: true,
},
},
ServerGroups: []*agd.ServerGroup{{
TLS: &agd.TLS{
- DeviceIDWildcards: []string{testDevIDWildcard},
+ DeviceIDWildcards: []string{dnssvctest.DeviceIDWildcard},
},
DDR: &agd.DDR{
Enabled: true,
},
Name: testSrvGrpName,
FilteringGroup: testFltGrpID,
- Servers: srvs,
+ Servers: []*agd.Server{srv},
}},
}
@@ -264,10 +244,10 @@ func newTestService(
require.NoError(t, err)
require.NotNil(t, svc)
- err = svc.Start()
+ err = svc.Start(agdtest.ContextWithTimeout(t, dnssvctest.Timeout))
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
- return svc.Shutdown(context.Background())
+ return svc.Shutdown(agdtest.ContextWithTimeout(t, dnssvctest.Timeout))
})
return svc, srvAddr
@@ -287,20 +267,19 @@ func TestService_Wrap(t *testing.T) {
}
}()
- const domain = "example.org"
-
- domainFQDN := dns.Fqdn(domain)
-
reqType := dns.TypeA
- req := dnsservertest.CreateMessage(domain, reqType)
+ req := dnsservertest.CreateMessage(dnssvctest.DomainFQDN, reqType)
clientAddr := &net.TCPAddr{IP: net.IP{1, 2, 3, 4}, Port: 12345}
+ tlsServerName := strings.ReplaceAll(
+ dnssvctest.DeviceIDWildcard,
+ "*",
+ string(dnssvctest.DeviceID),
+ )
+
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
- TLSServerName: strings.ReplaceAll(testDevIDWildcard, "*", string(dnssvctest.DeviceID)),
- })
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{
+ ctx = dnsserver.ContextWithServerInfo(ctx, &dnsserver.ServerInfo{
Proto: agd.ProtoDoT,
})
@@ -312,7 +291,7 @@ func TestService_Wrap(t *testing.T) {
) (r filter.Result, err error) {
pt := testutil.PanicT{}
require.NotEmpty(pt, m.Question)
- require.Equal(pt, domainFQDN, m.Question[0].Name)
+ require.Equal(pt, dnssvctest.DomainFQDN, m.Question[0].Name)
return nil, nil
}
@@ -338,9 +317,12 @@ func TestService_Wrap(t *testing.T) {
clientAddr,
)
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ TLSServerName: tlsServerName,
+ })
- err := svc.Handle(ctx, testSrvGrpName, testSrvName, rw, req)
+ err := svc.Handle(ctx, testSrvGrpName, dnssvctest.ServerName, rw, req)
require.NoError(t, err)
resp := rw.Msg()
@@ -349,11 +331,11 @@ func TestService_Wrap(t *testing.T) {
assert.Equal(t, dnssvctest.DeviceID, <-profileDBCh)
logEntry := <-querylogCh
- assert.Equal(t, domainFQDN, logEntry.DomainFQDN)
+ assert.Equal(t, dnssvctest.DomainFQDN, logEntry.DomainFQDN)
assert.Equal(t, reqType, logEntry.RequestType)
assert.Equal(t, "", <-geoIPCh)
- assert.Equal(t, domain, <-geoIPCh)
+ assert.Equal(t, dnssvctest.Domain, <-geoIPCh)
dnsDBReqInfo := <-dnsDBCh
assert.NotNil(t, dnsDBReqInfo)
@@ -363,7 +345,7 @@ func TestService_Wrap(t *testing.T) {
t.Run("request_cname", func(t *testing.T) {
const (
cname = "cname.example.org"
- cnameRule agd.FilterRuleText = "||" + domain + "^$dnsrewrite=" + cname
+ cnameRule agd.FilterRuleText = "||" + dnssvctest.Domain + "^$dnsrewrite=" + cname
)
cnameFQDN := dns.Fqdn(cname)
@@ -380,7 +362,7 @@ func TestService_Wrap(t *testing.T) {
return &filter.ResultModified{
Msg: mod,
- List: testFltListID,
+ List: dnssvctest.FilterListID1,
Rule: cnameRule,
}, nil
},
@@ -409,9 +391,12 @@ func TestService_Wrap(t *testing.T) {
clientAddr,
)
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ TLSServerName: tlsServerName,
+ })
- err := svc.Handle(ctx, testSrvGrpName, testSrvName, rw, req)
+ err := svc.Handle(ctx, testSrvGrpName, dnssvctest.ServerName, rw, req)
require.NoError(t, err)
resp := rw.Msg()
@@ -420,7 +405,7 @@ func TestService_Wrap(t *testing.T) {
assert.Equal(t, []dns.RR{&dns.CNAME{
Hdr: dns.RR_Header{
- Name: domainFQDN,
+ Name: dnssvctest.DomainFQDN,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: uint32(agdtest.FilteredResponseTTL.Seconds()),
@@ -439,7 +424,7 @@ func TestService_Wrap(t *testing.T) {
assert.Equal(t, dnssvctest.DeviceID, <-profileDBCh)
logEntry := <-querylogCh
- assert.Equal(t, domainFQDN, logEntry.DomainFQDN)
+ assert.Equal(t, dnssvctest.DomainFQDN, logEntry.DomainFQDN)
assert.Equal(t, reqType, logEntry.RequestType)
assert.Equal(t, "", <-geoIPCh)
diff --git a/internal/dnssvc/internal/accessmw/access.go b/internal/dnssvc/internal/accessmw/access.go
index 54fe0d0..ce8ab4f 100644
--- a/internal/dnssvc/internal/accessmw/access.go
+++ b/internal/dnssvc/internal/accessmw/access.go
@@ -54,7 +54,8 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
// Assume that module dnsserver has already validated that the request
// always has exactly one question for us.
q := req.Question[0]
- if blocked := mw.accessManager.IsBlockedHost(normalizeDomain(q.Name), q.Qtype); blocked {
+ normalizedDomain := agdnet.NormalizeQueryDomain(q.Name)
+ if mw.accessManager.IsBlockedHost(normalizedDomain, q.Qtype) {
metrics.AccessBlockedForHostTotal.Inc()
return nil
@@ -65,16 +66,3 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
return dnsserver.HandlerFunc(f)
}
-
-// normalizeDomain returns a lowercased version of the host without the final
-// dot, unless the host is ".", in which case it returns the unchanged host.
-// That is the special case to allow matching queries like:
-//
-// dig IN NS '.'
-func normalizeDomain(host string) (norm string) {
- if host == "." {
- return host
- }
-
- return agdnet.NormalizeDomain(host)
-}
diff --git a/internal/dnssvc/internal/accessmw/access_test.go b/internal/dnssvc/internal/accessmw/access_test.go
index db1712c..39534d6 100644
--- a/internal/dnssvc/internal/accessmw/access_test.go
+++ b/internal/dnssvc/internal/accessmw/access_test.go
@@ -10,13 +10,18 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/accessmw"
+ "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
+
func TestMiddleware_Wrap(t *testing.T) {
- am, accessErr := access.New([]string{
+ am, accessErr := access.NewGlobal([]string{
"block.test",
"UPPERCASE.test",
"||block_aaaa.test^$dnstype=AAAA",
diff --git a/internal/dnssvc/internal/dnssvctest/dnssvctest.go b/internal/dnssvc/internal/dnssvctest/dnssvctest.go
index 1fba36e..dcb1f45 100644
--- a/internal/dnssvc/internal/dnssvctest/dnssvctest.go
+++ b/internal/dnssvc/internal/dnssvctest/dnssvctest.go
@@ -3,11 +3,12 @@
package dnssvctest
import (
+ "crypto/tls"
"net"
- "net/netip"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/miekg/dns"
)
// Timeout is the common timeout for tests.
@@ -25,6 +26,41 @@ const DeviceID agd.DeviceID = DeviceIDStr
// ProfileID is the common profile ID for tests.
const ProfileID agd.ProfileID = ProfileIDStr
+// String representations for the common filtering-rule list ID for tests.
+const (
+ FilterListID1Str = "flt_1"
+ FilterListID2Str = "flt_2"
+)
+
+// Common filtering-rule list ID for tests.
+const (
+ FilterListID1 agd.FilterListID = FilterListID1Str
+ FilterListID2 agd.FilterListID = FilterListID2Str
+)
+
+// Common domains and FQDNs for tests.
+const (
+ Domain = "test.example"
+ DomainAllowed = "allowed.example"
+ DomainBlocked = "blocked.example"
+ DomainRewritten = "rewritten.example"
+ DomainRewrittenCNAME = "rewritten-cname.example"
+
+ DomainFQDN = Domain + "."
+ DomainAllowedFQDN = DomainAllowed + "."
+ DomainBlockedFQDN = DomainBlocked + "."
+ DomainRewrittenFQDN = DomainRewritten + "."
+ DomainRewrittenCNAMEFQDN = DomainRewrittenCNAME + "."
+)
+
+// ServerName is the common server name for tests.
+const ServerName agd.ServerName = "test_server_dns_tls"
+
+// DeviceIDWildcard is the common wildcard domain for retrieving [agd.DeviceID]
+// in tests. Use [strings.ReplaceAll] to replace the "*" symbol with the actual
+// [agd.DeviceID].
+const DeviceIDWildcard = "*.dns.example.com"
+
// Common addresses for tests.
var (
ClientIP = net.IP{1, 2, 3, 4}
@@ -36,9 +72,49 @@ var (
ClientAddrPort = RemoteAddr.AddrPort()
ClientAddr = ClientAddrPort.Addr()
- ServerAddr = netip.MustParseAddr("5.6.7.8")
- LocalAddr = &net.TCPAddr{
- IP: ServerAddr.AsSlice(),
+ LocalAddr = &net.TCPAddr{
+ IP: net.IP{5, 6, 7, 8},
Port: 54321,
}
+
+ ServerAddrPort = LocalAddr.AddrPort()
+ ServerAddr = ServerAddrPort.Addr()
)
+
+// NewServer is a helper that returns a new *agd.Server for tests.
+func NewServer(
+ name agd.ServerName,
+ proto agd.Protocol,
+ bindData ...*agd.ServerBindData,
+) (srv *agd.Server) {
+ srv = &agd.Server{
+ Name: name,
+ Protocol: proto,
+ ReadTimeout: Timeout,
+ WriteTimeout: Timeout,
+ }
+
+ if proto.IsStdEncrypted() {
+ // #nosec G402 -- This is a test helper.
+ srv.TLS = &tls.Config{}
+ }
+
+ switch proto {
+ case agd.ProtoDoH, agd.ProtoDoQ:
+ srv.QUICConf = &agd.QUICConfig{}
+ case agd.ProtoDNS, agd.ProtoDoT:
+ srv.TCPConf = &agd.TCPConfig{
+ IdleTimeout: Timeout,
+ }
+
+ srv.UDPConf = &agd.UDPConfig{
+ MaxRespSize: dns.MaxMsgSize,
+ }
+ case agd.ProtoDNSCrypt:
+ srv.DNSCrypt = &agd.DNSCryptConfig{}
+ }
+
+ srv.SetBindData(bindData)
+
+ return srv
+}
diff --git a/internal/dnssvc/internal/initial/deviceid.go b/internal/dnssvc/internal/initial/deviceid.go
index 2e5f5f6..c4e4c0c 100644
--- a/internal/dnssvc/internal/initial/deviceid.go
+++ b/internal/dnssvc/internal/initial/deviceid.go
@@ -129,10 +129,10 @@ func deviceIDFromContext(
proto agd.Protocol,
wildcards []string,
) (id agd.DeviceID, err error) {
- ci := dnsserver.MustClientInfoFromContext(ctx)
+ ri := dnsserver.MustRequestInfoFromContext(ctx)
if proto == agd.ProtoDoH {
- id, err = deviceIDFromDoHURL(ci.URL)
+ id, err = deviceIDFromDoHURL(ri.URL)
if err != nil {
return "", &deviceIDError{
err: err,
@@ -151,7 +151,7 @@ func deviceIDFromContext(
return "", nil
}
- cliSrvName := ci.TLSServerName
+ cliSrvName := ri.TLSServerName
id, err = deviceIDFromClientServerName(cliSrvName, wildcards)
if err != nil {
return "", &deviceIDError{
diff --git a/internal/dnssvc/internal/initial/deviceid_internal_test.go b/internal/dnssvc/internal/initial/deviceid_internal_test.go
index 2dab12f..c26eb32 100644
--- a/internal/dnssvc/internal/initial/deviceid_internal_test.go
+++ b/internal/dnssvc/internal/initial/deviceid_internal_test.go
@@ -107,7 +107,7 @@ func TestDeviceIDFromContext(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
TLSServerName: tc.cliSrvName,
})
@@ -174,7 +174,7 @@ func TestDeviceIDFromContext_https(t *testing.T) {
}
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
URL: u,
})
@@ -192,13 +192,10 @@ func TestDeviceIDFromContext_https(t *testing.T) {
}
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
URL: u,
TLSServerName: u.Host,
})
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{
- Proto: proto,
- })
deviceID, err := deviceIDFromContext(ctx, proto, []string{"*.dns.example.com"})
require.NoError(t, err)
diff --git a/internal/dnssvc/internal/initial/initial.go b/internal/dnssvc/internal/initial/initial.go
index 9669a34..b7e75d4 100644
--- a/internal/dnssvc/internal/initial/initial.go
+++ b/internal/dnssvc/internal/initial/initial.go
@@ -1,7 +1,8 @@
-// Package initial contains the initial, outermost (except for ratelimit)
-// middleware of the AdGuard DNS server. It filters out the Firefox canary
-// domain logic, sets and resets the AD bit for further processing, as well as
-// puts as much information as it can into the context and request info.
+// Package initial contains the initial, outermost (except for ratelimit and
+// access) middleware of the AdGuard DNS server. It handles Firefox canary
+// hosts requests, applies profile access restrictions, sets and resets the AD
+// bit for further processing, as well as puts as much information as it can
+// into the context and request info.
package initial
import (
@@ -12,20 +13,23 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optlog"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
// Middleware is the initial middleware of the AdGuard DNS server. This
-// middleware must be the most outer middleware apart from the ratelimit one.
+// middleware must be the most outer middleware apart from the ratelimit and
+// global access middlewares.
type Middleware struct {
// messages is used to build the responses specific for the request's
// context.
@@ -41,7 +45,7 @@ type Middleware struct {
srv *agd.Server
// pool is the pool of [agd.RequestInfo] values.
- pool *agdsync.TypedPool[agd.RequestInfo]
+ pool *syncutil.Pool[agd.RequestInfo]
// db is the database of user profiles and devices.
db profiledb.Interface
@@ -50,13 +54,13 @@ type Middleware struct {
geoIP geoip.Interface
// errColl collects and reports the errors considered non-critical.
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
}
// Config is the configuration structure for the initial middleware. All fields
// must be non-nil.
type Config struct {
- // messages is used to build the responses specific for a request's context.
+ // Messages is used to build the responses specific for a request's context.
Messages *dnsmsg.Constructor
// FilteringGroup is the filtering group to which Server belongs.
@@ -75,7 +79,7 @@ type Config struct {
GeoIP geoip.Interface
// ErrColl collects and reports the errors considered non-critical.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
}
// New returns a new initial middleware. c must not be nil.
@@ -85,7 +89,7 @@ func New(c *Config) (mw *Middleware) {
fltGrp: c.FilteringGroup,
srvGrp: c.ServerGroup,
srv: c.Server,
- pool: agdsync.NewTypedPool(func() (v *agd.RequestInfo) {
+ pool: syncutil.NewPool(func() (v *agd.RequestInfo) {
return &agd.RequestInfo{}
}),
db: c.ProfileDB,
@@ -110,14 +114,10 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
reqDO := dnsmsg.IsDO(req)
req.AuthenticatedData = true
- // Assume that module dnsserver has already validated that the request
- // always has exactly one question for us.
- q := req.Question[0]
- qt := q.Qtype
- cl := q.Qclass
+ rAddr := netutil.NetAddrToAddrPort(rw.RemoteAddr())
// Get the request's information, such as GeoIP data and user profiles.
- ri, err := mw.newRequestInfo(ctx, req, rw.LocalAddr(), rw.RemoteAddr(), q.Name, qt, cl)
+ ri, err := mw.newRequestInfo(ctx, req, rw.LocalAddr(), rAddr)
if err != nil {
// Don't wrap the error, because this is the main flow, and there is
// already [errors.Annotate] here.
@@ -125,7 +125,15 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
}
defer mw.pool.Put(ri)
- if specHdlr, name := mw.reqInfoSpecialHandler(ri, cl); specHdlr != nil {
+ // Apply profile access restrictions.
+ if isBlockedByProfileAccess(ri, req, rAddr) {
+ optlog.Debug1("init mw: access: profile: req %q blocked", ri.ID)
+ metrics.AccessBlockedForProfileTotal.Inc()
+
+ return nil
+ }
+
+ if specHdlr, name := mw.reqInfoSpecialHandler(ri); specHdlr != nil {
optlog.Debug1("init mw: got req-info special handler %s", name)
// Don't wrap the error, because it's informative enough as is, and
@@ -167,11 +175,8 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
func (mw *Middleware) newRequestInfo(
ctx context.Context,
req *dns.Msg,
- laddr net.Addr,
- raddr net.Addr,
- fqdn string,
- qt dnsmsg.RRType,
- cl dnsmsg.Class,
+ lAddr net.Addr,
+ rAddr netip.AddrPort,
) (ri *agd.RequestInfo, err error) {
ri = mw.pool.Get()
@@ -193,12 +198,17 @@ func (mw *Middleware) newRequestInfo(
// immediately.
ri.FilteringGroup = mw.fltGrp
ri.Messages = mw.messages
- ri.RemoteIP = netutil.NetAddrToAddrPort(raddr).Addr()
+ ri.RemoteIP = rAddr.Addr()
ri.ServerGroup = mw.srvGrp.Name
ri.Server = mw.srv.Name
- ri.Host = agdnet.NormalizeDomain(fqdn)
- ri.QType = qt
- ri.QClass = cl
+ ri.Proto = mw.srv.Protocol
+
+ // Assume that module dnsserver has already validated that the request
+ // always has exactly one question for us.
+ q := req.Question[0]
+ ri.Host = agdnet.NormalizeDomain(q.Name)
+ ri.QType = q.Qtype
+ ri.QClass = q.Qclass
// As an optimization, put the request ID closer to the top of the context
// stack.
@@ -212,8 +222,8 @@ func (mw *Middleware) newRequestInfo(
}
// Add the profile information, if any.
- localIP := netutil.NetAddrToAddrPort(laddr).Addr()
- err = mw.addProfile(ctx, ri, req, localIP)
+ localAddr := netutil.NetAddrToAddrPort(lAddr)
+ err = mw.addProfile(ctx, ri, req, localAddr)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
@@ -244,11 +254,15 @@ func (mw *Middleware) addLocation(ctx context.Context, ri *agd.RequestInfo, req
// locationData returns the GeoIP location information about the IP address.
// typ is the type of data being requested for error reporting and logging.
-func (mw *Middleware) locationData(ctx context.Context, ip netip.Addr, typ string) (l *agd.Location) {
+func (mw *Middleware) locationData(
+ ctx context.Context,
+ ip netip.Addr,
+ typ string,
+) (l *geoip.Location) {
l, err := mw.geoIP.Data("", ip)
if err != nil {
// Consider GeoIP errors non-critical. Report and go on.
- agd.Collectf(ctx, mw.errColl, "init mw: getting geoip for %s ip: %w", typ, err)
+ errcoll.Collectf(ctx, mw.errColl, "init mw: getting geoip for %s ip: %w", typ, err)
}
if l == nil {
@@ -268,6 +282,14 @@ func (mw *Middleware) processReqInfoErr(
req *dns.Msg,
origErr error,
) (err error) {
+ if errors.Is(origErr, errUnknownDedicated) {
+ metrics.DNSSvcUnknownDedicatedTotal.Inc()
+
+ // The request is dropped by the profile search. Don't write anything
+ // and just return.
+ return nil
+ }
+
var ecsErr dnsmsg.BadECSError
if errors.As(origErr, &ecsErr) {
// We've got a bad ECS option. Log and respond with a FORMERR
@@ -282,3 +304,14 @@ func (mw *Middleware) processReqInfoErr(
return origErr
}
+
+// isBlockedByProfileAccess returns true if req is blocked by profile access
+// settings.
+func isBlockedByProfileAccess(
+ ri *agd.RequestInfo,
+ req *dns.Msg,
+ rAddr netip.AddrPort,
+) (blocked bool) {
+ return ri.Profile != nil &&
+ ri.Profile.Access.IsBlocked(req, rAddr, ri.Location)
+}
diff --git a/internal/dnssvc/internal/initial/initial_test.go b/internal/dnssvc/internal/initial/initial_test.go
index f666d23..9ae7088 100644
--- a/internal/dnssvc/internal/initial/initial_test.go
+++ b/internal/dnssvc/internal/initial/initial_test.go
@@ -2,15 +2,18 @@ package initial_test
import (
"context"
- "crypto/tls"
+ "net"
"net/netip"
"testing"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
@@ -22,95 +25,39 @@ import (
"golang.org/x/exp/maps"
)
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
+
+const (
+ resolverName = "dns.example.com"
+ resolverFQDN = resolverName + "."
+
+ targetWithID = dnssvctest.DeviceIDStr + ".d." + resolverName + "."
+
+ ddrFQDN = initial.DDRDomain + "."
+
+ dohPath = "/dns-query"
+)
+
func TestMiddleware_Wrap(t *testing.T) {
- const (
- resolverName = "dns.example.com"
- resolverFQDN = resolverName + "."
-
- targetWithID = dnssvctest.DeviceIDStr + ".d." + resolverName + "."
-
- ddrFQDN = initial.DDRDomain + "."
-
- dohPath = "/dns-query"
- )
-
testDevice := &agd.Device{ID: dnssvctest.DeviceID}
- srvs := map[agd.ServerName]*agd.Server{
- "dot": {
- TLS: &tls.Config{},
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
- }},
- Protocol: agd.ProtoDoT,
- },
- "doh": {
- TLS: &tls.Config{},
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("5.6.7.8:54321"),
- }},
- Protocol: agd.ProtoDoH,
- },
- "dns": {
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
- }},
- Protocol: agd.ProtoDNS,
- LinkedIPEnabled: true,
- },
- "dns_nolink": {
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
- }},
- Protocol: agd.ProtoDNS,
- },
- }
-
- srvGrp := &agd.ServerGroup{
- TLS: &agd.TLS{
- DeviceIDWildcards: []string{"*.d." + resolverName},
- },
- DDR: &agd.DDR{
- DeviceTargets: stringutil.NewSet(),
- PublicTargets: stringutil.NewSet(),
- Enabled: true,
- },
- Name: agd.ServerGroupName("test_server_group"),
- Servers: maps.Values(srvs),
- }
-
- srvGrp.DDR.DeviceTargets.Add("d." + resolverName)
- srvGrp.DDR.PublicTargets.Add(resolverName)
-
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- _ agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
) (_ netip.Prefix, _ error) {
panic("not implemented")
},
- OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) {
+ OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
return nil, nil
},
}
- messages := agdtest.NewConstructor()
-
- pubSVCBTmpls := []*dns.SVCB{
- messages.NewDDRTemplate(agd.ProtoDoH, resolverName, dohPath, nil, nil, 443, 1),
- messages.NewDDRTemplate(agd.ProtoDoT, resolverName, "", nil, nil, 853, 1),
- messages.NewDDRTemplate(agd.ProtoDoQ, resolverName, "", nil, nil, 853, 1),
- }
-
- devSVCBTmpls := []*dns.SVCB{
- messages.NewDDRTemplate(agd.ProtoDoH, "d."+resolverName, dohPath, nil, nil, 443, 1),
- messages.NewDDRTemplate(agd.ProtoDoT, "d."+resolverName, "", nil, nil, 853, 1),
- messages.NewDDRTemplate(agd.ProtoDoQ, "d."+resolverName, "", nil, nil, 853, 1),
- }
-
- srvGrp.DDR.PublicRecordTemplates = pubSVCBTmpls
- srvGrp.DDR.DeviceRecordTemplates = devSVCBTmpls
+ srvs := newServers()
+ // TODO(a.garipov): Use stdlib's maps in Go 1.22 or later.
+ srvGrp := newServerGroup(maps.Values(srvs))
var handler dnsserver.Handler = dnsserver.HandlerFunc(func(
_ context.Context,
@@ -135,7 +82,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dot"],
host: ddrFQDN,
wantTarget: targetWithID,
- wantNum: len(pubSVCBTmpls),
+ wantNum: len(srvGrp.DDR.PublicRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: testDevice,
@@ -143,7 +90,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dot"],
host: initial.DDRLabel + "." + targetWithID,
wantTarget: targetWithID,
- wantNum: len(devSVCBTmpls),
+ wantNum: len(srvGrp.DDR.DeviceRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: nil,
@@ -151,7 +98,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dot"],
host: ddrFQDN,
wantTarget: resolverFQDN,
- wantNum: len(pubSVCBTmpls),
+ wantNum: len(srvGrp.DDR.PublicRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: testDevice,
@@ -159,7 +106,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dns"],
host: ddrFQDN,
wantTarget: targetWithID,
- wantNum: len(pubSVCBTmpls),
+ wantNum: len(srvGrp.DDR.PublicRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: testDevice,
@@ -167,7 +114,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dns_nolink"],
host: ddrFQDN,
wantTarget: resolverFQDN,
- wantNum: len(pubSVCBTmpls),
+ wantNum: len(srvGrp.DDR.PublicRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: testDevice,
@@ -175,7 +122,7 @@ func TestMiddleware_Wrap(t *testing.T) {
srv: srvs["dot"],
host: initial.DDRLabel + "." + resolverFQDN,
wantTarget: targetWithID,
- wantNum: len(pubSVCBTmpls),
+ wantNum: len(srvGrp.DDR.PublicRecordTemplates),
qtype: dns.TypeSVCB,
}, {
device: nil,
@@ -197,6 +144,10 @@ func TestMiddleware_Wrap(t *testing.T) {
qtype: dns.TypeA,
}}
+ prof := &agd.Profile{
+ Access: access.EmptyProfile{},
+ }
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := &agdtest.ProfileDB{
@@ -204,7 +155,7 @@ func TestMiddleware_Wrap(t *testing.T) {
_ context.Context,
_ agd.DeviceID,
) (p *agd.Profile, d *agd.Device, err error) {
- return &agd.Profile{}, tc.device, nil
+ return prof, tc.device, nil
},
OnProfileByDedicatedIP: func(
_ context.Context,
@@ -216,7 +167,7 @@ func TestMiddleware_Wrap(t *testing.T) {
_ context.Context,
_ netip.Addr,
) (p *agd.Profile, d *agd.Device, err error) {
- return &agd.Profile{}, tc.device, nil
+ return prof, tc.device, nil
},
}
@@ -233,7 +184,7 @@ func TestMiddleware_Wrap(t *testing.T) {
})
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
TLSServerName: srvNameForProto(tc.device, resolverName, tc.srv.Protocol),
})
@@ -300,23 +251,19 @@ func TestMiddleware_Wrap_error(t *testing.T) {
Name: agd.ServerGroupName("test_server_group"),
}
- srv := &agd.Server{
- BindData: []*agd.ServerBindData{{
- AddrPort: netip.MustParseAddrPort("1.2.3.4:53"),
- }},
- Protocol: agd.ProtoDNS,
- LinkedIPEnabled: true,
- }
+ srv := dnssvctest.NewServer("dns", agd.ProtoDNS, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:53"),
+ })
+ srv.LinkedIPEnabled = true
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- _ agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
) (_ netip.Prefix, _ error) {
panic("not implemented")
},
- OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) {
+ OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
return nil, nil
},
}
@@ -334,13 +281,13 @@ func TestMiddleware_Wrap_error(t *testing.T) {
_ context.Context,
_ netip.Addr,
) (p *agd.Profile, d *agd.Device, err error) {
- return nil, nil, testError
+ panic("not implemented")
},
OnProfileByLinkedIP: func(
_ context.Context,
_ netip.Addr,
) (p *agd.Profile, d *agd.Device, err error) {
- panic("not implemented")
+ return nil, nil, testError
},
}
@@ -371,6 +318,288 @@ func TestMiddleware_Wrap_error(t *testing.T) {
assert.ErrorIs(t, err, testError)
}
+func TestMiddleware_Wrap_access(t *testing.T) {
+ const passHost = "pass.test"
+
+ passIP := net.IP{3, 3, 3, 3}
+
+ srvs := newServers()
+ // TODO(a.garipov): Use stdlib's maps in Go 1.22 or later.
+ srvGrp := newServerGroup(maps.Values(srvs))
+
+ testDevice := &agd.Device{ID: dnssvctest.DeviceID}
+ testProfile := &agd.Profile{
+ Access: access.NewDefaultProfile(&access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.1/32")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{1, 2},
+ BlocklistDomainRules: []string{
+ "block.test",
+ "UPPERCASE.test",
+ "||block_aaaa.test^$dnstype=AAAA",
+ "||allowlist.test^",
+ "@@||allow.allowlist.test^",
+ },
+ }),
+ }
+
+ db := &agdtest.ProfileDB{
+ OnProfileByDeviceID: func(
+ _ context.Context,
+ _ agd.DeviceID,
+ ) (p *agd.Profile, d *agd.Device, err error) {
+ return testProfile, testDevice, nil
+ },
+ OnProfileByDedicatedIP: func(
+ _ context.Context,
+ _ netip.Addr,
+ ) (p *agd.Profile, d *agd.Device, err error) {
+ return nil, nil, profiledb.ErrDeviceNotFound
+ },
+ OnProfileByLinkedIP: func(
+ _ context.Context,
+ _ netip.Addr,
+ ) (p *agd.Profile, d *agd.Device, err error) {
+ return nil, nil, profiledb.ErrDeviceNotFound
+ },
+ }
+
+ var handler dnsserver.Handler = dnsserver.HandlerFunc(func(
+ ctx context.Context,
+ rw dnsserver.ResponseWriter,
+ q *dns.Msg,
+ ) (_ error) {
+ resp := dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ q,
+ dnsservertest.SectionAnswer{
+ dnsservertest.NewA("test.domain", 0, netip.MustParseAddr("5.5.5.5")),
+ },
+ )
+
+ err := rw.WriteMsg(ctx, q, resp)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ testCases := []struct {
+ loc *geoip.Location
+ wantResp assert.BoolAssertionFunc
+ name string
+ host string
+ ip net.IP
+ qt uint16
+ }{{
+ wantResp: assert.True,
+ name: "pass",
+ host: passHost,
+ qt: dns.TypeA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "blocked_domain_A",
+ host: "block.test",
+ qt: dns.TypeA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "blocked_domain_HTTPS",
+ host: "block.test",
+ qt: dns.TypeHTTPS,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "uppercase_domain",
+ host: "uppercase.test",
+ qt: dns.TypeHTTPS,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.True,
+ name: "pass_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "block_qt",
+ host: "block_aaaa.test",
+ qt: dns.TypeAAAA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "allowlist_block",
+ host: "block.allowlist.test",
+ qt: dns.TypeA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.True,
+ name: "allowlist_test",
+ host: "allow.allowlist.test",
+ qt: dns.TypeA,
+ ip: passIP,
+ loc: nil,
+ }, {
+ wantResp: assert.True,
+ name: "pass_ip",
+ ip: net.IP{1, 1, 1, 1},
+ host: passHost,
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ wantResp: assert.False,
+ name: "block_subnet",
+ ip: net.IP{1, 1, 1, 2},
+ host: passHost,
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ wantResp: assert.True,
+ name: "pass_subnet",
+ ip: net.IP{1, 2, 2, 2},
+ host: passHost,
+ qt: dns.TypeA,
+ loc: nil,
+ }, {
+ wantResp: assert.True,
+ name: "pass_asn",
+ ip: passIP,
+ host: "pass.test",
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 1},
+ }, {
+ wantResp: assert.False,
+ name: "block_host_pass_asn",
+ ip: passIP,
+ host: "block.test",
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 1},
+ }, {
+ wantResp: assert.False,
+ name: "block_asn",
+ ip: passIP,
+ host: passHost,
+ qt: dns.TypeA,
+ loc: &geoip.Location{ASN: 2},
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ geoIP := &agdtest.GeoIP{
+ OnSubnetByLocation: func(
+ _ *geoip.Location,
+ _ netutil.AddrFamily,
+ ) (_ netip.Prefix, _ error) {
+ panic("not implemented")
+ },
+ OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
+ return tc.loc, nil
+ },
+ }
+
+ mw := initial.New(&initial.Config{
+ Messages: agdtest.NewConstructor(),
+ FilteringGroup: &agd.FilteringGroup{},
+ ServerGroup: srvGrp,
+ Server: srvs["dot"],
+ ProfileDB: db,
+ GeoIP: geoIP,
+ ErrColl: &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, _ error) { panic("not implemented") },
+ },
+ })
+
+ ctx := context.Background()
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ TLSServerName: srvNameForProto(testDevice, resolverName, srvs["dot"].Protocol),
+ })
+
+ rw := dnsserver.NewNonWriterResponseWriter(nil, &net.TCPAddr{IP: tc.ip, Port: 5357})
+ req := &dns.Msg{
+ Question: []dns.Question{{
+ Name: tc.host,
+ Qtype: tc.qt,
+ Qclass: dns.ClassINET,
+ }},
+ }
+
+ h := mw.Wrap(handler)
+ err := h.ServeDNS(ctx, rw, req)
+ require.NoError(t, err)
+
+ resp := rw.Msg()
+ tc.wantResp(t, resp != nil)
+ })
+ }
+}
+
+func newServers() (srvs map[agd.ServerName]*agd.Server) {
+ linkIPSrv := dnssvctest.NewServer("dns", agd.ProtoDNS, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
+ })
+ linkIPSrv.LinkedIPEnabled = true
+
+ return map[agd.ServerName]*agd.Server{
+ "dns": linkIPSrv,
+ "dns_nolink": dnssvctest.NewServer("dns_nolink", agd.ProtoDNS, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
+ }),
+ "dot": dnssvctest.NewServer("dot", agd.ProtoDoT, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
+ }),
+ "doh": dnssvctest.NewServer("doh", agd.ProtoDoH, &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:54321"),
+ }),
+ }
+}
+
+func newServerGroup(srvs []*agd.Server) (srvGrp *agd.ServerGroup) {
+ srvGrp = &agd.ServerGroup{
+ TLS: &agd.TLS{
+ DeviceIDWildcards: []string{"*.d." + resolverName},
+ },
+ DDR: &agd.DDR{
+ DeviceTargets: stringutil.NewSet(),
+ PublicTargets: stringutil.NewSet(),
+ Enabled: true,
+ },
+ Name: "test_server_group",
+ Servers: srvs,
+ }
+
+ srvGrp.DDR.DeviceTargets.Add("d." + resolverName)
+ srvGrp.DDR.PublicTargets.Add(resolverName)
+
+ messages := agdtest.NewConstructor()
+
+ pubSVCBTmpls := []*dns.SVCB{
+ messages.NewDDRTemplate(agd.ProtoDoH, resolverName, dohPath, nil, nil, 443, 1),
+ messages.NewDDRTemplate(agd.ProtoDoT, resolverName, "", nil, nil, 853, 1),
+ messages.NewDDRTemplate(agd.ProtoDoQ, resolverName, "", nil, nil, 853, 1),
+ }
+
+ devSVCBTmpls := []*dns.SVCB{
+ messages.NewDDRTemplate(agd.ProtoDoH, "d."+resolverName, dohPath, nil, nil, 443, 1),
+ messages.NewDDRTemplate(agd.ProtoDoT, "d."+resolverName, "", nil, nil, 853, 1),
+ messages.NewDDRTemplate(agd.ProtoDoQ, "d."+resolverName, "", nil, nil, 853, 1),
+ }
+
+ srvGrp.DDR.PublicRecordTemplates = pubSVCBTmpls
+ srvGrp.DDR.DeviceRecordTemplates = devSVCBTmpls
+
+ return srvGrp
+}
+
var errSink error
func BenchmarkMiddleware_Wrap(b *testing.B) {
@@ -385,32 +614,30 @@ func BenchmarkMiddleware_Wrap(b *testing.B) {
Enabled: true,
},
Name: agd.ServerGroupName("test_server_group"),
- Servers: []*agd.Server{{
- BindData: []*agd.ServerBindData{{
+ Servers: []*agd.Server{
+ dnssvctest.NewServer("test_server_dot", agd.ProtoDoT, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
- }, {
+ }, &agd.ServerBindData{
AddrPort: netip.MustParseAddrPort("4.3.2.1:12345"),
- }},
- Protocol: agd.ProtoDoT,
- }},
+ }),
+ },
}
messages := agdtest.NewConstructor()
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- _ agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
- ) (n netip.Prefix, err error) {
+ ) (_ netip.Prefix, _ error) {
panic("not implemented")
},
- OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) {
+ OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
return nil, nil
},
}
- ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindData[0].AddrPort.Addr()}
+ ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindData()[0].AddrPort.Addr()}
ipv6Hints := []netip.Addr{netip.MustParseAddr("2001::1234")}
srvGrp.DDR.DeviceTargets.Add(devIDTarget)
@@ -420,11 +647,13 @@ func BenchmarkMiddleware_Wrap(b *testing.B) {
messages.NewDDRTemplate(agd.ProtoDoQ, devIDTarget, "", ipv4Hints, ipv6Hints, 853, 1),
}
- prof := &agd.Profile{}
+ prof := &agd.Profile{
+ Access: access.EmptyProfile{},
+ }
dev := &agd.Device{}
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
TLSServerName: dnssvctest.DeviceIDStr + ".dns.example.com",
})
@@ -653,8 +882,8 @@ func BenchmarkMiddleware_Wrap(b *testing.B) {
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
- // BenchmarkMiddleware_Wrap/success-16 1970464 735.8 ns/op 72 B/op 2 allocs/op
- // BenchmarkMiddleware_Wrap/not_found-16 1469100 715.9 ns/op 48 B/op 1 allocs/op
- // BenchmarkMiddleware_Wrap/firefox_canary-16 1644410 861.9 ns/op 72 B/op 2 allocs/op
- // BenchmarkMiddleware_Wrap/ddr-16 252656 4810 ns/op 1408 B/op 45 allocs/op
+ // BenchmarkMiddleware_Wrap/success-16 1929643 659.7 ns/op 72 B/op 2 allocs/op
+ // BenchmarkMiddleware_Wrap/not_found-16 2377362 587.3 ns/op 48 B/op 1 allocs/op
+ // BenchmarkMiddleware_Wrap/firefox_canary-16 1663753 757.4 ns/op 72 B/op 2 allocs/op
+ // BenchmarkMiddleware_Wrap/ddr-16 280981 4431 ns/op 1408 B/op 45 allocs/op
}
diff --git a/internal/dnssvc/internal/initial/profile.go b/internal/dnssvc/internal/initial/profile.go
index 266a87d..88d02d6 100644
--- a/internal/dnssvc/internal/initial/profile.go
+++ b/internal/dnssvc/internal/initial/profile.go
@@ -19,7 +19,7 @@ func (mw *Middleware) addProfile(
ctx context.Context,
ri *agd.RequestInfo,
req *dns.Msg,
- localIP netip.Addr,
+ localAddr netip.AddrPort,
) (err error) {
defer func() { err = errors.Annotate(err, "getting profile from req: %w") }()
@@ -39,9 +39,9 @@ func (mw *Middleware) addProfile(
return err
}
- optlog.Debug3("init mw: got device id %q, raddr %s, and laddr %s", id, ri.RemoteIP, localIP)
+ optlog.Debug3("init mw: got device id %q, raddr %s, and laddr %s", id, ri.RemoteIP, localAddr)
- prof, dev, byWhat, err := mw.profile(ctx, localIP, ri.RemoteIP, id)
+ prof, dev, byWhat, err := mw.profile(ctx, localAddr, ri.RemoteIP, id)
if err != nil {
if !errors.Is(err, profiledb.ErrDeviceNotFound) {
// Very unlikely, since there is only one error type currently
@@ -56,7 +56,10 @@ func (mw *Middleware) addProfile(
optlog.Debug3("init mw: found profile %s and device %s by %s", prof.ID, dev.ID, byWhat)
ri.Device, ri.Profile = dev, prof
- ri.Messages = dnsmsg.NewConstructor(prof.BlockingMode.Mode, prof.FilteredResponseTTL)
+
+ // TODO(a.garipov): Consider using the global cloner here once it is
+ // tested and optimized.
+ ri.Messages = dnsmsg.NewConstructor(nil, prof.BlockingMode, prof.FilteredResponseTTL)
}
return nil
@@ -69,10 +72,14 @@ const (
byLinkedIP = "linked ip"
)
+// errUnknownDedicated is returned by [Middleware.profile] if the request should
+// be dropped, because it's a request for an unknown dedicated IP address.
+const errUnknownDedicated errors.Error = "drop"
+
// profile finds the profile by the client data.
func (mw *Middleware) profile(
ctx context.Context,
- localIP netip.Addr,
+ localAddr netip.AddrPort,
remoteIP netip.Addr,
id agd.DeviceID,
) (prof *agd.Profile, dev *agd.Device, byWhat string, err error) {
@@ -85,26 +92,40 @@ func (mw *Middleware) profile(
return prof, dev, byDeviceID, nil
}
+ if mw.srv.Protocol == agd.ProtoDNS {
+ return mw.profileByAddrs(ctx, localAddr, remoteIP)
+ }
+
+ return nil, nil, "", profiledb.ErrDeviceNotFound
+}
+
+// profileByAddrs finds the profile by the remote and local addresses.
+func (mw *Middleware) profileByAddrs(
+ ctx context.Context,
+ localAddr netip.AddrPort,
+ remoteIP netip.Addr,
+) (prof *agd.Profile, dev *agd.Device, byWhat string, err error) {
+ if mw.srv.BindsToInterfaces() && !mw.srv.HasAddr(localAddr) {
+ prof, dev, err = mw.db.ProfileByDedicatedIP(ctx, localAddr.Addr())
+ if err == nil {
+ return prof, dev, byDedicatedIP, nil
+ } else if errors.Is(err, profiledb.ErrDeviceNotFound) {
+ optlog.Debug1("init mw: unknown dedicated ip for server %s; dropping", mw.srv.Name)
+
+ err = errUnknownDedicated
+ }
+
+ return nil, nil, "", err
+ }
+
if !mw.srv.LinkedIPEnabled {
- optlog.Debug1("init mw: not matching by linked or dedicated ip for server %s", mw.srv.Name)
-
- return nil, nil, "", profiledb.ErrDeviceNotFound
- } else if p := mw.srv.Protocol; p != agd.ProtoDNS {
- optlog.Debug1("init mw: not matching by linked or dedicated ip for proto %v", p)
-
return nil, nil, "", profiledb.ErrDeviceNotFound
}
- byWhat = byDedicatedIP
- prof, dev, err = mw.db.ProfileByDedicatedIP(ctx, localIP)
- if errors.Is(err, profiledb.ErrDeviceNotFound) {
- byWhat = byLinkedIP
- prof, dev, err = mw.db.ProfileByLinkedIP(ctx, remoteIP)
- }
-
+ prof, dev, err = mw.db.ProfileByLinkedIP(ctx, remoteIP)
if err != nil {
return nil, nil, "", err
}
- return prof, dev, byWhat, nil
+ return prof, dev, byLinkedIP, nil
}
diff --git a/internal/dnssvc/internal/initial/profile_internal_test.go b/internal/dnssvc/internal/initial/profile_internal_test.go
index 3bd5644..189b8a3 100644
--- a/internal/dnssvc/internal/initial/profile_internal_test.go
+++ b/internal/dnssvc/internal/initial/profile_internal_test.go
@@ -5,7 +5,9 @@ import (
"net/netip"
"testing"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
@@ -13,13 +15,40 @@ import (
"github.com/stretchr/testify/assert"
)
+// Bind data for tests.
+var (
+ bindDataAddr = &agd.ServerBindData{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:53"),
+ }
+
+ bindDataIface = &agd.ServerBindData{
+ ListenConfig: &agdtest.ListenConfig{},
+ PrefixAddr: &agdnet.PrefixNetAddr{
+ Prefix: netip.MustParsePrefix("1.2.3.0/24"),
+ Net: "",
+ Port: 53,
+ },
+ }
+
+ bindDataIfaceSingleIP = &agd.ServerBindData{
+ ListenConfig: &agdtest.ListenConfig{},
+ PrefixAddr: &agdnet.PrefixNetAddr{
+ Prefix: netip.PrefixFrom(dnssvctest.ServerAddr, 32),
+ Net: "",
+ Port: dnssvctest.ServerAddrPort.Port(),
+ },
+ }
+)
+
func TestMiddleware_profile(t *testing.T) {
prof := &agd.Profile{
- ID: dnssvctest.ProfileID,
+ Access: access.EmptyProfile{},
+ ID: dnssvctest.ProfileID,
DeviceIDs: []agd.DeviceID{
dnssvctest.DeviceID,
},
}
+
dev := &agd.Device{
ID: dnssvctest.DeviceID,
LinkedIP: dnssvctest.ClientAddr,
@@ -55,15 +84,6 @@ func TestMiddleware_profile(t *testing.T) {
id: dnssvctest.DeviceID,
proto: agd.ProtoDNS,
linkedIPEnabled: true,
- }, {
- wantDev: dev,
- wantProf: prof,
- wantByWhat: byLinkedIP,
- wantErrMsg: "",
- name: "linked_ip",
- id: "",
- proto: agd.ProtoDNS,
- linkedIPEnabled: true,
}, {
wantDev: nil,
wantProf: nil,
@@ -73,14 +93,73 @@ func TestMiddleware_profile(t *testing.T) {
id: "",
proto: agd.ProtoDoT,
linkedIPEnabled: true,
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ srv := dnssvctest.NewServer("test_server", tc.proto, bindDataAddr)
+ srv.LinkedIPEnabled = tc.linkedIPEnabled
+
+ mw := New(&Config{
+ Server: srv,
+ ProfileDB: newProfileDB(t, prof, dev, tc.wantByWhat),
+ })
+
+ ctx := context.Background()
+ gotProf, gotDev, gotByWhat, err := mw.profile(
+ ctx,
+ dnssvctest.ServerAddrPort,
+ dnssvctest.ClientAddr,
+ tc.id,
+ )
+ testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+ assert.Equal(t, tc.wantProf, gotProf)
+ assert.Equal(t, tc.wantDev, gotDev)
+ assert.Equal(t, tc.wantByWhat, gotByWhat)
+ })
+ }
+}
+
+func TestMiddleware_profileByAddrs(t *testing.T) {
+ prof := &agd.Profile{
+ Access: access.EmptyProfile{},
+ ID: dnssvctest.ProfileID,
+ DeviceIDs: []agd.DeviceID{
+ dnssvctest.DeviceID,
+ },
+ }
+
+ dev := &agd.Device{
+ ID: dnssvctest.DeviceID,
+ LinkedIP: dnssvctest.ClientAddr,
+ DedicatedIPs: []netip.Addr{
+ dnssvctest.ServerAddr,
+ },
+ }
+
+ testCases := []struct {
+ wantDev *agd.Device
+ wantProf *agd.Profile
+ wantByWhat string
+ wantErrMsg string
+ name string
+ bindData []*agd.ServerBindData
+ linkedIPEnabled bool
+ }{{
+ wantDev: dev,
+ wantProf: prof,
+ wantByWhat: byLinkedIP,
+ wantErrMsg: "",
+ name: "linked_ip",
+ bindData: []*agd.ServerBindData{bindDataAddr},
+ linkedIPEnabled: true,
}, {
wantDev: nil,
wantProf: nil,
wantByWhat: "",
wantErrMsg: "device not found",
name: "linked_ip_disabled",
- id: "",
- proto: agd.ProtoDoT,
+ bindData: []*agd.ServerBindData{bindDataAddr},
linkedIPEnabled: false,
}, {
wantDev: dev,
@@ -88,27 +167,44 @@ func TestMiddleware_profile(t *testing.T) {
wantByWhat: byDedicatedIP,
wantErrMsg: "",
name: "dedicated_ip",
- id: "",
- proto: agd.ProtoDNS,
+ bindData: []*agd.ServerBindData{bindDataIface},
+ linkedIPEnabled: true,
+ }, {
+ wantDev: nil,
+ wantProf: nil,
+ wantByWhat: "",
+ wantErrMsg: "drop",
+ name: "dedicated_ip_not_found",
+ bindData: []*agd.ServerBindData{bindDataIface},
+ linkedIPEnabled: true,
+ }, {
+ wantDev: nil,
+ wantProf: nil,
+ wantByWhat: "",
+ wantErrMsg: "device not found",
+ name: "dedicated_ip_and_single_ip",
+ bindData: []*agd.ServerBindData{
+ bindDataIface,
+ bindDataIfaceSingleIP,
+ },
linkedIPEnabled: true,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
+ srv := dnssvctest.NewServer("test_server", agd.ProtoDNS, tc.bindData...)
+ srv.LinkedIPEnabled = tc.linkedIPEnabled
+
mw := New(&Config{
- Server: &agd.Server{
- Protocol: tc.proto,
- LinkedIPEnabled: tc.linkedIPEnabled,
- },
+ Server: srv,
ProfileDB: newProfileDB(t, prof, dev, tc.wantByWhat),
})
ctx := context.Background()
- gotProf, gotDev, gotByWhat, err := mw.profile(
+ gotProf, gotDev, gotByWhat, err := mw.profileByAddrs(
ctx,
- dnssvctest.ServerAddr,
+ dnssvctest.ServerAddrPort,
dnssvctest.ClientAddr,
- tc.id,
)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
assert.Equal(t, tc.wantProf, gotProf)
diff --git a/internal/dnssvc/internal/initial/specialdomain.go b/internal/dnssvc/internal/initial/specialdomain.go
index 1269c5a..73c8467 100644
--- a/internal/dnssvc/internal/initial/specialdomain.go
+++ b/internal/dnssvc/internal/initial/specialdomain.go
@@ -5,7 +5,6 @@ import (
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
@@ -50,11 +49,8 @@ const (
// reqInfoSpecialHandler returns a handler that can handle a special-domain
// query based on the request info, as well as the handler's name for debugging.
-func (mw *Middleware) reqInfoSpecialHandler(
- ri *agd.RequestInfo,
- cl dnsmsg.Class,
-) (f reqInfoHandlerFunc, name string) {
- if cl != dns.ClassINET {
+func (mw *Middleware) reqInfoSpecialHandler(ri *agd.RequestInfo) (f reqInfoHandlerFunc, name string) {
+ if ri.QClass != dns.ClassINET {
return nil, ""
}
diff --git a/internal/dnssvc/internal/initial/specialdomain_test.go b/internal/dnssvc/internal/initial/specialdomain_test.go
index 2ca8cad..943972d 100644
--- a/internal/dnssvc/internal/initial/specialdomain_test.go
+++ b/internal/dnssvc/internal/initial/specialdomain_test.go
@@ -5,12 +5,14 @@ import (
"net/netip"
"testing"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
@@ -143,12 +145,14 @@ func TestMiddleware_ServeDNS_specialDomain(t *testing.T) {
}
prof := &agd.Profile{
+ Access: access.EmptyProfile{},
BlockPrivateRelay: tc.profBlocked,
BlockFirefoxCanary: tc.profBlocked,
}
return prof, &agd.Device{}, nil
}
+
db := &agdtest.ProfileDB{
OnProfileByDeviceID: func(
_ context.Context,
@@ -167,13 +171,12 @@ func TestMiddleware_ServeDNS_specialDomain(t *testing.T) {
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- _ agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {
panic("not implemented")
},
- OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) {
+ OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
return nil, nil
},
}
diff --git a/internal/dnssvc/debug.go b/internal/dnssvc/internal/mainmw/debug.go
similarity index 75%
rename from internal/dnssvc/debug.go
rename to internal/dnssvc/internal/mainmw/debug.go
index 626e985..aa24c81 100644
--- a/internal/dnssvc/debug.go
+++ b/internal/dnssvc/internal/mainmw/debug.go
@@ -1,4 +1,4 @@
-package dnssvc
+package mainmw
import (
"context"
@@ -7,9 +7,9 @@ import (
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
@@ -31,19 +31,17 @@ const (
)
// writeDebugResponse writes the debug response to rw.
-func (svc *Service) writeDebugResponse(
+func (mw *Middleware) writeDebugResponse(
ctx context.Context,
+ fctx *filteringContext,
rw dnsserver.ResponseWriter,
- req *dns.Msg,
- resp *dns.Msg,
- reqRes filter.Result,
- respRes filter.Result,
) (err error) {
defer func() { err = errors.Annotate(err, "debug: %w") }()
+ resp := fctx.filteredResponse
resp.Question[0].Qclass = dns.ClassCHAOS
- debugReq := dnsmsg.Clone(req)
+ debugReq := mw.cloner.Clone(fctx.originalRequest)
debugReq.Question[0].Qclass = dns.ClassCHAOS
debugReq.Question[0].Qtype = dns.TypeTXT
@@ -51,7 +49,7 @@ func (svc *Service) writeDebugResponse(
cliIP, _ := netutil.IPAndPortFromAddr(rAddr)
setQuestionName(debugReq, "", hdrNameClientIP)
- err = svc.messages.AppendDebugExtra(debugReq, resp, cliIP.String())
+ err = mw.messages.AppendDebugExtra(debugReq, resp, cliIP.String())
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameClientIP, err)
}
@@ -60,33 +58,33 @@ func (svc *Service) writeDebugResponse(
localIP, _ := netutil.IPAndPortFromAddr(lAddr)
setQuestionName(debugReq, "", hdrNameServerIP)
- err = svc.messages.AppendDebugExtra(debugReq, resp, localIP.String())
+ err = mw.messages.AppendDebugExtra(debugReq, resp, localIP.String())
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameServerIP, err)
}
- err = svc.appendDebugExtraFromContext(ctx, debugReq, resp)
+ err = mw.appendDebugExtraFromContext(ctx, debugReq, resp)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
- if reqRes == nil {
- err = svc.debugResponse(debugReq, resp, respRes, "resp")
+ if fctx.requestResult == nil {
+ err = mw.debugResponse(debugReq, resp, fctx.responseResult, "resp")
} else {
- err = svc.debugResponse(debugReq, resp, reqRes, "req")
+ err = mw.debugResponse(debugReq, resp, fctx.requestResult, "req")
}
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
- return rw.WriteMsg(ctx, req, resp)
+ return rw.WriteMsg(ctx, fctx.originalRequest, resp)
}
// appendDebugExtraFromContext appends debug extra records that we can get from
// the context.
-func (svc *Service) appendDebugExtraFromContext(
+func (mw *Middleware) appendDebugExtraFromContext(
ctx context.Context,
debugReq *dns.Msg,
resp *dns.Msg,
@@ -94,7 +92,7 @@ func (svc *Service) appendDebugExtraFromContext(
ri := agd.MustRequestInfoFromContext(ctx)
if d := ri.Device; d != nil {
setQuestionName(debugReq, "", hdrNameDeviceID)
- err = svc.messages.AppendDebugExtra(debugReq, resp, string(d.ID))
+ err = mw.messages.AppendDebugExtra(debugReq, resp, string(d.ID))
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameDeviceID, err)
}
@@ -102,14 +100,14 @@ func (svc *Service) appendDebugExtraFromContext(
if p := ri.Profile; p != nil {
setQuestionName(debugReq, "", hdrNameProfileID)
- err = svc.messages.AppendDebugExtra(debugReq, resp, string(p.ID))
+ err = mw.messages.AppendDebugExtra(debugReq, resp, string(p.ID))
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameProfileID, err)
}
}
if loc := ri.Location; loc != nil {
- err = svc.appendDebugExtraFromLocation(loc, debugReq, resp)
+ err = mw.appendDebugExtraFromLocation(loc, debugReq, resp)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
@@ -121,26 +119,26 @@ func (svc *Service) appendDebugExtraFromContext(
// appendDebugExtraFromLocation adds debug info to response got from request
// info location. loc should not be nil.
-func (svc *Service) appendDebugExtraFromLocation(
- loc *agd.Location,
+func (mw *Middleware) appendDebugExtraFromLocation(
+ loc *geoip.Location,
debugReq *dns.Msg,
resp *dns.Msg,
) (err error) {
setQuestionName(debugReq, "", hdrNameCountry)
- err = svc.messages.AppendDebugExtra(debugReq, resp, string(loc.Country))
+ err = mw.messages.AppendDebugExtra(debugReq, resp, string(loc.Country))
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameCountry, err)
}
setQuestionName(debugReq, "", hdrNameASN)
- err = svc.messages.AppendDebugExtra(debugReq, resp, strconv.FormatUint(uint64(loc.ASN), 10))
+ err = mw.messages.AppendDebugExtra(debugReq, resp, strconv.FormatUint(uint64(loc.ASN), 10))
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameASN, err)
}
if subdivision := loc.TopSubdivision; subdivision != "" {
setQuestionName(debugReq, "", hdrNameSubdivision)
- err = svc.messages.AppendDebugExtra(debugReq, resp, subdivision)
+ err = mw.messages.AppendDebugExtra(debugReq, resp, subdivision)
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameSubdivision, err)
}
@@ -150,7 +148,7 @@ func (svc *Service) appendDebugExtraFromLocation(
}
// debugResponse forms a debug response.
-func (svc *Service) debugResponse(
+func (mw *Middleware) debugResponse(
req *dns.Msg,
resp *dns.Msg,
fltRes filter.Result,
@@ -158,7 +156,7 @@ func (svc *Service) debugResponse(
) (err error) {
if fltRes == nil {
setQuestionName(req, applyTo, hdrNameResType)
- err = svc.messages.AppendDebugExtra(req, resp, "normal")
+ err = mw.messages.AppendDebugExtra(req, resp, "normal")
return errors.Annotate(err, "adding %s extra: %w", hdrNameProfileID)
}
@@ -181,11 +179,11 @@ func (svc *Service) debugResponse(
})
}
- return svc.addDebugExtraFromFiltering(req, resp, state, string(rule), string(fltID), applyTo)
+ return mw.addDebugExtraFromFiltering(req, resp, state, string(rule), string(fltID), applyTo)
}
// addDebugExtraFromFiltering adds to response debug info from filtering meta.
-func (svc *Service) addDebugExtraFromFiltering(
+func (mw *Middleware) addDebugExtraFromFiltering(
req *dns.Msg,
resp *dns.Msg,
state string,
@@ -194,7 +192,7 @@ func (svc *Service) addDebugExtraFromFiltering(
applyTo string,
) (err error) {
setQuestionName(req, applyTo, hdrNameResType)
- err = svc.messages.AppendDebugExtra(req, resp, state)
+ err = mw.messages.AppendDebugExtra(req, resp, state)
if err != nil {
return fmt.Errorf(
"adding %s debug extra for %s response: %w",
@@ -205,13 +203,13 @@ func (svc *Service) addDebugExtraFromFiltering(
}
setQuestionName(req, applyTo, hdrNameRule)
- err = svc.messages.AppendDebugExtra(req, resp, rule)
+ err = mw.messages.AppendDebugExtra(req, resp, rule)
if err != nil {
return fmt.Errorf("adding %s debug extra: %w", hdrNameRule, err)
}
setQuestionName(req, applyTo, hdrNameRuleListID)
- err = svc.messages.AppendDebugExtra(req, resp, ruleID)
+ err = mw.messages.AppendDebugExtra(req, resp, ruleID)
if err != nil {
return fmt.Errorf("adding %s debug extra: %w", hdrNameRuleListID, err)
}
diff --git a/internal/dnssvc/debug_internal_test.go b/internal/dnssvc/internal/mainmw/debug_internal_test.go
similarity index 56%
rename from internal/dnssvc/debug_internal_test.go
rename to internal/dnssvc/internal/mainmw/debug_internal_test.go
index 4170226..e9f21bf 100644
--- a/internal/dnssvc/debug_internal_test.go
+++ b/internal/dnssvc/internal/mainmw/debug_internal_test.go
@@ -1,7 +1,8 @@
-package dnssvc
+package mainmw
import (
"context"
+ "fmt"
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -10,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -36,29 +38,44 @@ func newTXTExtra(strs [][2]string) (extra []dns.RR) {
return extra
}
-func TestService_writeDebugResponse(t *testing.T) {
- svc := &Service{
+// TODO(a.garipov): Rewrite into cases in external tests.
+func TestMiddleware_writeDebugResponse(t *testing.T) {
+ mw := &Middleware{
messages: agdtest.NewConstructor(),
+ cloner: agdtest.NewCloner(),
+ errColl: &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, err error) {
+ panic(fmt.Errorf("unexpected error: %w", err))
+ },
+ },
}
+ // TODO(a.garipov): Consider moving to dnssvctest and DRY'ing with
+ // mainmw_test.
const (
- fltListID1 agd.FilterListID = "fl1"
- fltListID2 agd.FilterListID = "fl2"
-
- blockRule = "||example.com^"
+ allowRule = "||" + dnssvctest.DomainAllowed + "^"
+ blockRule = "||" + dnssvctest.DomainBlocked + "^"
+ rewriteRule = "||" + dnssvctest.DomainRewritten + "^$dnsrewrite=REFUSED"
)
clientIPStr := dnssvctest.ClientIP.String()
serverIPStr := dnssvctest.ServerAddr.String()
+
+ defaultReqInfo := &agd.RequestInfo{
+ Messages: agdtest.NewConstructor(),
+ }
+
testCases := []struct {
name string
- ri *agd.RequestInfo
+ domain string
+ reqInfo *agd.RequestInfo
reqRes filter.Result
respRes filter.Result
wantExtra []dns.RR
}{{
name: "normal",
- ri: &agd.RequestInfo{},
+ domain: dnssvctest.DomainFQDN,
+ reqInfo: defaultReqInfo,
reqRes: nil,
respRes: nil,
wantExtra: newTXTExtra([][2]string{
@@ -68,69 +85,87 @@ func TestService_writeDebugResponse(t *testing.T) {
}),
}, {
name: "request_result_blocked",
- ri: &agd.RequestInfo{},
- reqRes: &filter.ResultBlocked{List: fltListID1, Rule: blockRule},
+ domain: dnssvctest.DomainBlockedFQDN,
+ reqInfo: defaultReqInfo,
+ reqRes: &filter.ResultBlocked{List: dnssvctest.FilterListID1, Rule: blockRule},
respRes: nil,
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"req.res-type.adguard-dns.com.", "blocked"},
- {"req.rule.adguard-dns.com.", "||example.com^"},
- {"req.rule-list-id.adguard-dns.com.", "fl1"},
+ {"req.rule.adguard-dns.com.", blockRule},
+ {"req.rule-list-id.adguard-dns.com.", dnssvctest.FilterListID1Str},
}),
}, {
name: "response_result_blocked",
- ri: &agd.RequestInfo{},
+ domain: dnssvctest.DomainBlockedFQDN,
+ reqInfo: defaultReqInfo,
reqRes: nil,
- respRes: &filter.ResultBlocked{List: fltListID2, Rule: blockRule},
+ respRes: &filter.ResultBlocked{List: dnssvctest.FilterListID2, Rule: blockRule},
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"resp.res-type.adguard-dns.com.", "blocked"},
- {"resp.rule.adguard-dns.com.", "||example.com^"},
- {"resp.rule-list-id.adguard-dns.com.", "fl2"},
+ {"resp.rule.adguard-dns.com.", blockRule},
+ {"resp.rule-list-id.adguard-dns.com.", dnssvctest.FilterListID2Str},
}),
}, {
name: "request_result_allowed",
- ri: &agd.RequestInfo{},
- reqRes: &filter.ResultAllowed{},
+ domain: dnssvctest.DomainAllowedFQDN,
+ reqInfo: defaultReqInfo,
+ reqRes: &filter.ResultAllowed{
+ Rule: allowRule,
+ },
respRes: nil,
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"req.res-type.adguard-dns.com.", "allowed"},
- {"req.rule.adguard-dns.com.", ""},
+ {"req.rule.adguard-dns.com.", allowRule},
{"req.rule-list-id.adguard-dns.com.", ""},
}),
}, {
name: "response_result_allowed",
- ri: &agd.RequestInfo{},
+ domain: dnssvctest.DomainAllowedFQDN,
+ reqInfo: defaultReqInfo,
reqRes: nil,
- respRes: &filter.ResultAllowed{},
+ respRes: &filter.ResultAllowed{
+ Rule: allowRule,
+ },
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"resp.res-type.adguard-dns.com.", "allowed"},
- {"resp.rule.adguard-dns.com.", ""},
+ {"resp.rule.adguard-dns.com.", allowRule},
{"resp.rule-list-id.adguard-dns.com.", ""},
}),
}, {
- name: "request_result_modified",
- ri: &agd.RequestInfo{},
+ name: "request_result_modified",
+ domain: dnssvctest.DomainRewrittenFQDN,
+ reqInfo: defaultReqInfo,
reqRes: &filter.ResultModified{
- Rule: "||example.com^$dnsrewrite=REFUSED",
+ Rule: rewriteRule,
+ Msg: dnsservertest.NewReq(
+ dnssvctest.DomainRewrittenCNAMEFQDN,
+ dns.TypeA,
+ dns.ClassINET,
+ ),
},
respRes: nil,
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"req.res-type.adguard-dns.com.", "modified"},
- {"req.rule.adguard-dns.com.", "||example.com^$dnsrewrite=REFUSED"},
+ {"req.rule.adguard-dns.com.", rewriteRule},
{"req.rule-list-id.adguard-dns.com.", ""},
}),
}, {
- name: "device",
- ri: &agd.RequestInfo{Device: &agd.Device{ID: dnssvctest.DeviceID}},
+ name: "device",
+ domain: dnssvctest.DomainFQDN,
+ reqInfo: &agd.RequestInfo{
+ Messages: agdtest.NewConstructor(),
+ Device: &agd.Device{ID: dnssvctest.DeviceID},
+ },
reqRes: nil,
respRes: nil,
wantExtra: newTXTExtra([][2]string{
@@ -140,9 +175,11 @@ func TestService_writeDebugResponse(t *testing.T) {
{"resp.res-type.adguard-dns.com.", "normal"},
}),
}, {
- name: "profile",
- ri: &agd.RequestInfo{
- Profile: &agd.Profile{ID: dnssvctest.ProfileID},
+ name: "profile",
+ domain: dnssvctest.DomainFQDN,
+ reqInfo: &agd.RequestInfo{
+ Messages: agdtest.NewConstructor(),
+ Profile: &agd.Profile{ID: dnssvctest.ProfileID},
},
reqRes: nil,
respRes: nil,
@@ -153,28 +190,34 @@ func TestService_writeDebugResponse(t *testing.T) {
{"resp.res-type.adguard-dns.com.", "normal"},
}),
}, {
- name: "location",
- ri: &agd.RequestInfo{Location: &agd.Location{Country: agd.CountryAD}},
- reqRes: nil,
- respRes: nil,
- wantExtra: newTXTExtra([][2]string{
- {"client-ip.adguard-dns.com.", clientIPStr},
- {"server-ip.adguard-dns.com.", serverIPStr},
- {"country.adguard-dns.com.", string(agd.CountryAD)},
- {"asn.adguard-dns.com.", "0"},
- {"resp.res-type.adguard-dns.com.", "normal"},
- }),
- }, {
- name: "location_subdivision",
- ri: &agd.RequestInfo{
- Location: &agd.Location{Country: agd.CountryAD, TopSubdivision: "CA"},
+ name: "location",
+ domain: dnssvctest.DomainFQDN,
+ reqInfo: &agd.RequestInfo{
+ Messages: agdtest.NewConstructor(),
+ Location: &geoip.Location{Country: geoip.CountryAD},
},
reqRes: nil,
respRes: nil,
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
- {"country.adguard-dns.com.", string(agd.CountryAD)},
+ {"country.adguard-dns.com.", string(geoip.CountryAD)},
+ {"asn.adguard-dns.com.", "0"},
+ {"resp.res-type.adguard-dns.com.", "normal"},
+ }),
+ }, {
+ name: "location_subdivision",
+ domain: dnssvctest.DomainFQDN,
+ reqInfo: &agd.RequestInfo{
+ Messages: agdtest.NewConstructor(),
+ Location: &geoip.Location{Country: geoip.CountryAD, TopSubdivision: "CA"},
+ },
+ reqRes: nil,
+ respRes: nil,
+ wantExtra: newTXTExtra([][2]string{
+ {"client-ip.adguard-dns.com.", clientIPStr},
+ {"server-ip.adguard-dns.com.", serverIPStr},
+ {"country.adguard-dns.com.", string(geoip.CountryAD)},
{"asn.adguard-dns.com.", "0"},
{"subdivision.adguard-dns.com.", "CA"},
{"resp.res-type.adguard-dns.com.", "normal"},
@@ -185,12 +228,21 @@ func TestService_writeDebugResponse(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
- ctx := agd.ContextWithRequestInfo(context.Background(), tc.ri)
+ ctx := agd.ContextWithRequestInfo(context.Background(), tc.reqInfo)
- req := dnsservertest.NewReq("example.com", dns.TypeA, dns.ClassINET)
- resp := dnsservertest.NewResp(dns.RcodeSuccess, req)
+ origReq := dnsservertest.NewReq(tc.domain, dns.TypeA, dns.ClassINET)
+ origResp := dnsservertest.NewResp(dns.RcodeSuccess, origReq)
- err := svc.writeDebugResponse(ctx, rw, req, resp, tc.reqRes, tc.respRes)
+ fctx := &filteringContext{
+ originalRequest: origReq,
+ originalResponse: origResp,
+ requestResult: tc.reqRes,
+ responseResult: tc.respRes,
+ }
+
+ mw.setFilteredResponse(ctx, fctx, tc.reqInfo)
+
+ err := mw.writeDebugResponse(ctx, fctx, rw)
require.NoError(t, err)
msg := rw.Msg()
diff --git a/internal/dnssvc/internal/mainmw/error.go b/internal/dnssvc/internal/mainmw/error.go
new file mode 100644
index 0000000..d1f079a
--- /dev/null
+++ b/internal/dnssvc/internal/mainmw/error.go
@@ -0,0 +1,40 @@
+package mainmw
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
+ "github.com/AdguardTeam/golibs/errors"
+)
+
+// afterFilteringError is returned by the handler function of [Middleware.Wrap]
+// in case there is an error after filtering.
+type afterFilteringError struct {
+ err error
+}
+
+// type check
+var _ error = afterFilteringError{}
+
+// Error implements the error interface for afterFilteringError.
+func (err afterFilteringError) Error() (msg string) {
+ return fmt.Sprintf("after filtering: %s", err.err)
+}
+
+// type check
+var _ errors.Wrapper = afterFilteringError{}
+
+// Unwrap implements the [errors.Wrapper] interface for afterFilteringError.
+func (err afterFilteringError) Unwrap() (unwrapped error) {
+ return err.err
+}
+
+// type check
+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)
+}
diff --git a/internal/dnssvc/internal/mainmw/filter.go b/internal/dnssvc/internal/mainmw/filter.go
new file mode 100644
index 0000000..0053f81
--- /dev/null
+++ b/internal/dnssvc/internal/mainmw/filter.go
@@ -0,0 +1,220 @@
+package mainmw
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/miekg/dns"
+)
+
+// filteringContext contains information regarding request and response
+// filtering.
+type filteringContext struct {
+ originalRequest *dns.Msg
+ modifiedRequest *dns.Msg
+
+ originalResponse *dns.Msg
+ filteredResponse *dns.Msg
+
+ requestResult filter.Result
+ responseResult filter.Result
+
+ elapsed time.Duration
+
+ isDebug bool
+}
+
+// newFilteringContext returns a new filtering context initialized with the data
+// from req.
+func (mw *Middleware) newFilteringContext(req *dns.Msg) (fctx *filteringContext) {
+ fctx = mw.fltCtxPool.Get()
+ *fctx = filteringContext{}
+
+ isDebug := req.Question[0].Qclass == dns.ClassCHAOS
+ if isDebug {
+ req.Question[0].Qclass = dns.ClassINET
+ }
+
+ fctx.originalRequest = req
+ fctx.isDebug = isDebug
+
+ return fctx
+}
+
+// filterRequest applies f to req and sets the result of filtering in fctx. If
+// the result is the CNAME rewrite, it also sets the modified request to
+// resolve. It also adds the time elapsed on filtering. All errors are
+// reported using [Middleware.reportf].
+func (mw *Middleware) filterRequest(
+ ctx context.Context,
+ fctx *filteringContext,
+ f filter.Interface,
+ ri *agd.RequestInfo,
+) {
+ start := time.Now()
+
+ reqRes, err := f.FilterRequest(ctx, fctx.originalRequest, ri)
+ if err != nil {
+ mw.reportf(ctx, "filtering request: %w", err)
+ }
+
+ if mod, ok := reqRes.(*filter.ResultModified); ok && !mod.Msg.Response {
+ fctx.modifiedRequest = mod.Msg
+ }
+
+ fctx.requestResult = reqRes
+ fctx.elapsed += time.Since(start)
+}
+
+// filterResponse applies f to resp and sets the result of filtering in fctx.
+// If origReq has a different question name than resp, the request assumed being
+// CNAME-rewritten and no filtering performed on resp, the CNAME is prepended to
+// resp answer section instead. It also sets the time elapsed on filtering.
+// All errors are reported using [Middleware.reportf].
+func (mw *Middleware) filterResponse(
+ ctx context.Context,
+ fctx *filteringContext,
+ f filter.Interface,
+ ri *agd.RequestInfo,
+) {
+ start := time.Now()
+
+ if modReq := fctx.modifiedRequest; modReq != nil {
+ // Return the request name to its original state, since it was
+ // previously rewritten by CNAME rewrite rule.
+ origReq := fctx.originalRequest
+ origResp := fctx.originalResponse
+ origResp.Question[0] = origReq.Question[0]
+
+ // Prepend the CNAME answer to the response and don't filter it.
+ var rr dns.RR = ri.Messages.NewAnswerCNAME(origReq, modReq.Question[0].Name)
+ origResp.Answer = slices.Insert(origResp.Answer, 0, rr)
+ } else {
+ respRes, err := f.FilterResponse(ctx, fctx.originalResponse, ri)
+ if err != nil {
+ mw.reportf(ctx, "filtering response: %w", err)
+ }
+
+ fctx.responseResult = respRes
+ }
+
+ fctx.elapsed += time.Since(start)
+}
+
+// filteringData returns the data necessary for request information recording
+// from the filtering context.
+func filteringData(
+ fctx *filteringContext,
+) (id agd.FilterListID, text agd.FilterRuleText, blocked bool) {
+ if fctx.requestResult != nil {
+ return resultData(fctx.requestResult, "reqRes")
+ }
+
+ return resultData(fctx.responseResult, "respRes")
+}
+
+// resultData returns the data necessary for request information recording from
+// one filtering result. argName is used to provide better error handling.
+func resultData(
+ res filter.Result,
+ argName string,
+) (id agd.FilterListID, text agd.FilterRuleText, blocked bool) {
+ if res == nil {
+ return agd.FilterListIDNone, "", false
+ }
+
+ id, text = res.MatchedRule()
+ switch res := res.(type) {
+ case *filter.ResultAllowed:
+ blocked = false
+ case
+ *filter.ResultBlocked,
+ *filter.ResultModified:
+ blocked = true
+ default:
+ // Consider unhandled sum type members as unrecoverable programmer
+ // errors.
+ panic(&agd.ArgumentError{
+ Name: argName,
+ Message: fmt.Sprintf("unexpected type %T", res),
+ })
+ }
+
+ return id, text, blocked
+}
+
+// setFilteredResponse sets the response in fctx if the filtering results
+// require that. After calling setFilteredResponse, fctx.filteredResponse will
+// not be nil. All errors are reported using [Middleware.reportf].
+func (mw *Middleware) setFilteredResponse(
+ ctx context.Context,
+ fctx *filteringContext,
+ ri *agd.RequestInfo,
+) {
+ switch reqRes := fctx.requestResult.(type) {
+ case nil:
+ mw.setFilteredResponseNoReq(ctx, fctx, ri)
+ case *filter.ResultBlocked:
+ var err error
+ fctx.filteredResponse, err = ri.Messages.NewBlockedRespMsg(fctx.originalRequest)
+ if err != nil {
+ mw.reportf(ctx, "creating blocked resp for filtered req: %w", err)
+ fctx.filteredResponse = fctx.originalResponse
+ }
+ case *filter.ResultAllowed:
+ fctx.filteredResponse = fctx.originalResponse
+ case *filter.ResultModified:
+ if reqRes.Msg.Response {
+ // Only use the request filtering result in case it's already a
+ // response. Otherwise, it's a CNAME rewrite result, which isn't
+ // filtered after resolving.
+ fctx.filteredResponse = reqRes.Msg
+ } else {
+ fctx.filteredResponse = fctx.originalResponse
+ }
+ default:
+ // Consider unhandled sum type members as unrecoverable programmer
+ // errors.
+ panic(&agd.ArgumentError{
+ Name: "reqRes",
+ Message: fmt.Sprintf("unexpected type %T", reqRes),
+ })
+ }
+}
+
+// setFilteredResponseNoReq sets the response in fctx if the response filtering
+// results require that. After calling setFilteredResponseNoReq,
+// fctx.filteredResponse will not be nil. All errors are reported using
+// [Middleware.reportf].
+func (mw *Middleware) setFilteredResponseNoReq(
+ ctx context.Context,
+ fctx *filteringContext,
+ ri *agd.RequestInfo,
+) {
+ switch respRes := fctx.responseResult.(type) {
+ case nil, *filter.ResultAllowed:
+ fctx.filteredResponse = fctx.originalResponse
+ case *filter.ResultBlocked:
+ var err error
+ fctx.filteredResponse, err = ri.Messages.NewBlockedRespMsg(fctx.originalRequest)
+ if err != nil {
+ mw.reportf(ctx, "creating blocked resp for filtered resp: %w", err)
+ fctx.filteredResponse = fctx.originalResponse
+ }
+ case *filter.ResultModified:
+ // NOTE: Technically doesn't happen right now, since rewrites are not
+ // applied to responses.
+ fctx.filteredResponse = respRes.Msg
+ default:
+ // Consider unhandled sum type members as unrecoverable programmer
+ // errors.
+ panic(&agd.ArgumentError{
+ Name: "respRes",
+ Message: fmt.Sprintf("unexpected type %T", respRes),
+ })
+ }
+}
diff --git a/internal/dnssvc/resp_internal_test.go b/internal/dnssvc/internal/mainmw/filter_internal_test.go
similarity index 68%
rename from internal/dnssvc/resp_internal_test.go
rename to internal/dnssvc/internal/mainmw/filter_internal_test.go
index f987be7..cfe8956 100644
--- a/internal/dnssvc/resp_internal_test.go
+++ b/internal/dnssvc/internal/mainmw/filter_internal_test.go
@@ -1,14 +1,14 @@
-package dnssvc
+package mainmw
import (
"context"
+ "fmt"
"net"
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/golibs/testutil"
@@ -17,7 +17,8 @@ import (
"github.com/stretchr/testify/require"
)
-func TestWriteFilteredResp(t *testing.T) {
+// TODO(a.garipov): Rewrite into cases in external tests.
+func TestMiddleware_setFilteredResponse(t *testing.T) {
const (
respTTL = 60
)
@@ -28,10 +29,18 @@ func TestWriteFilteredResp(t *testing.T) {
blockIP := netip.IPv4Unspecified()
const domain = "example.com"
- req := dnsservertest.NewReq(domain, dns.TypeA, dns.ClassINET)
- rewrResp := dnsservertest.NewResp(dns.RcodeSuccess, req)
+ origReq := dnsservertest.NewReq(domain, dns.TypeA, dns.ClassINET)
+ rewrResp := dnsservertest.NewResp(dns.RcodeSuccess, origReq)
rewrResp.Answer = append(rewrResp.Answer, dnsservertest.NewA(domain, fltRespTTL, rewrIP))
+ mw := &Middleware{
+ errColl: &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, err error) {
+ panic(fmt.Errorf("unexpected error: %w", err))
+ },
+ },
+ }
+
testCases := []struct {
reqRes filter.Result
respRes filter.Result
@@ -82,27 +91,31 @@ func TestWriteFilteredResp(t *testing.T) {
wantTTL: fltRespTTL,
}}
- ctx := context.Background()
ri := &agd.RequestInfo{
Messages: agdtest.NewConstructor(),
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- rw := dnsserver.NewNonWriterResponseWriter(nil, nil)
- resp := dnsservertest.NewResp(dns.RcodeSuccess, req)
- resp.Answer = append(resp.Answer, dnsservertest.NewA(domain, respTTL, respIP))
+ origResp := dnsservertest.NewResp(dns.RcodeSuccess, origReq)
+ origResp.Answer = append(origResp.Answer, dnsservertest.NewA(domain, respTTL, respIP))
- written, err := writeFilteredResp(ctx, ri, rw, req, resp, tc.reqRes, tc.respRes)
- require.NoError(t, err)
- require.NotNil(t, written)
+ ctx := context.Background()
- actuallyWritten := rw.Msg()
- assert.Equal(t, written, actuallyWritten)
+ fctx := &filteringContext{
+ originalRequest: origReq,
+ originalResponse: origResp,
+ requestResult: tc.reqRes,
+ responseResult: tc.respRes,
+ }
- require.Len(t, written.Answer, 1)
+ mw.setFilteredResponse(ctx, fctx, ri)
- ans := written.Answer[0]
+ filtered := fctx.filteredResponse
+ require.NotNil(t, filtered)
+ require.Len(t, filtered.Answer, 1)
+
+ ans := filtered.Answer[0]
assert.Equal(t, tc.wantTTL, ans.Header().Ttl)
a := testutil.RequireTypeAssert[*dns.A](t, ans)
diff --git a/internal/dnssvc/internal/mainmw/mainmw.go b/internal/dnssvc/internal/mainmw/mainmw.go
new file mode 100644
index 0000000..a949be9
--- /dev/null
+++ b/internal/dnssvc/internal/mainmw/mainmw.go
@@ -0,0 +1,287 @@
+// Package mainmw contains the main middleware of AdGuard DNS. It processes
+// filtering, debug DNS API, query logging, as well as statistics.
+package mainmw
+
+import (
+ "context"
+ "strconv"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
+ "github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
+ "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
+ "github.com/AdguardTeam/AdGuardDNS/internal/querylog"
+ "github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/syncutil"
+ "github.com/miekg/dns"
+)
+
+// Middleware is the main middleware of AdGuard DNS.
+type Middleware struct {
+ messages *dnsmsg.Constructor
+ cloner *dnsmsg.Cloner
+ fltCtxPool *syncutil.Pool[filteringContext]
+ billStat billstat.Recorder
+ errColl errcoll.Interface
+ fltStrg filter.Storage
+ geoIP geoip.Interface
+ queryLog querylog.Interface
+ ruleStat rulestat.Interface
+ researchMetrics bool
+ researchLogs bool
+}
+
+// Config is the configuration structure for the main middleware. All fields
+// must be non-nil.
+type Config struct {
+ // Messages is the message constructor used to create blocked and other
+ // messages for this middleware.
+ Messages *dnsmsg.Constructor
+
+ // Cloner is used to clone messages more efficiently by disposing of parts
+ // of DNS responses for later reuse.
+ //
+ // TODO(a.garipov): Use.
+ Cloner *dnsmsg.Cloner
+
+ // BillStat is used to collect billing statistics.
+ BillStat billstat.Recorder
+
+ // ErrColl is the error collector that is used to collect critical and
+ // non-critical errors.
+ ErrColl errcoll.Interface
+
+ // FilterStorage is the storage of all filters.
+ FilterStorage filter.Storage
+
+ // GeoIP is the GeoIP database used to detect geographic data about IP
+ // addresses in requests and responses.
+ GeoIP geoip.Interface
+
+ // QueryLog is used to write the logs into.
+ QueryLog querylog.Interface
+
+ // RuleStat is used to collect statistics about matched filtering rules and
+ // rule lists.
+ RuleStat rulestat.Interface
+
+ // ResearchLogs controls whether logging of additional info for research
+ // purposes is enabled. These logs may be overly verbose and are only
+ // required temporary, that's why it's controlled by a separate setting.
+ // This setting will only be used when ResearchMetrics is also set to true.
+ ResearchLogs bool
+
+ // ResearchMetrics controls whether research metrics are enabled or not.
+ // This is a set of metrics that we may need temporary, so its collection is
+ // controlled by a separate setting.
+ ResearchMetrics bool
+}
+
+// New returns a new main middleware. c must not be nil.
+func New(c *Config) (mw *Middleware) {
+ return &Middleware{
+ messages: c.Messages,
+ cloner: c.Cloner,
+ fltCtxPool: syncutil.NewPool[filteringContext](func() (v *filteringContext) {
+ return &filteringContext{}
+ }),
+ billStat: c.BillStat,
+ errColl: c.ErrColl,
+ fltStrg: c.FilterStorage,
+ geoIP: c.GeoIP,
+ queryLog: c.QueryLog,
+ ruleStat: c.RuleStat,
+ researchMetrics: c.ResearchMetrics,
+ researchLogs: c.ResearchLogs,
+ }
+}
+
+// type check
+var _ dnsserver.Middleware = (*Middleware)(nil)
+
+// Wrap implements the [dnsserver.Middleware] interface for *Middleware
+//
+// TODO(a.garipov): Refactor and lower gocognit to 10 or below.
+func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
+ f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
+ defer func() { err = errors.Annotate(err, "main mw: %w") }()
+
+ fctx := mw.newFilteringContext(req)
+ defer mw.fltCtxPool.Put(fctx)
+
+ ri := agd.MustRequestInfoFromContext(ctx)
+ optlog.Debug2("processing request %q from %s", ri.ID, ri.RemoteIP)
+ defer optlog.Debug2("finished processing request %q from %s", ri.ID, ri.RemoteIP)
+
+ flt := mw.fltStrg.FilterFromContext(ctx, ri)
+ mw.filterRequest(ctx, fctx, flt, ri)
+
+ // Check the context error here, since the context could have already
+ // been canceled during filtering, e.g. while resolving a safe-search
+ // replacement domain.
+ err = ctx.Err()
+ if err != nil {
+ return afterFilteringError{err: err}
+ }
+
+ nwrw := internal.MakeNonWriter(rw)
+ err = next.ServeDNS(mw.nextParams(ctx, fctx, nwrw, ri))
+ if err != nil {
+ return err
+ }
+
+ fctx.originalResponse = nwrw.Msg()
+ mw.filterResponse(ctx, fctx, flt, ri)
+
+ mw.reportMetrics(fctx, ri)
+
+ mw.setFilteredResponse(ctx, fctx, ri)
+
+ if fctx.isDebug {
+ return mw.writeDebugResponse(ctx, fctx, rw)
+ }
+
+ err = rw.WriteMsg(ctx, fctx.originalRequest, fctx.filteredResponse)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return err
+ }
+
+ mw.recordQueryInfo(ctx, fctx, ri)
+
+ if fctx.filteredResponse != fctx.originalResponse {
+ mw.cloner.Dispose(fctx.originalResponse)
+ }
+
+ return nil
+ }
+
+ return dnsserver.HandlerFunc(f)
+}
+
+// nextParams is a helper that returns the parameters to call the next handler
+// with taking the filtering context into account.
+func (mw *Middleware) nextParams(
+ parent context.Context,
+ fctx *filteringContext,
+ origRW dnsserver.ResponseWriter,
+ ri *agd.RequestInfo,
+) (ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) {
+ ctx = parent
+
+ modReq := fctx.modifiedRequest
+ if modReq == nil {
+ return ctx, origRW, fctx.originalRequest
+ }
+
+ // Modified request is set only if the request was modified by a CNAME
+ // rewrite rule, so resolve the request as if it was for the rewritten name.
+ //
+ // Clone the request information and replace the host name with the
+ // rewritten one, since the request information from current context must
+ // only be accessed for reading, see [agd.RequestInfo]. Shallow copy is
+ // enough, because we only change the [agd.RequestInfo.Host] field, which is
+ // a string.
+ modReqInfo := &agd.RequestInfo{}
+ *modReqInfo = *ri
+ modReqInfo.Host = agdnet.NormalizeDomain(modReq.Question[0].Name)
+
+ ctx = agd.ContextWithRequestInfo(ctx, modReqInfo)
+
+ optlog.Debug2(
+ "mainmw: request for %q rewritten to %q by CNAME rewrite rule",
+ ri.Host,
+ modReqInfo.Host,
+ )
+
+ return ctx, origRW, modReq
+}
+
+// reportMetrics extracts filtering metrics data from the context and reports it
+// to Prometheus.
+func (mw *Middleware) reportMetrics(fctx *filteringContext, ri *agd.RequestInfo) {
+ var ctry, cont string
+ asn := "0"
+ if l := ri.Location; l != nil {
+ ctry, cont = string(l.Country), string(l.Continent)
+ asn = strconv.FormatUint(uint64(l.ASN), 10)
+ }
+
+ // Here and below stick to using WithLabelValues instead of With in order
+ // to avoid extra allocations on prometheus.Labels.
+
+ metrics.DNSSvcRequestByCountryTotal.WithLabelValues(cont, ctry).Inc()
+ metrics.DNSSvcRequestByASNTotal.WithLabelValues(ctry, asn).Inc()
+
+ id, _, isBlocked := filteringData(fctx)
+ metrics.DNSSvcRequestByFilterTotal.WithLabelValues(
+ string(id),
+ metrics.BoolString(ri.Profile == nil),
+ ).Inc()
+
+ metrics.DNSSvcFilteringDuration.Observe(fctx.elapsed.Seconds())
+ metrics.DNSSvcUsersCountUpdate(ri.RemoteIP)
+
+ if mw.researchMetrics {
+ reportResearchMetrics(ri, fctx.originalResponse, id, isBlocked, mw.researchLogs)
+ }
+}
+
+// reportResearchMetrics reports research metrics to prometheus.
+func reportResearchMetrics(
+ ri *agd.RequestInfo,
+ origResp *dns.Msg,
+ fltID agd.FilterListID,
+ isBlocked bool,
+ researchLogs bool,
+) {
+ filteringEnabled := ri.FilteringGroup != nil &&
+ ri.FilteringGroup.RuleListsEnabled &&
+ len(ri.FilteringGroup.RuleListIDs) > 0
+
+ // The current research metrics only count queries that come to public DNS
+ // servers where filtering is enabled.
+ if !filteringEnabled || ri.Profile != nil {
+ return
+ }
+
+ var ctry string
+ var subdiv string
+ if l := ri.Location; l != nil {
+ if l.ASN == 212772 {
+ // Ignore AdGuard ASN specifically in order to avoid counting
+ // queries that come from the monitoring. This part is ugly, but
+ // since these metrics are a one-time deal, this is acceptable.
+ //
+ // TODO(ameshkov): Think of a better way later if we need to do that
+ // again.
+ return
+ }
+
+ ctry = string(l.Country)
+ subdiv = l.TopSubdivision
+ }
+
+ metrics.ReportResearch(&metrics.ResearchData{
+ OriginalResponse: origResp,
+ FilterID: string(fltID),
+ Country: ctry,
+ TopSubdivision: subdiv,
+ Host: ri.Host,
+ QType: ri.QType,
+ Blocked: isBlocked,
+ }, researchLogs)
+}
+
+// reportf is a helper method for reporting non-critical errors.
+func (mw *Middleware) reportf(ctx context.Context, format string, args ...any) {
+ errcoll.Collectf(ctx, mw.errColl, "mainmw: "+format, args...)
+}
diff --git a/internal/dnssvc/internal/mainmw/mainmw_test.go b/internal/dnssvc/internal/mainmw/mainmw_test.go
new file mode 100644
index 0000000..9b29b75
--- /dev/null
+++ b/internal/dnssvc/internal/mainmw/mainmw_test.go
@@ -0,0 +1,726 @@
+package mainmw_test
+
+import (
+ "context"
+ "fmt"
+ "net/netip"
+ "testing"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/mainmw"
+ "github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/AdGuardDNS/internal/querylog"
+ "github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/miekg/dns"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
+
+// Common constants for tests.
+const (
+ testASN geoip.ASN = 12345
+ testCountry = geoip.CountryAD
+
+ testProto = dnsserver.ProtoDNS
+
+ testRespAddr4Str = "3.4.5.6"
+ testRewriteAddrStr = "7.8.9.0"
+
+ testRuleAllow agd.FilterRuleText = "@@||" + dnssvctest.DomainAllowed + "^"
+ testRuleBlockReq agd.FilterRuleText = "||" + dnssvctest.DomainBlocked + "^"
+ testRuleBlockResp agd.FilterRuleText = "||" + testRespAddr4Str + "^"
+ testRuleRewrite agd.FilterRuleText = "||" + dnssvctest.DomainRewritten +
+ "^$dnsrewrite=NOERROR;A;" + testRewriteAddrStr
+ testRuleRewriteCNAME agd.FilterRuleText = "||" + dnssvctest.DomainRewritten +
+ "^$dnsrewrite=NOERROR;CNAME;" + dnssvctest.DomainRewrittenCNAME
+)
+
+// Common variables for tests.
+var (
+ testRespAddr4 = netip.MustParseAddr(testRespAddr4Str)
+ testRespAddr6 = netip.MustParseAddr("3456::789a")
+ testRewriteAddr = netip.MustParseAddr(testRewriteAddrStr)
+
+ testDevice = &agd.Device{
+ ID: dnssvctest.DeviceID,
+ }
+
+ testProfile = &agd.Profile{
+ ID: dnssvctest.ProfileID,
+ QueryLogEnabled: true,
+ IPLogEnabled: true,
+ }
+)
+
+func TestMiddleware_Wrap(t *testing.T) {
+ t.Parallel()
+
+ reqStart := time.Now()
+ var (
+ billStatNotImp = &agdtest.BillStatRecorder{
+ OnRecord: func(
+ _ context.Context,
+ _ agd.DeviceID,
+ _ geoip.Country,
+ _ geoip.ASN,
+ _ time.Time,
+ _ agd.Protocol,
+ ) {
+ panic("not implemented")
+ },
+ }
+
+ billStatCheck = &agdtest.BillStatRecorder{
+ OnRecord: func(
+ _ context.Context,
+ devID agd.DeviceID,
+ ctry geoip.Country,
+ asn geoip.ASN,
+ start time.Time,
+ proto agd.Protocol,
+ ) {
+ pt := testutil.PanicT{}
+ checkBillStat(pt, devID, ctry, asn, start, proto, reqStart)
+ },
+ }
+ )
+
+ errColl := &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, err error) {
+ panic(fmt.Errorf("unexpected error: %v", err))
+ },
+ }
+
+ flt := &agdtest.Filter{
+ OnFilterRequest: func(
+ _ context.Context,
+ _ *dns.Msg,
+ _ *agd.RequestInfo,
+ ) (r filter.Result, err error) {
+ return nil, nil
+ },
+ OnFilterResponse: func(
+ _ context.Context,
+ _ *dns.Msg,
+ _ *agd.RequestInfo,
+ ) (r filter.Result, err error) {
+ return nil, nil
+ },
+ }
+
+ fltStrg := &agdtest.FilterStorage{
+ OnFilterFromContext: func(_ context.Context, _ *agd.RequestInfo) (f filter.Interface) {
+ return flt
+ },
+ OnHasListID: func(_ agd.FilterListID) (ok bool) { panic("not implemented") },
+ }
+
+ geoIP := &agdtest.GeoIP{
+ OnSubnetByLocation: func(
+ _ *geoip.Location,
+ _ netutil.AddrFamily,
+ ) (n netip.Prefix, err error) {
+ panic("not implemented")
+ },
+ OnData: func(host string, addr netip.Addr) (l *geoip.Location, err error) {
+ pt := testutil.PanicT{}
+ require.Equal(pt, dnssvctest.Domain, host)
+ if addr.Is4() {
+ require.Equal(pt, addr, testRespAddr4)
+ } else if addr.Is6() {
+ require.Equal(pt, addr, testRespAddr6)
+ }
+
+ return nil, nil
+ },
+ }
+
+ ruleStat := &agdtest.RuleStat{
+ OnCollect: func(_ context.Context, id agd.FilterListID, text agd.FilterRuleText) {
+ pt := testutil.PanicT{}
+ require.Equal(pt, agd.FilterListID(""), id)
+ require.Equal(pt, agd.FilterRuleText(""), text)
+ },
+ }
+
+ testCases := []struct {
+ req *dns.Msg
+ device *agd.Device
+ profile *agd.Profile
+ billStat *agdtest.BillStatRecorder
+ name string
+ wantErrMsg string
+ }{{
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeA, dns.ClassINET),
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ name: "success_ipv4",
+ wantErrMsg: "",
+ }, {
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeAAAA, dns.ClassINET),
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ name: "success_ipv6",
+ wantErrMsg: "",
+ }, {
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeA, dns.ClassCHAOS),
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ name: "debug",
+ wantErrMsg: "",
+ }, {
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeA, dns.ClassINET),
+ device: testDevice,
+ profile: testProfile,
+ billStat: billStatCheck,
+ name: "success_profile",
+ wantErrMsg: "",
+ }, {
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeAAAA, dns.ClassINET),
+ device: testDevice,
+ profile: testProfile,
+ billStat: billStatCheck,
+ name: "success_ipv6_profile",
+ wantErrMsg: "",
+ }, {
+ req: dnsservertest.NewReq(dnssvctest.DomainFQDN, dns.TypeA, dns.ClassCHAOS),
+ device: testDevice,
+ profile: testProfile,
+ billStat: billStatCheck,
+ name: "debug_profile",
+ wantErrMsg: "",
+ }}
+
+ for _, tc := range testCases {
+ tc := tc
+
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ q := tc.req.Question[0]
+ reqFQDN := q.Name
+ reqHost := agdnet.NormalizeDomain(reqFQDN)
+ reqQType := q.Qtype
+
+ queryLog := &agdtest.QueryLog{
+ OnWrite: func(_ context.Context, e *querylog.Entry) (err error) {
+ pt := testutil.PanicT{}
+ checkQueryLog(pt, e, nil, nil, reqStart, reqFQDN, reqQType)
+
+ return nil
+ },
+ }
+
+ c := &mainmw.Config{
+ Messages: agdtest.NewConstructor(),
+ Cloner: agdtest.NewCloner(),
+ BillStat: tc.billStat,
+ ErrColl: errColl,
+ FilterStorage: fltStrg,
+ GeoIP: geoIP,
+ QueryLog: queryLog,
+ RuleStat: ruleStat,
+ ResearchLogs: true,
+ ResearchMetrics: true,
+ }
+
+ mw := mainmw.New(c)
+
+ wantResp := dnsservertest.NewResp(dns.RcodeSuccess, tc.req, dnsservertest.SectionAnswer{
+ wantAns(t, reqQType),
+ })
+ h := mw.Wrap(newSimpleHandler(t, tc.req, wantResp))
+
+ ctx := newContext(tc.device, tc.profile, reqHost, reqQType, reqStart)
+ rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
+
+ err := h.ServeDNS(ctx, rw, tc.req)
+ testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+ assert.Equal(t, wantResp, rw.Msg())
+ })
+ }
+}
+
+// checkBillStat checks the billing statistics call against the common test
+// values and given parameters.
+func checkBillStat(
+ pt testutil.PanicT,
+ devID agd.DeviceID,
+ ctry geoip.Country,
+ asn geoip.ASN,
+ start time.Time,
+ proto agd.Protocol,
+ wantStart time.Time,
+) {
+ require.Equal(pt, testDevice.ID, devID)
+ require.Equal(pt, testCountry, ctry)
+ require.Equal(pt, testASN, asn)
+ require.Equal(pt, wantStart, start)
+ require.Equal(pt, testProto, proto)
+}
+
+// checkQueryLog checks the query log entry against the common test values and
+// given parameters.
+func checkQueryLog(
+ pt testutil.PanicT,
+ e *querylog.Entry,
+ wantReqRes filter.Result,
+ wantRespRes filter.Result,
+ wantStart time.Time,
+ wantFQDN string,
+ wantReqType dnsmsg.RRType,
+) {
+ require.Equal(pt, dnssvctest.ClientAddr, e.RemoteIP)
+ require.Equal(pt, wantReqRes, e.RequestResult)
+ require.Equal(pt, wantRespRes, e.ResponseResult)
+ require.Equal(pt, wantStart, e.Time)
+ require.Equal(pt, testDevice.ID, e.DeviceID)
+ require.Equal(pt, testProfile.ID, e.ProfileID)
+ require.Equal(pt, testCountry, e.ClientCountry)
+ require.Equal(pt, wantFQDN, e.DomainFQDN)
+ require.Equal(pt, testASN, e.ClientASN)
+
+ // Don't check that e.Elapsed is greater than zero, because most of
+ // the time it is zero in the tests.
+
+ require.Equal(pt, wantReqType, e.RequestType)
+ require.Equal(pt, testProto, e.Protocol)
+ require.False(pt, e.DNSSEC)
+ require.Equal(pt, dnsmsg.RCode(dns.RcodeSuccess), e.ResponseCode)
+}
+
+// wantAns is a helper that returns the expected address answer based on the
+// question type.
+func wantAns(t testing.TB, qtype dnsmsg.RRType) (ans dns.RR) {
+ t.Helper()
+
+ const fqdn = dnssvctest.DomainFQDN
+ switch qtype {
+ case dns.TypeA:
+ return dnsservertest.NewA(fqdn, agdtest.FilteredResponseTTLSec, testRespAddr4)
+ case dns.TypeAAAA:
+ return dnsservertest.NewAAAA(fqdn, agdtest.FilteredResponseTTLSec, testRespAddr6)
+ default:
+ t.Fatalf("bad qtype: %v", qtype)
+
+ // Never reached.
+ return nil
+ }
+}
+
+// newContext returns a new context with the given data, the common test values
+// for location and protocol, as well as an enabled filtering-group with the
+// standard list IDs.
+func newContext(
+ d *agd.Device,
+ p *agd.Profile,
+ host string,
+ qType dnsmsg.RRType,
+ start time.Time,
+) (ctx context.Context) {
+ ctx = context.Background()
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: start,
+ })
+ ctx = agd.ContextWithRequestInfo(ctx, &agd.RequestInfo{
+ Device: d,
+ Profile: p,
+ Location: &geoip.Location{
+ Country: testCountry,
+ ASN: testASN,
+ },
+ FilteringGroup: &agd.FilteringGroup{
+ RuleListIDs: []agd.FilterListID{
+ dnssvctest.FilterListID1,
+ dnssvctest.FilterListID2,
+ },
+ RuleListsEnabled: true,
+ },
+ Messages: agdtest.NewConstructor(),
+ RemoteIP: dnssvctest.ClientAddr,
+ Host: host,
+ QType: qType,
+ Proto: testProto,
+ })
+
+ return ctx
+}
+
+// newSimpleHandler returns a simple handler that checks whether a request is
+// performed correctly and returns the given response.
+func newSimpleHandler(t testing.TB, wantReq, resp *dns.Msg) (h dnsserver.Handler) {
+ f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
+ require.Equal(t, wantReq, req)
+
+ return rw.WriteMsg(ctx, req, resp)
+ }
+
+ return dnsserver.HandlerFunc(f)
+}
+
+func TestMiddleware_Wrap_filtering(t *testing.T) {
+ t.Parallel()
+
+ reqStart := time.Now()
+ var (
+ billStatNotImp = &agdtest.BillStatRecorder{
+ OnRecord: func(
+ _ context.Context,
+ _ agd.DeviceID,
+ _ geoip.Country,
+ _ geoip.ASN,
+ _ time.Time,
+ _ agd.Protocol,
+ ) {
+ panic("not implemented")
+ },
+ }
+
+ billStatCheck = &agdtest.BillStatRecorder{
+ OnRecord: func(
+ _ context.Context,
+ devID agd.DeviceID,
+ ctry geoip.Country,
+ asn geoip.ASN,
+ start time.Time,
+ proto agd.Protocol,
+ ) {
+ pt := testutil.PanicT{}
+ checkBillStat(pt, devID, ctry, asn, start, proto, reqStart)
+ },
+ }
+ )
+
+ errColl := &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, err error) {
+ panic(fmt.Errorf("unexpected error: %v", err))
+ },
+ }
+
+ geoIP := &agdtest.GeoIP{
+ OnSubnetByLocation: func(
+ _ *geoip.Location,
+ _ netutil.AddrFamily,
+ ) (n netip.Prefix, err error) {
+ panic("not implemented")
+ },
+ OnData: func(host string, addr netip.Addr) (l *geoip.Location, err error) {
+ return nil, nil
+ },
+ }
+
+ var (
+ reqAllow = dnsservertest.NewReq(
+ dnssvctest.DomainAllowedFQDN,
+ dns.TypeA,
+ dns.ClassINET,
+ )
+
+ reqBlock = dnsservertest.NewReq(
+ dnssvctest.DomainBlockedFQDN,
+ dns.TypeA,
+ dns.ClassINET,
+ )
+
+ reqRewrite = dnsservertest.NewReq(
+ dnssvctest.DomainRewrittenFQDN,
+ dns.TypeA,
+ dns.ClassINET,
+ )
+
+ reqRewriteCNAME = dnsservertest.NewReq(
+ dnssvctest.DomainRewrittenCNAMEFQDN,
+ dns.TypeA,
+ dns.ClassINET,
+ )
+ )
+
+ var (
+ respAllow = dnsservertest.NewResp(dns.RcodeSuccess, reqAllow, dnsservertest.SectionAnswer{
+ wantAns(t, dns.TypeA),
+ })
+
+ respBlock = dnsservertest.NewResp(dns.RcodeSuccess, reqBlock, dnsservertest.SectionAnswer{
+ dnsservertest.NewA(
+ dnssvctest.DomainBlockedFQDN,
+ agdtest.FilteredResponseTTLSec,
+ netip.IPv4Unspecified(),
+ ),
+ })
+
+ respRewrite = dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ reqRewrite,
+ dnsservertest.SectionAnswer{
+ dnsservertest.NewA(
+ dnssvctest.DomainRewrittenFQDN,
+ agdtest.FilteredResponseTTLSec,
+ testRewriteAddr,
+ ),
+ },
+ )
+
+ respRewriteCNAMEUps = dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ reqRewrite,
+ dnsservertest.SectionAnswer{
+ dnsservertest.NewA(
+ dnssvctest.DomainRewrittenCNAMEFQDN,
+ agdtest.FilteredResponseTTLSec,
+ testRewriteAddr,
+ ),
+ },
+ )
+
+ respRewriteCNAME = dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ reqRewrite,
+ dnsservertest.SectionAnswer{
+ dnsservertest.NewCNAME(
+ dnssvctest.DomainRewrittenFQDN,
+ agdtest.FilteredResponseTTLSec,
+ dnssvctest.DomainRewrittenCNAMEFQDN,
+ ),
+ dnsservertest.NewA(
+ dnssvctest.DomainRewrittenCNAMEFQDN,
+ agdtest.FilteredResponseTTLSec,
+ testRewriteAddr,
+ ),
+ },
+ )
+ )
+
+ var (
+ resReqAllow = &filter.ResultAllowed{
+ List: dnssvctest.FilterListID1,
+ Rule: testRuleAllow,
+ }
+
+ resReqBlock = &filter.ResultBlocked{
+ List: dnssvctest.FilterListID1,
+ Rule: testRuleBlockReq,
+ }
+
+ resReqRewrite = &filter.ResultModified{
+ List: dnssvctest.FilterListID1,
+ Rule: testRuleRewrite,
+ Msg: respRewrite,
+ }
+
+ resReqRewriteCNAME = &filter.ResultModified{
+ List: dnssvctest.FilterListID1,
+ Rule: testRuleRewriteCNAME,
+ Msg: reqRewriteCNAME,
+ }
+
+ resRespBlock = &filter.ResultBlocked{
+ List: dnssvctest.FilterListID1,
+ Rule: testRuleBlockResp,
+ }
+ )
+
+ testCases := []struct {
+ req *dns.Msg
+ device *agd.Device
+ profile *agd.Profile
+ billStat *agdtest.BillStatRecorder
+ wantResp *dns.Msg
+ wantUpsReq *dns.Msg
+ upsResp *dns.Msg
+ reqRes filter.Result
+ respRes filter.Result
+ name string
+ wantErrMsg string
+ wantRule agd.FilterRuleText
+ }{{
+ req: reqAllow,
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ reqRes: resReqAllow,
+ respRes: nil,
+ wantResp: respAllow,
+ wantUpsReq: reqAllow,
+ upsResp: respAllow,
+ name: "success_allowed",
+ wantErrMsg: "",
+ wantRule: testRuleAllow,
+ }, {
+ req: reqBlock,
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ reqRes: resReqBlock,
+ respRes: nil,
+ wantResp: respBlock,
+ wantUpsReq: reqBlock,
+ upsResp: respAllow,
+ name: "success_blocked",
+ wantErrMsg: "",
+ wantRule: testRuleBlockReq,
+ }, {
+ req: reqRewrite,
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ reqRes: resReqRewriteCNAME,
+ respRes: nil,
+ wantResp: respRewriteCNAME,
+ wantUpsReq: reqRewriteCNAME,
+ upsResp: respRewriteCNAMEUps,
+ name: "success_rewritten_req",
+ wantErrMsg: "",
+ wantRule: testRuleRewriteCNAME,
+ }, {
+ req: reqRewrite,
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ reqRes: resReqRewrite,
+ respRes: nil,
+ wantResp: respRewrite,
+ wantUpsReq: reqRewrite,
+ upsResp: respAllow,
+ name: "success_rewritten_resp",
+ wantErrMsg: "",
+ wantRule: testRuleRewrite,
+ }, {
+ req: reqAllow,
+ device: testDevice,
+ profile: testProfile,
+ billStat: billStatCheck,
+ reqRes: resReqAllow,
+ respRes: nil,
+ wantResp: respAllow,
+ wantUpsReq: reqAllow,
+ upsResp: respAllow,
+ name: "success_profile_allowed",
+ wantErrMsg: "",
+ wantRule: testRuleAllow,
+ }, {
+ req: reqBlock,
+ device: testDevice,
+ profile: testProfile,
+ billStat: billStatCheck,
+ reqRes: resReqBlock,
+ respRes: nil,
+ wantResp: respBlock,
+ wantUpsReq: reqBlock,
+ upsResp: respAllow,
+ name: "success_profile_blocked",
+ wantErrMsg: "",
+ wantRule: testRuleBlockReq,
+ }, {
+ req: reqBlock,
+ device: nil,
+ profile: nil,
+ billStat: billStatNotImp,
+ reqRes: nil,
+ respRes: resRespBlock,
+ wantResp: respBlock,
+ wantUpsReq: reqBlock,
+ upsResp: respAllow,
+ name: "success_blocked_resp",
+ wantErrMsg: "",
+ wantRule: testRuleBlockResp,
+ }}
+
+ for _, tc := range testCases {
+ tc := tc
+
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ flt := &agdtest.Filter{
+ OnFilterRequest: func(
+ _ context.Context,
+ _ *dns.Msg,
+ _ *agd.RequestInfo,
+ ) (r filter.Result, err error) {
+ return tc.reqRes, nil
+ },
+ OnFilterResponse: func(
+ _ context.Context,
+ _ *dns.Msg,
+ _ *agd.RequestInfo,
+ ) (r filter.Result, err error) {
+ return tc.respRes, nil
+ },
+ }
+
+ fltStrg := &agdtest.FilterStorage{
+ OnFilterFromContext: func(
+ _ context.Context,
+ _ *agd.RequestInfo,
+ ) (f filter.Interface) {
+ return flt
+ },
+ OnHasListID: func(_ agd.FilterListID) (ok bool) { panic("not implemented") },
+ }
+
+ q := tc.req.Question[0]
+ reqFQDN := q.Name
+ reqHost := agdnet.NormalizeDomain(reqFQDN)
+ reqQType := q.Qtype
+
+ queryLog := &agdtest.QueryLog{
+ OnWrite: func(_ context.Context, e *querylog.Entry) (err error) {
+ pt := testutil.PanicT{}
+ checkQueryLog(pt, e, tc.reqRes, tc.respRes, reqStart, reqFQDN, reqQType)
+
+ return nil
+ },
+ }
+
+ ruleStat := &agdtest.RuleStat{
+ OnCollect: func(_ context.Context, id agd.FilterListID, text agd.FilterRuleText) {
+ pt := testutil.PanicT{}
+ require.Equal(pt, dnssvctest.FilterListID1, id)
+ require.Equal(pt, tc.wantRule, text)
+ },
+ }
+
+ c := &mainmw.Config{
+ Messages: agdtest.NewConstructor(),
+ Cloner: agdtest.NewCloner(),
+ BillStat: tc.billStat,
+ ErrColl: errColl,
+ FilterStorage: fltStrg,
+ GeoIP: geoIP,
+ QueryLog: queryLog,
+ RuleStat: ruleStat,
+ ResearchLogs: true,
+ ResearchMetrics: true,
+ }
+
+ mw := mainmw.New(c)
+
+ h := mw.Wrap(newSimpleHandler(t, tc.wantUpsReq, tc.upsResp))
+
+ ctx := newContext(tc.device, tc.profile, reqHost, reqQType, reqStart)
+ rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
+
+ err := h.ServeDNS(ctx, rw, tc.req)
+ testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+ assert.Equal(t, tc.wantResp, rw.Msg())
+ })
+ }
+}
diff --git a/internal/dnssvc/internal/mainmw/record.go b/internal/dnssvc/internal/mainmw/record.go
new file mode 100644
index 0000000..7d9ab70
--- /dev/null
+++ b/internal/dnssvc/internal/mainmw/record.go
@@ -0,0 +1,177 @@
+package mainmw
+
+import (
+ "context"
+ "net"
+ "net/netip"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/AdGuardDNS/internal/querylog"
+ "github.com/AdguardTeam/golibs/netutil"
+ "github.com/miekg/dns"
+)
+
+// recordQueryInfo extracts loggable information from request, response, and
+// filtering data and writes them to the query log, billing, and filtering-rule
+// statistics, handling non-critical errors.
+func (mw *Middleware) recordQueryInfo(
+ ctx context.Context,
+ fctx *filteringContext,
+ ri *agd.RequestInfo,
+) {
+ id, text, blocked := filteringData(fctx)
+ mw.ruleStat.Collect(ctx, id, text)
+
+ prof := ri.Profile
+ if prof == nil {
+ return
+ }
+
+ var devID agd.DeviceID
+ if d := ri.Device; d != nil {
+ devID = d.ID
+ }
+
+ var reqCtry geoip.Country
+ var reqASN geoip.ASN
+ if g := ri.Location; g != nil {
+ reqCtry, reqASN = g.Country, g.ASN
+ }
+
+ reqInfo := dnsserver.MustRequestInfoFromContext(ctx)
+ start := reqInfo.StartTime
+ mw.billStat.Record(ctx, devID, reqCtry, reqASN, start, ri.Proto)
+
+ if !prof.QueryLogEnabled {
+ return
+ }
+
+ rcode, respIP, respDNSSEC := mw.responseData(ctx, fctx.filteredResponse)
+ if blocked {
+ // If the request or the response were blocked, resp may contain an
+ // unspecified IP address, a rewritten IP address, or none at all, while
+ // the original response may contain an actual IP address that should be
+ // used to determine the response country.
+ _, respIP, _ = mw.responseData(ctx, fctx.originalResponse)
+ }
+
+ var clientIP netip.Addr
+ if prof.IPLogEnabled {
+ clientIP = ri.RemoteIP
+ }
+
+ q := fctx.originalRequest.Question[0]
+ e := &querylog.Entry{
+ RequestResult: fctx.requestResult,
+ ResponseResult: fctx.responseResult,
+ Time: start,
+ RequestID: ri.ID,
+ ProfileID: prof.ID,
+ DeviceID: devID,
+ ClientCountry: reqCtry,
+ ResponseCountry: mw.responseCountry(ctx, fctx, ri, respIP),
+ DomainFQDN: q.Name,
+ ClientASN: reqASN,
+ Elapsed: uint16(time.Since(start).Milliseconds()),
+ RequestType: ri.QType,
+ Protocol: ri.Proto,
+ DNSSEC: respDNSSEC,
+ ResponseCode: rcode,
+ RemoteIP: clientIP,
+ }
+
+ err := mw.queryLog.Write(ctx, e)
+ if err != nil {
+ // Consider query logging errors non-critical.
+ mw.reportf(ctx, "writing query log: %w", err)
+ }
+}
+
+// responseCountry returns the country of the response IP address based on the
+// request and filtering data.
+func (mw *Middleware) responseCountry(
+ ctx context.Context,
+ fctx *filteringContext,
+ ri *agd.RequestInfo,
+ respIP netip.Addr,
+) (ctry geoip.Country) {
+ if respIP == (netip.Addr{}) || respIP.IsUnspecified() {
+ return geoip.CountryNone
+ }
+
+ host := ri.Host
+ if modReq := fctx.modifiedRequest; modReq != nil {
+ // If the request was modified by CNAME rule, the actual result
+ // belongs to the hostname from that CNAME.
+ host = agdnet.NormalizeDomain(modReq.Question[0].Name)
+ }
+
+ return mw.country(ctx, host, respIP)
+}
+
+// responseData is a helper that returns the response code, the first IP
+// address, and the DNSSEC AD flag from the DNS query response if the answer has
+// the type of A or AAAA or an empty IP address otherwise.
+//
+// If resp is nil or contains invalid data, it returns 0xff (an unassigned
+// RCODE), net.Addr{}, and false. It reports all errors using
+// [Middleware.reportf].
+func (mw *Middleware) responseData(
+ ctx context.Context,
+ resp *dns.Msg,
+) (rcode dnsmsg.RCode, ip netip.Addr, dnssec bool) {
+ if resp == nil {
+ return 0xff, netip.Addr{}, false
+ }
+
+ var rrType dns.Type
+ var fam netutil.AddrFamily
+ var netIP net.IP
+ dnssec = resp.AuthenticatedData
+ rcode = dnsmsg.RCode(resp.Rcode)
+ for _, rr := range resp.Answer {
+ switch v := rr.(type) {
+ case *dns.A:
+ fam = netutil.AddrFamilyIPv4
+ rrType, netIP = dns.Type(v.Hdr.Rrtype), v.A
+ case *dns.AAAA:
+ fam = netutil.AddrFamilyIPv6
+ rrType, netIP = dns.Type(v.Hdr.Rrtype), v.AAAA
+ default:
+ continue
+ }
+
+ break
+ }
+
+ if netIP != nil {
+ var err error
+ ip, err = netutil.IPToAddr(netIP, fam)
+ if err != nil {
+ mw.reportf(ctx, "converting %s resp data: %w", rrType, err)
+ }
+ }
+
+ return rcode, ip, dnssec
+}
+
+// country is a wrapper around the GeoIP call that contains the handling of
+// non-critical GeoIP errors.
+func (mw *Middleware) country(ctx context.Context, host string, ip netip.Addr) (c geoip.Country) {
+ l, err := mw.geoIP.Data(host, ip)
+ if err != nil {
+ // Consider GeoIP errors non-critical.
+ mw.reportf(ctx, "getting geoip data: %w", err)
+ }
+
+ if l != nil {
+ return l.Country
+ }
+
+ return geoip.CountryNone
+}
diff --git a/internal/dnssvc/internal/preservice/preservice.go b/internal/dnssvc/internal/preservice/preservice.go
new file mode 100644
index 0000000..4864e5d
--- /dev/null
+++ b/internal/dnssvc/internal/preservice/preservice.go
@@ -0,0 +1,129 @@
+// Package preservice contains the middleware that comes right before the main
+// filtering middleware of DNS service.
+package preservice
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/miekg/dns"
+)
+
+// Middleware is a middleware that comes right before the main filtering
+// middleware of DNS service. It includes handling of TXT queries for domain
+// names that may be filtered by safe browsing or parental control filters as
+// well as handling of the DNS-server check queries.
+type Middleware struct {
+ // messages is used to construct TXT responses.
+ messages *dnsmsg.Constructor
+
+ // hashMatcher is the safe browsing DNS hashMatcher.
+ hashMatcher filter.HashMatcher
+
+ // checker is used to detect and process DNS-check requests.
+ checker dnscheck.Interface
+}
+
+// Config is the configurational structure for the preservice middleware. All
+// fields must be non-nil.
+type Config struct {
+ // Messages is used to construct TXT responses.
+ Messages *dnsmsg.Constructor
+
+ // HashMatcher is the safe browsing DNS hashMatcher.
+ HashMatcher filter.HashMatcher
+
+ // Checker is used to detect and process DNS-check requests.
+ Checker dnscheck.Interface
+}
+
+// New returns a new preservice middleware. c must not be nil.
+func New(c *Config) (mw *Middleware) {
+ return &Middleware{
+ messages: c.Messages,
+ hashMatcher: c.HashMatcher,
+ checker: c.Checker,
+ }
+}
+
+// type check
+var _ dnsserver.Middleware = (*Middleware)(nil)
+
+// Wrap implements the [dnsserver.Middleware] interface for *Middleware.
+func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
+ f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
+ defer func() { err = errors.Annotate(err, "preservice mw: %w") }()
+
+ ri := agd.MustRequestInfoFromContext(ctx)
+ if ri.QType == dns.TypeTXT {
+ // Don't wrap the error, because it's informative enough as is.
+ return mw.respondWithHashes(ctx, next, rw, req, ri)
+ }
+
+ resp, err := mw.checker.Check(ctx, req, ri)
+ if err != nil {
+ return fmt.Errorf("calling dnscheck: %w", err)
+ } else if resp != nil {
+ return errors.Annotate(rw.WriteMsg(ctx, req, resp), "writing dnscheck response: %w")
+ }
+
+ // Don't wrap the error, because this is the main flow, and there is
+ // already [errors.Annotate] here.
+ return next.ServeDNS(ctx, rw, req)
+ }
+
+ return dnsserver.HandlerFunc(f)
+}
+
+// respondWithHashes collects the hashes that match the given hash-prefix query
+// and writes a response with them.
+func (mw *Middleware) respondWithHashes(
+ ctx context.Context,
+ next dnsserver.Handler,
+ rw dnsserver.ResponseWriter,
+ req *dns.Msg,
+ ri *agd.RequestInfo,
+) (err error) {
+ optlog.Debug1("preservice mw: safe browsing: got txt req for %q", ri.Host)
+
+ hashes, matched, err := mw.hashMatcher.MatchByPrefix(ctx, ri.Host)
+ if err != nil {
+ // Don't return or collect this error to prevent DDoS of the error
+ // collector by sending bad requests.
+ log.Error("preservice mw: safe browsing: matching hashes: %s", err)
+
+ resp := mw.messages.NewMsgREFUSED(req)
+ err = rw.WriteMsg(ctx, req, resp)
+
+ return errors.Annotate(err, "writing refused response: %w")
+ } else if !matched {
+ // Don't wrap the error, because this is the main flow, and there is
+ // already [errors.Annotate] here.
+ return next.ServeDNS(ctx, rw, req)
+ }
+
+ resp, err := mw.messages.NewTXTRespMsg(req, hashes...)
+ if err != nil {
+ // Technically should never happen since the only error that could arise
+ // in [dnsmsg.Constructor.NewTXTRespMsg] is the one about request type
+ // mismatch.
+ return fmt.Errorf("creating safe browsing result: %w", err)
+ }
+
+ optlog.Debug1("preservice mw: safe browsing: writing hashes %q", hashes)
+
+ err = rw.WriteMsg(ctx, req, resp)
+ if err != nil {
+ return fmt.Errorf("writing safe browsing response: %w", err)
+ }
+
+ return nil
+}
diff --git a/internal/dnssvc/presvcmw_internal_test.go b/internal/dnssvc/internal/preservice/preservice_test.go
similarity index 69%
rename from internal/dnssvc/presvcmw_internal_test.go
rename to internal/dnssvc/internal/preservice/preservice_test.go
index 7b59e57..34d0d8f 100644
--- a/internal/dnssvc/presvcmw_internal_test.go
+++ b/internal/dnssvc/internal/preservice/preservice_test.go
@@ -1,4 +1,4 @@
-package dnssvc
+package preservice_test
import (
"context"
@@ -13,30 +13,40 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/preservice"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
+ "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
+
func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
const safeBrowsingHost = "scam.example.net."
var (
- ip = netutil.IPv4Localhost()
- name = "example.com"
+ ip = netutil.IPv4Localhost()
+ name = "example.com"
+ badHash = "bad.hash"
)
sum := sha256.Sum256([]byte(safeBrowsingHost))
hashStr := hex.EncodeToString(sum[:])
host := hashStr[:hashprefix.PrefixEncLen] + filter.GeneralTXTSuffix
+ // Set the context necessary for [dnsservertest.DefaultHandler].
ctx := context.Background()
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{})
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{})
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
+ ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{
+ StartTime: time.Now(),
+ })
+ ctx = dnsserver.ContextWithServerInfo(ctx, &dnsserver.ServerInfo{})
const ttl = 60
@@ -47,6 +57,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
ri *agd.RequestInfo
hashes []string
wantAns []dns.RR
+ wantRCode dnsmsg.RCode
}{{
name: "normal",
req: dnsservertest.CreateMessage(name, dns.TypeA),
@@ -56,6 +67,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
wantAns: []dns.RR{
dnsservertest.NewA(name, 100, ip),
},
+ wantRCode: dns.RcodeSuccess,
}, {
name: "dnscheck",
req: dnsservertest.CreateMessage(name, dns.TypeA),
@@ -73,6 +85,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
wantAns: []dns.RR{
dnsservertest.NewA(name, ttl, ip),
},
+ wantRCode: dns.RcodeSuccess,
}, {
name: "with_hashes",
req: dnsservertest.CreateMessage(safeBrowsingHost, dns.TypeTXT),
@@ -88,6 +101,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
},
Txt: []string{hashStr},
}},
+ wantRCode: dns.RcodeSuccess,
}, {
name: "not_matched",
req: dnsservertest.CreateMessage(name, dns.TypeTXT),
@@ -95,6 +109,15 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
ri: &agd.RequestInfo{Host: name, QType: dns.TypeTXT},
hashes: nil,
wantAns: []dns.RR{dnsservertest.NewA(name, 100, ip)},
+ wantRCode: dns.RcodeSuccess,
+ }, {
+ name: "bad_hash",
+ req: dnsservertest.CreateMessage(name, dns.TypeTXT),
+ dnscheckResp: nil,
+ ri: &agd.RequestInfo{Host: badHash, QType: dns.TypeTXT},
+ hashes: nil,
+ wantAns: nil,
+ wantRCode: dns.RcodeRefused,
}}
for _, tc := range testCases {
@@ -104,9 +127,9 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
dnsCk := &agdtest.DNSCheck{
OnCheck: func(
- ctx context.Context,
- msg *dns.Msg,
- ri *agd.RequestInfo,
+ _ context.Context,
+ _ *dns.Msg,
+ _ *agd.RequestInfo,
) (resp *dns.Msg, err error) {
return tc.dnscheckResp, nil
},
@@ -114,18 +137,26 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
hashMatcher := &agdtest.HashMatcher{
OnMatchByPrefix: func(
- ctx context.Context,
+ _ context.Context,
host string,
) (hashes []string, matched bool, err error) {
+ if host == badHash {
+ return nil, false, errors.Error("bad hash")
+ }
+
return tc.hashes, len(tc.hashes) > 0, nil
},
}
- mw := &preServiceMw{
- messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
- hashMatcher: hashMatcher,
- checker: dnsCk,
- }
+ mw := preservice.New(&preservice.Config{
+ Messages: dnsmsg.NewConstructor(
+ nil,
+ &dnsmsg.BlockingModeNullIP{},
+ ttl*time.Second,
+ ),
+ HashMatcher: hashMatcher,
+ Checker: dnsCk,
+ })
handler := dnsservertest.DefaultHandler()
h := mw.Wrap(handler)
@@ -136,6 +167,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
require.NotNil(t, msg)
assert.Equal(t, tc.wantAns, msg.Answer)
+ assert.Equal(t, tc.wantRCode, dnsmsg.RCode(msg.Rcode))
})
}
}
diff --git a/internal/dnssvc/internal/preupstream/preupstream.go b/internal/dnssvc/internal/preupstream/preupstream.go
new file mode 100644
index 0000000..27cd1bc
--- /dev/null
+++ b/internal/dnssvc/internal/preupstream/preupstream.go
@@ -0,0 +1,205 @@
+// Package preupstream contains the middleware that prepares records for
+// upstream handling and caches them, as well as records anonymous DNS
+// statistics.
+package preupstream
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
+ "github.com/AdguardTeam/AdGuardDNS/internal/ecscache"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/log"
+ "github.com/miekg/dns"
+)
+
+// Middleware is a middleware that prepares records for caching and upstream
+// handling as well as records anonymous DNS statistics.
+type Middleware struct {
+ cloner *dnsmsg.Cloner
+ db dnsdb.Interface
+ geoIP geoip.Interface
+ cacheMinTTL time.Duration
+ cacheSize int
+ ecsCacheSize int
+ useECSCache bool
+ useCacheTTLOverride bool
+}
+
+// Config is the configurational structure for the preupstream middleware. DB
+// must not be nil.
+type Config struct {
+ // Cloner is used to clone messages taken from cache.
+ Cloner *dnsmsg.Cloner
+
+ // DB is used to update anonymous statistics about DNS queries.
+ DB dnsdb.Interface
+
+ // GeoIP is the GeoIP database used to detect geographic data about IP
+ // addresses in requests and responses.
+ GeoIP geoip.Interface
+
+ // CacheMinTTL is the minimum supported TTL for cache items.
+ CacheMinTTL time.Duration
+
+ // CacheSize is the size of the DNS cache for domain names that don't
+ // support ECS.
+ CacheSize int
+
+ // ECSCacheSize is the size of the DNS cache for domain names that support
+ // ECS.
+ ECSCacheSize int
+
+ // UseECSCache shows if the EDNS Client Subnet (ECS) aware cache should be
+ // used.
+ UseECSCache bool
+
+ // UseCacheTTLOverride shows if the TTL overrides logic should be used.
+ UseCacheTTLOverride bool
+}
+
+// New returns a new preupstream middleware. c must not be nil.
+func New(c *Config) (mw *Middleware) {
+ return &Middleware{
+ cloner: c.Cloner,
+ db: c.DB,
+ geoIP: c.GeoIP,
+ cacheMinTTL: c.CacheMinTTL,
+ cacheSize: c.CacheSize,
+ ecsCacheSize: c.ECSCacheSize,
+ useECSCache: c.UseECSCache,
+ useCacheTTLOverride: c.UseCacheTTLOverride,
+ }
+}
+
+// type check
+var _ dnsserver.Middleware = (*Middleware)(nil)
+
+// Wrap implements the [dnsserver.Middleware] interface for *Middleware.
+func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
+ next = mw.wrapCacheMw(next)
+
+ f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
+ defer func() { err = errors.Annotate(err, "preupstream mw: %w") }()
+
+ if rn := agdnet.AndroidMetricDomainReplacement(req.Question[0].Name); rn != "" {
+ // Don't wrap the error, because it's informative enough as is.
+ return mw.serveAndroidMetric(ctx, next, rw, req, rn)
+ }
+
+ nwrw := internal.MakeNonWriter(rw)
+ err = next.ServeDNS(ctx, nwrw, req)
+ if err != nil {
+ // Don't wrap the error, because this is the main flow, and there is
+ // already errors.Annotate here.
+ return err
+ }
+
+ resp := nwrw.Msg()
+ ri := agd.MustRequestInfoFromContext(ctx)
+ mw.db.Record(ctx, resp, ri)
+
+ err = rw.WriteMsg(ctx, req, resp)
+ if err != nil {
+ return fmt.Errorf("writing response: %w", err)
+ }
+
+ return nil
+ }
+
+ return dnsserver.HandlerFunc(f)
+}
+
+// wrapCacheMw does nothing if cacheSize is zero otherwise returns wrapped
+// handler with caching middleware which is ECS-aware or not.
+//
+// TODO(s.chzhen): Consider separating caching middleware.
+func (mw *Middleware) wrapCacheMw(next dnsserver.Handler) (wrapped dnsserver.Handler) {
+ log.Info("cache: size: %d, ecs: %t", mw.cacheSize, mw.useECSCache)
+
+ if mw.cacheSize == 0 {
+ return next
+ }
+
+ var cacheMw dnsserver.Middleware
+ if mw.useECSCache {
+ cacheMw = ecscache.NewMiddleware(&ecscache.MiddlewareConfig{
+ Cloner: mw.cloner,
+ GeoIP: mw.geoIP,
+ Size: mw.cacheSize,
+ ECSSize: mw.ecsCacheSize,
+ MinTTL: mw.cacheMinTTL,
+ UseTTLOverride: mw.useCacheTTLOverride,
+ })
+ } else {
+ cacheMw = cache.NewMiddleware(&cache.MiddlewareConfig{
+ MetricsListener: &prometheus.CacheMetricsListener{},
+ Size: mw.cacheSize,
+ MinTTL: mw.cacheMinTTL,
+ UseTTLOverride: mw.useCacheTTLOverride,
+ })
+ }
+
+ return cacheMw.Wrap(next)
+}
+
+// serveAndroidMetric makes sure we avoid resolving random Android DoT, DoH
+// metric domains. replName is the replacement domain name to use to improve
+// caching of these metric domains.
+func (mw *Middleware) serveAndroidMetric(
+ ctx context.Context,
+ h dnsserver.Handler,
+ rw dnsserver.ResponseWriter,
+ origReq *dns.Msg,
+ replName string,
+) (err error) {
+ defer func() { err = errors.Annotate(err, "android metrics: %w") }()
+
+ req := dnsmsg.Clone(origReq)
+ req.Question[0].Name = replName
+
+ nwrw := internal.MakeNonWriter(rw)
+ err = h.ServeDNS(ctx, nwrw, req)
+ if err != nil {
+ // Don't wrap the error, because this is the main flow, and there is
+ // already errors.Annotate here.
+ return err
+ }
+
+ resp := nwrw.Msg()
+ resp.SetReply(origReq)
+ mw.replaceResp(origReq.Question[0].Name, resp)
+
+ err = rw.WriteMsg(ctx, origReq, resp)
+ if err != nil {
+ return fmt.Errorf("writing response: %w", err)
+ }
+
+ return nil
+}
+
+// replaceResp replaces the name of the answers in resp with name. This is
+// required since all Android metrics requests are cached as one.
+func (mw *Middleware) replaceResp(name string, resp *dns.Msg) {
+ if len(resp.Answer) == 0 {
+ return
+ }
+
+ // TODO(a.garipov): Add Ns and Extra handling as well?
+ for _, a := range resp.Answer {
+ h := a.Header()
+ if agdnet.AndroidMetricDomainReplacement(h.Name) != "" {
+ h.Name = name
+ }
+ }
+}
diff --git a/internal/dnssvc/preupstreammw_internal_test.go b/internal/dnssvc/internal/preupstream/preupstream_test.go
similarity index 78%
rename from internal/dnssvc/preupstreammw_internal_test.go
rename to internal/dnssvc/internal/preupstream/preupstream_test.go
index 26fc3c3..9880a8c 100644
--- a/internal/dnssvc/preupstreammw_internal_test.go
+++ b/internal/dnssvc/internal/preupstream/preupstream_test.go
@@ -1,11 +1,10 @@
-package dnssvc
+package preupstream_test
import (
"context"
"net"
"net/netip"
"testing"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
@@ -13,23 +12,29 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/preupstream"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+func TestMain(m *testing.M) {
+ testutil.DiscardLogOutput(m)
+}
+
const (
reqHostname = "example.com."
defaultTTL = 3600
)
func TestPreUpstreamMwHandler_ServeDNS_withCache(t *testing.T) {
- remoteIP := netip.MustParseAddr("1.2.3.4")
aReq := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
resp := dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{
- dnsservertest.NewA(reqHostname, defaultTTL, remoteIP),
+ dnsservertest.NewA(reqHostname, defaultTTL, dnssvctest.ClientAddr),
})
ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{
Host: aReq.Question[0].Name,
@@ -63,15 +68,16 @@ func TestPreUpstreamMwHandler_ServeDNS_withCache(t *testing.T) {
return rw.WriteMsg(ctx, req, resp)
})
- mw := &preUpstreamMw{
- db: dnsdb.Empty{},
- cacheSize: tc.cacheSize,
- }
+ mw := preupstream.New(&preupstream.Config{
+ Cloner: agdtest.NewCloner(),
+ DB: dnsdb.Empty{},
+ CacheSize: tc.cacheSize,
+ })
h := mw.Wrap(handler)
for i := 0; i < N; i++ {
req := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
- addr := &net.UDPAddr{IP: remoteIP.AsSlice(), Port: 53}
+ addr := &net.UDPAddr{IP: dnssvctest.ClientIP, Port: 53}
nrw := dnsserver.NewNonWriterResponseWriter(addr, addr)
err := h.ServeDNS(ctx, nrw, req)
@@ -85,13 +91,12 @@ func TestPreUpstreamMwHandler_ServeDNS_withCache(t *testing.T) {
func TestPreUpstreamMwHandler_ServeDNS_withECSCache(t *testing.T) {
aReq := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
- remoteIP := netip.MustParseAddr("1.2.3.4")
subnet := netip.MustParsePrefix("1.2.3.4/24")
- const ctry = agd.CountryAD
+ const ctry = geoip.CountryAD
resp := dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{
- dnsservertest.NewA(reqHostname, defaultTTL, remoteIP),
+ dnsservertest.NewA(reqHostname, defaultTTL, dnssvctest.ClientAddr),
})
numReq := 0
@@ -105,45 +110,45 @@ func TestPreUpstreamMwHandler_ServeDNS_withECSCache(t *testing.T) {
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- ctry agd.Country,
- _ agd.ASN,
+ _ *geoip.Location,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {
return netip.MustParsePrefix("1.2.0.0/16"), nil
},
- OnData: func(_ string, _ netip.Addr) (_ *agd.Location, _ error) {
+ OnData: func(_ string, _ netip.Addr) (_ *geoip.Location, _ error) {
panic("not implemented")
},
}
- mw := &preUpstreamMw{
- db: dnsdb.Empty{},
- geoIP: geoIP,
- cacheSize: 100,
- ecsCacheSize: 100,
- useECSCache: true,
- }
+ mw := preupstream.New(&preupstream.Config{
+ Cloner: agdtest.NewCloner(),
+ DB: dnsdb.Empty{},
+ GeoIP: geoIP,
+ CacheSize: 100,
+ ECSCacheSize: 100,
+ UseECSCache: true,
+ })
h := mw.Wrap(handler)
ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{
- Location: &agd.Location{
+ Location: &geoip.Location{
Country: ctry,
},
ECS: &agd.ECS{
- Location: &agd.Location{
+ Location: &geoip.Location{
Country: ctry,
},
Subnet: subnet,
Scope: 0,
},
Host: aReq.Question[0].Name,
- RemoteIP: remoteIP,
+ RemoteIP: dnssvctest.ClientAddr,
})
const N = 5
var nrw *dnsserver.NonWriterResponseWriter
for i := 0; i < N; i++ {
- addr := &net.UDPAddr{IP: remoteIP.AsSlice(), Port: 53}
+ addr := &net.UDPAddr{IP: dnssvctest.ClientIP, Port: 53}
nrw = dnsserver.NewNonWriterResponseWriter(addr, addr)
req := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
@@ -155,15 +160,15 @@ func TestPreUpstreamMwHandler_ServeDNS_withECSCache(t *testing.T) {
}
func TestPreUpstreamMwHandler_ServeDNS_androidMetric(t *testing.T) {
- mw := &preUpstreamMw{db: dnsdb.Empty{}}
+ mw := preupstream.New(&preupstream.Config{
+ Cloner: agdtest.NewCloner(),
+ DB: dnsdb.Empty{},
+ })
- req := dnsservertest.CreateMessage("example.com", dns.TypeA)
+ req := dnsservertest.CreateMessage(reqHostname, dns.TypeA)
resp := new(dns.Msg).SetReply(req)
ctx := context.Background()
- ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{})
- ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{})
- ctx = dnsserver.ContextWithStartTime(ctx, time.Now())
ctx = agd.ContextWithRequestInfo(ctx, &agd.RequestInfo{})
ipA := netip.MustParseAddr("1.2.3.4")
@@ -184,9 +189,9 @@ func TestPreUpstreamMwHandler_ServeDNS_androidMetric(t *testing.T) {
wantAns []dns.RR
}{{
name: "no_changes",
- req: dnsservertest.CreateMessage("example.com.", dns.TypeA),
+ req: dnsservertest.CreateMessage(reqHostname, dns.TypeA),
resp: resp,
- wantName: "example.com.",
+ wantName: reqHostname,
wantAns: nil,
}, {
name: "android-tls-metric",
diff --git a/internal/dnssvc/middleware.go b/internal/dnssvc/middleware.go
deleted file mode 100644
index 09fbfaa..0000000
--- a/internal/dnssvc/middleware.go
+++ /dev/null
@@ -1,222 +0,0 @@
-package dnssvc
-
-import (
- "context"
- "strconv"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
- "github.com/AdguardTeam/AdGuardDNS/internal/filter"
- "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
- "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
- "github.com/miekg/dns"
- "golang.org/x/exp/slices"
-)
-
-// Middlewares
-
-// Main DNS Service Middleware
-
-// Wrap implements the dnsserver.Middleware interface for *Service.
-func (svc *Service) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) {
- return &svcHandler{
- svc: svc,
- next: h,
- }
-}
-
-// svcHandler implements the [dnsserver.Handler] interface and will be used
-// as a [dnsserver.Handler] that the Service middleware returns from the Wrap
-// function call.
-type svcHandler struct {
- svc *Service
- next dnsserver.Handler
-}
-
-// type check
-var _ dnsserver.Handler = (*svcHandler)(nil)
-
-// ServeDNS implements the [dnsserver.Handler] interface for *svcHandler.
-func (mh *svcHandler) ServeDNS(
- ctx context.Context,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
-) (err error) {
- isDebug := req.Question[0].Qclass == dns.ClassCHAOS
- if isDebug {
- req.Question[0].Qclass = dns.ClassINET
- }
-
- reqID, _ := agd.RequestIDFromContext(ctx)
- raddr := rw.RemoteAddr()
- optlog.Debug2("processing request %q from %s", reqID, raddr)
- defer optlog.Debug2("finished processing request %q from %s", reqID, raddr)
-
- reqInfo := agd.MustRequestInfoFromContext(ctx)
- flt := mh.svc.fltStrg.FilterFromContext(ctx, reqInfo)
-
- modReq, reqRes, elapsedReq := mh.svc.filterRequest(ctx, req, flt, reqInfo)
-
- nwrw := internal.MakeNonWriter(rw)
- if modReq != nil {
- // Modified request is set only if the request was modified by a CNAME
- // rewrite rule, so resolve the request as if it was for the rewritten
- // name.
-
- // Clone the request information and replace the host name with the
- // rewritten one, since the request information from current context
- // must only be accessed for reading, see [agd.RequestInfo]. Shallow
- // copy is enough, because we only change the [agd.RequestInfo.Host]
- // field, which is a string.
- modReqInfo := &agd.RequestInfo{}
- *modReqInfo = *reqInfo
- modReqInfo.Host = agdnet.NormalizeDomain(modReq.Question[0].Name)
-
- modReqCtx := agd.ContextWithRequestInfo(ctx, modReqInfo)
-
- optlog.Debug2(
- "dnssvc: request for %q rewritten to %q by CNAME rewrite rule",
- reqInfo.Host,
- modReqInfo.Host,
- )
-
- err = mh.next.ServeDNS(modReqCtx, nwrw, modReq)
- } else {
- err = mh.next.ServeDNS(ctx, nwrw, req)
- }
- if err != nil {
- return err
- }
-
- origResp := nwrw.Msg()
- respRes, elapsedResp := mh.svc.filterResponse(ctx, req, origResp, flt, reqInfo, modReq)
-
- mh.svc.reportMetrics(reqInfo, reqRes, respRes, origResp, elapsedReq+elapsedResp)
-
- if isDebug {
- return mh.svc.writeDebugResponse(ctx, rw, req, origResp, reqRes, respRes)
- }
-
- resp, err := writeFilteredResp(ctx, reqInfo, rw, req, origResp, reqRes, respRes)
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return err
- }
-
- mh.svc.recordQueryInfo(ctx, req, resp, origResp, reqInfo, reqRes, respRes)
-
- return nil
-}
-
-// rewrittenRequest returns a request from res in case it's a CNAME rewrite, and
-// returns nil otherwise. Note that the returned message is always a request
-// since any other rewrite rule type turns into response.
-func rewrittenRequest(res filter.Result) (req *dns.Msg) {
- if res, ok := res.(*filter.ResultModified); ok && !res.Msg.Response {
- return res.Msg
- }
-
- return nil
-}
-
-// filterRequest applies f to req and returns the result of filtering. If the
-// result is the CNAME rewrite, it also returns the modified request to resolve.
-// It also returns the time elapsed on filtering.
-func (svc *Service) filterRequest(
- ctx context.Context,
- req *dns.Msg,
- f filter.Interface,
- ri *agd.RequestInfo,
-) (modReq *dns.Msg, reqRes filter.Result, elapsed time.Duration) {
- start := time.Now()
- reqRes, err := f.FilterRequest(ctx, req, ri)
- if err != nil {
- svc.reportf(ctx, "filtering request: %w", err)
- }
-
- // Consider this operation related to filtering and account the elapsed
- // time.
- modReq = rewrittenRequest(reqRes)
-
- return modReq, reqRes, time.Since(start)
-}
-
-// filterResponse applies f to resp and returns the result of filtering. If
-// origReq has a different question name than resp, the request assumed being
-// CNAME-rewritten and no filtering performed on resp, the CNAME is prepended to
-// resp answer section instead. It also returns the time elapsed on filtering.
-func (svc *Service) filterResponse(
- ctx context.Context,
- req *dns.Msg,
- resp *dns.Msg,
- f filter.Interface,
- ri *agd.RequestInfo,
- modReq *dns.Msg,
-) (respRes filter.Result, elapsed time.Duration) {
- start := time.Now()
-
- if modReq != nil {
- // Return the request name to its original state, since it was
- // previously rewritten by CNAME rewrite rule.
- resp.Question[0] = req.Question[0]
-
- // Prepend the CNAME answer to the response and don't filter it.
- var rr dns.RR = ri.Messages.NewAnswerCNAME(req, modReq.Question[0].Name)
- resp.Answer = slices.Insert(resp.Answer, 0, rr)
-
- // Also consider this operation related to filtering and account the
- // elapsed time.
- return nil, time.Since(start)
- }
-
- respRes, err := f.FilterResponse(ctx, resp, ri)
- if err != nil {
- svc.reportf(ctx, "filtering response: %w", err)
- }
-
- return respRes, time.Since(start)
-}
-
-// reportMetrics extracts filtering metrics data from the context and reports it
-// to Prometheus.
-func (svc *Service) reportMetrics(
- ri *agd.RequestInfo,
- reqRes filter.Result,
- respRes filter.Result,
- origResp *dns.Msg,
- elapsedFiltering time.Duration,
-) {
- var ctry, cont string
- asn := "0"
- if l := ri.Location; l != nil {
- ctry, cont = string(l.Country), string(l.Continent)
- asn = strconv.FormatUint(uint64(l.ASN), 10)
- }
-
- // Here and below stick to using WithLabelValues instead of With in order
- // to avoid extra allocations on prometheus.Labels.
-
- metrics.DNSSvcRequestByCountryTotal.WithLabelValues(cont, ctry).Inc()
- metrics.DNSSvcRequestByASNTotal.WithLabelValues(ctry, asn).Inc()
-
- id, _, isBlocked := filteringData(reqRes, respRes)
- metrics.DNSSvcRequestByFilterTotal.WithLabelValues(
- string(id),
- metrics.BoolString(ri.Profile == nil),
- ).Inc()
-
- metrics.DNSSvcFilteringDuration.Observe(elapsedFiltering.Seconds())
- metrics.DNSSvcUsersCountUpdate(ri.RemoteIP)
-
- if svc.researchMetrics {
- metrics.ReportResearch(ri, origResp, id, isBlocked, svc.researchLog)
- }
-}
-
-// reportf is a helper method for reporting non-critical errors.
-func (svc *Service) reportf(ctx context.Context, format string, args ...any) {
- agd.Collectf(ctx, svc.errColl, "dnssvc: "+format, args...)
-}
diff --git a/internal/dnssvc/presvcmw.go b/internal/dnssvc/presvcmw.go
deleted file mode 100644
index 16e0c48..0000000
--- a/internal/dnssvc/presvcmw.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package dnssvc
-
-import (
- "context"
- "fmt"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/AdguardTeam/AdGuardDNS/internal/filter"
- "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
- "github.com/AdguardTeam/golibs/errors"
- "github.com/AdguardTeam/golibs/log"
- "github.com/miekg/dns"
-)
-
-// Pre-service middleware
-
-// preServiceMw is a middleware that comes right before the main filtering
-// middleware of DNS service. It includes handling of TXT queries for domain
-// names that may be filtered by safe browsing or parental control filters as
-// well as handling of the DNS-server check queries.
-type preServiceMw struct {
- // messages is used to construct TXT responses.
- messages *dnsmsg.Constructor
-
- // hashMatcher is the safe browsing DNS hashMatcher.
- hashMatcher filter.HashMatcher
-
- // checker is used to detect and process DNS-check requests.
- checker dnscheck.Interface
-}
-
-// type check
-var _ dnsserver.Middleware = (*preServiceMw)(nil)
-
-// Wrap implements the [dnsserver.Middleware] interface for *preServiceMw.
-func (mw *preServiceMw) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) {
- return &preServiceMwHandler{
- mw: mw,
- next: h,
- }
-}
-
-// preServiceMwHandler implements the [dnsserver.Handler] interface and will be
-// used as a [dnsserver.Handler] that the preServiceMw middleware returns from
-// the Wrap function call.
-type preServiceMwHandler struct {
- mw *preServiceMw
- next dnsserver.Handler
-}
-
-// type check
-var _ dnsserver.Handler = (*preServiceMwHandler)(nil)
-
-// ServeDNS implements the [dnsserver.Handler] interface for
-// *preServiceMwHandler.
-func (mh *preServiceMwHandler) ServeDNS(
- ctx context.Context,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
-) (err error) {
- defer func() { err = errors.Annotate(err, "presvc mw: %w") }()
-
- ri := agd.MustRequestInfoFromContext(ctx)
- if ri.QType == dns.TypeTXT {
- // Don't wrap the error, because it's informative enough as is.
- return mh.respondWithHashes(ctx, rw, req, ri)
- }
-
- resp, err := mh.mw.checker.Check(ctx, req, ri)
- if err != nil {
- return fmt.Errorf("calling dnscheck: %w", err)
- } else if resp != nil {
- return errors.Annotate(rw.WriteMsg(ctx, req, resp), "writing dnscheck response: %w")
- }
-
- // Don't wrap the error, because this is the main flow, and there is already
- // [errors.Annotate] here.
- return mh.next.ServeDNS(ctx, rw, req)
-}
-
-// respondWithHashes collects the hashes that match the given hash-prefix query
-// and writes a response with them.
-func (mh *preServiceMwHandler) respondWithHashes(
- ctx context.Context,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
- ri *agd.RequestInfo,
-) (err error) {
- optlog.Debug1("presvc mw: safe browsing: got txt req for %q", ri.Host)
-
- hashes, matched, err := mh.mw.hashMatcher.MatchByPrefix(ctx, ri.Host)
- if err != nil {
- // Don't return or collect this error to prevent DDoS of the error
- // collector by sending bad requests.
- log.Error("presvc mw: safe browsing: matching hashes: %s", err)
-
- resp := mh.mw.messages.NewMsgREFUSED(req)
- err = rw.WriteMsg(ctx, req, resp)
-
- return errors.Annotate(err, "writing refused response: %w")
- } else if !matched {
- // Don't wrap the error, because this is the main flow, and there is
- // already [errors.Annotate] here.
- return mh.next.ServeDNS(ctx, rw, req)
- }
-
- resp, err := mh.mw.messages.NewTXTRespMsg(req, hashes...)
- if err != nil {
- // Technically should never happen since the only error that could arise
- // in [dnsmsg.Constructor.NewTXTRespMsg] is the one about request type
- // mismatch.
- return fmt.Errorf("creating safe browsing result: %w", err)
- }
-
- optlog.Debug1("presvc mw: safe browsing: writing hashes %q", hashes)
-
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- return fmt.Errorf("writing safe browsing response: %w", err)
- }
-
- return nil
-}
diff --git a/internal/dnssvc/preupstreammw.go b/internal/dnssvc/preupstreammw.go
deleted file mode 100644
index a31814b..0000000
--- a/internal/dnssvc/preupstreammw.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package dnssvc
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
- "github.com/AdguardTeam/AdGuardDNS/internal/ecscache"
- "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
- "github.com/AdguardTeam/golibs/errors"
- "github.com/AdguardTeam/golibs/log"
- "github.com/miekg/dns"
-)
-
-// Pre-Upstream Middleware
-
-// preUpstreamMw is a middleware that prepares records for caching and upstream
-// handling as well as records anonymous DNS statistics.
-type preUpstreamMw struct {
- db dnsdb.Interface
- geoIP geoip.Interface
- cacheMinTTL time.Duration
- cacheSize int
- ecsCacheSize int
- useECSCache bool
- useCacheTTLOverride bool
-}
-
-// type check
-var _ dnsserver.Middleware = (*preUpstreamMw)(nil)
-
-// Wrap implements the [dnsserver.Middleware] interface for *preUpstreamMw.
-func (mw *preUpstreamMw) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) {
- // Make sure that cache middleware is the closest one to the actual upstream
- // handler.
- if mw.cacheSize > 0 {
- var cacheMw dnsserver.Middleware
- if mw.useECSCache {
- cacheMw = ecscache.NewMiddleware(&ecscache.MiddlewareConfig{
- GeoIP: mw.geoIP,
- Size: mw.cacheSize,
- ECSSize: mw.ecsCacheSize,
- MinTTL: mw.cacheMinTTL,
- UseTTLOverride: mw.useCacheTTLOverride,
- })
- } else {
- cacheMw = cache.NewMiddleware(&cache.MiddlewareConfig{
- MetricsListener: &prometheus.CacheMetricsListener{},
- Size: mw.cacheSize,
- MinTTL: mw.cacheMinTTL,
- UseTTLOverride: mw.useCacheTTLOverride,
- })
- }
-
- h = cacheMw.Wrap(h)
- }
-
- log.Info("cache: size: %d, ecs: %t", mw.cacheSize, mw.useECSCache)
-
- return &preUpstreamMwHandler{
- mw: mw,
- next: h,
- }
-}
-
-// preUpstreamMwHandler implements the [dnsserver.Handler] interface and will
-// be used as a [dnsserver.Handler] that the preUpstreamMw middleware returns
-// from the Wrap function call.
-type preUpstreamMwHandler struct {
- mw *preUpstreamMw
- next dnsserver.Handler
-}
-
-// type check
-var _ dnsserver.Handler = (*preUpstreamMwHandler)(nil)
-
-// ServeDNS implements the [dnsserver.Handler] interface for
-// *preUpstreamMwHandler.
-func (mh *preUpstreamMwHandler) ServeDNS(
- ctx context.Context,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
-) (err error) {
- defer func() { err = errors.Annotate(err, "pre-upstream mw: %w") }()
-
- if rn := agdnet.AndroidMetricDomainReplacement(req.Question[0].Name); rn != "" {
- // Don't wrap the error, because it's informative enough as is.
- return mh.serveAndroidMetric(ctx, mh.next, rw, req, rn)
- }
-
- nwrw := internal.MakeNonWriter(rw)
- err = mh.next.ServeDNS(ctx, nwrw, req)
- if err != nil {
- // Don't wrap the error, because this is the main flow, and there is
- // already errors.Annotate here.
- return err
- }
-
- resp := nwrw.Msg()
- ri := agd.MustRequestInfoFromContext(ctx)
- mh.mw.db.Record(ctx, resp, ri)
-
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- return fmt.Errorf("writing response: %w", err)
- }
-
- return nil
-}
-
-// serveAndroidMetric makes sure we avoid resolving random Android DoT, DoH metric
-// domains. replName is the replacement domain name to use to improve caching
-// of these metric domains.
-func (mh *preUpstreamMwHandler) serveAndroidMetric(
- ctx context.Context,
- h dnsserver.Handler,
- rw dnsserver.ResponseWriter,
- origReq *dns.Msg,
- replName string,
-) (err error) {
- defer func() { err = errors.Annotate(err, "android metrics: %w") }()
-
- req := dnsmsg.Clone(origReq)
- req.Question[0].Name = replName
-
- nwrw := internal.MakeNonWriter(rw)
- err = h.ServeDNS(ctx, nwrw, req)
- if err != nil {
- // Don't wrap the error, because this is the main flow, and there is
- // already errors.Annotate here.
- return err
- }
-
- resp := nwrw.Msg()
- resp.SetReply(origReq)
- mh.replaceResp(origReq.Question[0].Name, resp)
-
- err = rw.WriteMsg(ctx, origReq, resp)
- if err != nil {
- return fmt.Errorf("writing response: %w", err)
- }
-
- return nil
-}
-
-// replaceResp replaces the name of the answers in resp with name. This is
-// required since all Android metrics requests are cached as one.
-func (mh *preUpstreamMwHandler) replaceResp(name string, resp *dns.Msg) {
- if len(resp.Answer) == 0 {
- return
- }
-
- // TODO(a.garipov): Add Ns and Extra handling as well?
- for _, a := range resp.Answer {
- h := a.Header()
- if agdnet.AndroidMetricDomainReplacement(h.Name) != "" {
- h.Name = name
- }
- }
-}
diff --git a/internal/dnssvc/record.go b/internal/dnssvc/record.go
deleted file mode 100644
index 2da0b7f..0000000
--- a/internal/dnssvc/record.go
+++ /dev/null
@@ -1,207 +0,0 @@
-package dnssvc
-
-import (
- "context"
- "fmt"
- "net"
- "net/netip"
- "strings"
- "time"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/AdguardTeam/AdGuardDNS/internal/filter"
- "github.com/AdguardTeam/AdGuardDNS/internal/querylog"
- "github.com/AdguardTeam/golibs/netutil"
- "github.com/miekg/dns"
-)
-
-// Query Logging, Billing, And Statistics Recording
-
-// recordQueryInfo extracts loggable information from request, response, and
-// filtering data and writes them to the query log, billing, and filtering-rule
-// statistics, handling non-critical errors.
-func (svc *Service) recordQueryInfo(
- ctx context.Context,
- req *dns.Msg,
- resp *dns.Msg,
- origResp *dns.Msg,
- ri *agd.RequestInfo,
- reqRes filter.Result,
- respRes filter.Result,
-) {
- id, text, blocked := filteringData(reqRes, respRes)
- svc.ruleStat.Collect(ctx, id, text)
-
- prof := ri.Profile
- if prof == nil {
- return
- }
-
- var devID agd.DeviceID
- if d := ri.Device; d != nil {
- devID = d.ID
- }
-
- var reqCtry agd.Country
- var reqASN agd.ASN
- if g := ri.Location; g != nil {
- reqCtry, reqASN = g.Country, g.ASN
- }
-
- proto := dnsserver.MustServerInfoFromContext(ctx).Proto
- start := dnsserver.MustStartTimeFromContext(ctx)
- svc.billStat.Record(ctx, devID, reqCtry, reqASN, start, proto)
-
- if !prof.QueryLogEnabled {
- return
- }
-
- rcode, respIP, respDNSSEC := svc.responseData(ctx, resp)
- if blocked {
- // If the request or the response were blocked, resp may contain an
- // unspecified IP address, a rewritten IP address, or none at all, while
- // the original response may contain an actual IP address that should be
- // used to determine the response country.
- _, respIP, _ = svc.responseData(ctx, origResp)
- }
-
- var respCtry agd.Country
- if !respIP.IsUnspecified() {
- host := ri.Host
- if modReq := rewrittenRequest(reqRes); modReq != nil {
- // If the request was modified by CNAME rule, the actual result
- // belongs to the hostname from that CNAME.
- host = strings.TrimSuffix(modReq.Question[0].Name, ".")
- }
-
- respCtry = svc.country(ctx, host, respIP)
- }
-
- q := req.Question[0]
- e := &querylog.Entry{
- RequestResult: reqRes,
- ResponseResult: respRes,
- Time: start,
- RequestID: ri.ID,
- ProfileID: prof.ID,
- DeviceID: devID,
- ClientCountry: reqCtry,
- ResponseCountry: respCtry,
- DomainFQDN: q.Name,
- ClientASN: reqASN,
- Elapsed: uint16(time.Since(start).Milliseconds()),
- RequestType: q.Qtype,
- Protocol: proto,
- DNSSEC: respDNSSEC,
- ResponseCode: rcode,
- }
-
- err := svc.queryLog.Write(ctx, e)
- if err != nil {
- // Consider query logging errors non-critical.
- svc.reportf(ctx, "writing query log: %w", err)
- }
-}
-
-// filteringData returns the data necessary for request information recording
-// from the request and response filtering results.
-func filteringData(
- reqRes, respRes filter.Result,
-) (id agd.FilterListID, text agd.FilterRuleText, blocked bool) {
- if reqRes != nil {
- return resultData(reqRes, "reqRes")
- }
-
- return resultData(respRes, "respRes")
-}
-
-// resultData returns the data necessary for request information recording from
-// one filtering result. argName is used to provide better error handling.
-func resultData(
- res filter.Result,
- argName string,
-) (id agd.FilterListID, text agd.FilterRuleText, blocked bool) {
- if res == nil {
- return agd.FilterListIDNone, "", false
- }
-
- id, text = res.MatchedRule()
- switch res := res.(type) {
- case *filter.ResultAllowed:
- blocked = false
- case
- *filter.ResultBlocked,
- *filter.ResultModified:
- blocked = true
- default:
- // Consider unhandled sum type members as unrecoverable programmer
- // errors.
- panic(&agd.ArgumentError{
- Name: argName,
- Message: fmt.Sprintf("unexpected type %T", res),
- })
- }
-
- return id, text, blocked
-}
-
-// responseData is a helper that returns the response code, the first IP
-// address, and the DNSSEC AD flag from the DNS query response if the answer has
-// the type of A or AAAA or a nil IP address otherwise.
-//
-// If resp is nil or contains invalid data, it returns 0xff (an unassigned
-// RCODE), net.Addr{}, and false. It reports all errors using svc.reportf.
-func (svc *Service) responseData(
- ctx context.Context,
- resp *dns.Msg,
-) (rcode dnsmsg.RCode, ip netip.Addr, dnssec bool) {
- if resp == nil {
- return 0xff, netip.Addr{}, false
- }
-
- var rrType dns.Type
- var fam netutil.AddrFamily
- var netIP net.IP
- dnssec = resp.AuthenticatedData
- rcode = dnsmsg.RCode(resp.Rcode)
- for _, rr := range resp.Answer {
- switch v := rr.(type) {
- case *dns.A:
- fam = netutil.AddrFamilyIPv4
- rrType, netIP = dns.Type(v.Hdr.Rrtype), v.A
- case *dns.AAAA:
- fam = netutil.AddrFamilyIPv6
- rrType, netIP = dns.Type(v.Hdr.Rrtype), v.AAAA
- default:
- continue
- }
-
- break
- }
-
- if netIP != nil {
- var err error
- ip, err = netutil.IPToAddr(netIP, fam)
- if err != nil {
- svc.reportf(ctx, "converting %s resp data: %w", rrType, err)
- }
- }
-
- return rcode, ip, dnssec
-}
-
-// country is a wrapper around the GeoIP call that contains the handling of
-// non-critical GeoIP errors.
-func (svc *Service) country(ctx context.Context, host string, ip netip.Addr) (c agd.Country) {
- l, err := svc.geoIP.Data(host, ip)
- if err != nil {
- // Consider GeoIP errors non-critical.
- svc.reportf(ctx, "getting geoip data: %w", err)
- } else if l == nil {
- return agd.CountryNone
- }
-
- return l.Country
-}
diff --git a/internal/dnssvc/resp.go b/internal/dnssvc/resp.go
deleted file mode 100644
index 12bfa1c..0000000
--- a/internal/dnssvc/resp.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package dnssvc
-
-import (
- "context"
- "fmt"
-
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
- "github.com/AdguardTeam/AdGuardDNS/internal/filter"
- "github.com/miekg/dns"
-)
-
-// Response Handling
-
-// writeFilteredResp writes the response to rw if reqRes and respRes require
-// that. written is the DNS message that was actually sent to the client.
-func writeFilteredResp(
- ctx context.Context,
- ri *agd.RequestInfo,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
- resp *dns.Msg,
- reqRes filter.Result,
- respRes filter.Result,
-) (written *dns.Msg, err error) {
- switch reqRes := reqRes.(type) {
- case nil:
- return writeFilteredRespNoReq(ctx, ri, rw, req, resp, respRes)
- case *filter.ResultBlocked:
- written, err = writeBlockedResp(ctx, ri, rw, req)
- case *filter.ResultAllowed:
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- err = fmt.Errorf("writing response to allowed request: %w", err)
- } else {
- written = resp
- }
- case *filter.ResultModified:
- if reqRes.Msg.Response {
- // Only use the request filtering result in case it's already a
- // response. Otherwise, it's a CNAME rewrite result, which isn't
- // filtered after resolving.
- resp = reqRes.Msg
- }
-
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- err = fmt.Errorf("writing response to modified request: %w", err)
- } else {
- written = resp
- }
- default:
- // Consider unhandled sum type members as unrecoverable programmer
- // errors.
- panic(&agd.ArgumentError{
- Name: "reqRes",
- Message: fmt.Sprintf("unexpected type %T", reqRes),
- })
- }
-
- return written, err
-}
-
-// writeFilteredRespNoReq writes the response to rw if respRes requires that.
-// written is the DNS message that was actually sent to the client.
-func writeFilteredRespNoReq(
- ctx context.Context,
- ri *agd.RequestInfo,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
- resp *dns.Msg,
- respRes filter.Result,
-) (written *dns.Msg, err error) {
- switch respRes := respRes.(type) {
- case nil, *filter.ResultAllowed:
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- err = fmt.Errorf("writing allowed or not filtered response: %w", err)
- } else {
- written = resp
- }
- case *filter.ResultBlocked:
- written, err = writeBlockedResp(ctx, ri, rw, req)
- case *filter.ResultModified:
- err = rw.WriteMsg(ctx, req, respRes.Msg)
- if err != nil {
- err = fmt.Errorf("writing modified response: %w", err)
- } else {
- written = respRes.Msg
- }
- default:
- // Consider unhandled sum type members as unrecoverable programmer
- // errors.
- panic(&agd.ArgumentError{
- Name: "respRes",
- Message: fmt.Sprintf("unexpected type %T", respRes),
- })
- }
-
- return written, err
-}
-
-// writeBlockedResp writes the appropriate blocked response to the response
-// writer and returns it.
-func writeBlockedResp(
- ctx context.Context,
- ri *agd.RequestInfo,
- rw dnsserver.ResponseWriter,
- req *dns.Msg,
-) (resp *dns.Msg, err error) {
- resp, err = ri.Messages.NewBlockedRespMsg(req)
- if err != nil {
- return nil, fmt.Errorf("creating blocked response: %w", err)
- }
-
- err = rw.WriteMsg(ctx, req, resp)
- if err != nil {
- return nil, fmt.Errorf("writing blocked response: %w", err)
- }
-
- return resp, nil
-}
diff --git a/internal/ecscache/cache.go b/internal/ecscache/cache.go
index ced15bf..ac1f0a8 100644
--- a/internal/ecscache/cache.go
+++ b/internal/ecscache/cache.go
@@ -47,26 +47,23 @@ type cacheRequest struct {
// there is one. If the host was found in the cache for domain names that
// support ECS, isECSDependent is true. cr, cr.req, and cr.subnet must not be
// nil.
-func (mw *Middleware) get(
- req *dns.Msg,
- cr *cacheRequest,
-) (resp *dns.Msg, found, isECSDependent bool) {
+func (mw *Middleware) get(req *dns.Msg, cr *cacheRequest) (resp *dns.Msg, isECSDependent bool) {
key := mw.toCacheKey(cr, false)
item, ok := itemFromCache(mw.cache, key, cr)
if ok {
- return fromCacheItem(item, req, cr.reqDO), true, false
+ return fromCacheItem(item, mw.cloner, req, cr.reqDO), false
} else if cr.isECSDeclined {
- return nil, false, false
+ return nil, false
}
// Try ECS-aware cache.
key = mw.toCacheKey(cr, true)
item, ok = itemFromCache(mw.ecsCache, key, cr)
if ok {
- return fromCacheItem(item, req, cr.reqDO), true, true
+ return fromCacheItem(item, mw.cloner, req, cr.reqDO), true
}
- return nil, false, false
+ return nil, false
}
// itemFromCache retrieves a DNS message for the given key. cr.host is used to
@@ -152,14 +149,15 @@ func (mw *Middleware) set(resp *dns.Msg, cr *cacheRequest, respIsECSDependent bo
exp := time.Duration(ttl) * time.Second
if mw.useTTLOverride && resp.Rcode != dns.RcodeServerFailure {
- // TODO(d.kolyshev): Use built-in max in go 1.21.
- exp = mathutil.Max(exp, mw.cacheMinTTL)
+ exp = max(exp, mw.cacheMinTTL)
dnsmsg.SetMinTTL(resp, uint32(exp.Seconds()))
}
key := mw.toCacheKey(cr, respIsECSDependent)
- item := toCacheItem(resp, cr.host)
+ cachedResp := mw.cloner.Clone(resp)
+
+ item := toCacheItem(cachedResp, cr.host)
err := cache.SetWithExpire(key, item, exp)
if err != nil {
// Shouldn't happen, since we don't set a serialization function.
@@ -180,18 +178,23 @@ type cacheItem struct {
host string
}
-// toCacheItem creates a cacheItem from a DNS message.
-func toCacheItem(msg *dns.Msg, host string) (item *cacheItem) {
+// toCacheItem creates a *cacheItem from a DNS message.
+func toCacheItem(resp *dns.Msg, host string) (item *cacheItem) {
return &cacheItem{
- msg: dnsmsg.Clone(msg),
+ msg: resp,
when: time.Now(),
host: host,
}
}
-// fromCacheItem creates a response from the cached item. item and req must not
-// be nil.
-func fromCacheItem(item *cacheItem, req *dns.Msg, reqDO bool) (msg *dns.Msg) {
+// fromCacheItem creates a response from the cached item. item, cloner, and req
+// must not be nil.
+func fromCacheItem(
+ item *cacheItem,
+ cloner *dnsmsg.Cloner,
+ req *dns.Msg,
+ reqDO bool,
+) (resp *dns.Msg) {
// Update the TTL depending on when the item was cached. If it's already
// expired, update TTL to 0.
newTTL := dnsmsg.FindLowestTTL(item.msg)
@@ -201,17 +204,18 @@ func fromCacheItem(item *cacheItem, req *dns.Msg, reqDO bool) (msg *dns.Msg) {
newTTL = 0
}
- msg = dnsmsg.Clone(item.msg)
- msg.SetRcode(req, item.msg.Rcode)
- setRespAD(msg, req.AuthenticatedData, reqDO)
+ resp = cloner.Clone(item.msg)
- for _, rrs := range [][]dns.RR{msg.Answer, msg.Ns, msg.Extra} {
+ resp.SetRcode(req, item.msg.Rcode)
+ setRespAD(resp, req.AuthenticatedData, reqDO)
+
+ for _, rrs := range [][]dns.RR{resp.Answer, resp.Ns, resp.Extra} {
for _, rr := range rrs {
rr.Header().Ttl = newTTL
}
}
- return msg
+ return resp
}
// roundDiv divides num by denom, rounding towards nearest integer. denom must
diff --git a/internal/ecscache/cache_internal_test.go b/internal/ecscache/cache_internal_test.go
index 50d5cea..7aa2090 100644
--- a/internal/ecscache/cache_internal_test.go
+++ b/internal/ecscache/cache_internal_test.go
@@ -34,6 +34,6 @@ func BenchmarkMiddleware_Get(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- msgSink, _, _ = mw.get(req, cr)
+ msgSink, _ = mw.get(req, cr)
}
}
diff --git a/internal/ecscache/ecsblocklist.go b/internal/ecscache/ecsblocklist.go
new file mode 100644
index 0000000..4455403
--- /dev/null
+++ b/internal/ecscache/ecsblocklist.go
@@ -0,0 +1,8566 @@
+// Code generated by go run ./ecsblocklist_generate.go; DO NOT EDIT.
+
+package ecscache
+
+// FakeECSFQDNs contains all domains that indicate ECS support, but in fact
+// don't have one.
+var FakeECSFQDNs = map[string]struct{}{
+ "01.cofile.net.": {},
+ "011b2ef417d541e4b4a0753f94353476.pacloudflare.com.": {},
+ "02.cofile.net.": {},
+ "0217991b.akstat.io.": {},
+ "0217991d.akstat.io.": {},
+ "0272ac85-5199-4024-a555-397c3d825d95.prmutv.co.": {},
+ "03.cofile.net.": {},
+ "04.cofile.net.": {},
+ "05.cofile.net.": {},
+ "06.cofile.net.": {},
+ "07.cofile.net.": {},
+ "08.cofile.net.": {},
+ "09.cofile.net.": {},
+ "0cf.io.": {},
+ "0cf17917-395b-4f25-91cc-db3bdd6044b0.prmutv.co.": {},
+ "1-146-13451-3.b.cdn12.com.": {},
+ "1-322-13451-3.b.cdn12.com.": {},
+ "1-361-13451-3.b.cdn12.com.": {},
+ "1-363-13451-3.b.cdn12.com.": {},
+ "1-94-13451-3.b.cdn12.com.": {},
+ "1-957-13451-3.b.cdn12.com.": {},
+ "1-961-13451-3.b.cdn12.com.": {},
+ "1.cn.pool.ntp.org.": {},
+ "10.cofile.net.": {},
+ "103.chtsite.com.": {},
+ "10m.com.cn.": {},
+ "11-alarm-mop.meshare.com.": {},
+ "11.cofile.net.": {},
+ "1184-ipv4v6e.clump.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "12-alarm-mop.meshare.com.": {},
+ "12.cofile.net.": {},
+ "126.net.": {},
+ "13-alarm-mop.meshare.com.": {},
+ "13.cofile.net.": {},
+ "14-alarm-mop.meshare.com.": {},
+ "14.cofile.net.": {},
+ "15.cofile.net.": {},
+ "16.cofile.net.": {},
+ "163jiasu.com.": {},
+ "163yun.com.": {},
+ "1688.com.": {},
+ "17.cofile.net.": {},
+ "173bf105.akstat.io.": {},
+ "17de4c13.akstat.io.": {},
+ "17de4c1f.akstat.io.": {},
+ "18.cofile.net.": {},
+ "1845588971.rsc.cdn77.org.": {},
+ "19.cofile.net.": {},
+ "192667-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "192991-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "193119-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "193253-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "193562-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "193592-ipv4v6e.farm.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "1941-ipv4v6e.clump.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "1weather.onelouder.com.": {},
+ "2.401402081.west-gcloud.codm.activision.com.": {},
+ "2.realtime.services.box.net.": {},
+ "20.cofile.net.": {},
+ "201205igp.gameloft.com.": {},
+ "2345.cc.": {},
+ "23andme.com.": {},
+ "2442-ipv4v6e.clump.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "2acdb9b66bb242618283aadb21ede6c1.pacloudflare.com.": {},
+ "2e4b93d1-a8ae-4a89-8885-6109135ac0de.prmutv.co.": {},
+ "2talk.com.": {},
+ "360.cn.": {},
+ "360os.com.": {},
+ "360safe.com.": {},
+ "365.kdocs.cn.": {},
+ "3a6b0682-f3e1-4576-a706-5eb4101b9cc3.prmutv.co.": {},
+ "3aba5292-ba75-422b-8715-bd21146f7836.prmutv.co.": {},
+ "3d2fb0bd-52fc-4b75-aaf5-2d436c172540.prmutv.co.": {},
+ "3g.cn.": {},
+ "4.401402081.west-gcloud.codm.activision.com.": {},
+ "4.adsco.re.": {},
+ "401402081.west-gcloud.codm.activision.com.": {},
+ "4065-ipv4v6e.clump.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "46a2e5c3c5a64e218b60f2c2ee76b750.pacloudflare.com.": {},
+ "4974-ipv4v6e.clump.dprodmgd105.aa-rt.sharepoint.com.": {},
+ "507b28fb-2ef1-4c34-8bda-ba32030bb199.prmutv.co.": {},
+ "520.jp.": {},
+ "6.401402081.west-gcloud.codm.activision.com.": {},
+ "6093eccf-6734-4877-ac8b-83d6d0e27b46.prmutv.co.": {},
+ "6c3e19e3-d05e-45d1-8f79-fcd6cb2f3a21.prmutv.co.": {},
+ "733868e706dd40d3a4a0588fc39b3df8.pacloudflare.com.": {},
+ "7c7b02d4bc3d48dd81a7c7738d4de1ab.pacloudflare.com.": {},
+ "88a66e5c-8fe8-48af-9c6c-3ec3f4983aad.prmutv.co.": {},
+ "8x8.com.cdn.cloudflare.net.": {},
+ "9pumbooc7hqedhjmeavbpmp5ik9u0ngr-data.customer.pendo.io.": {},
+ "a-m-p.xyz.": {},
+ "a.ad.gt.cdn.cloudflare.net.": {},
+ "a.beat3put.com.": {},
+ "a.fc.namequery.com.": {},
+ "a.nel.cloudflare.com.": {},
+ "a.ns.c10r.facebook.com.": {},
+ "a.ns.cdn.whatsapp.net.": {},
+ "a.ns.xx.fbcdn.net.": {},
+ "a.tile.openstreetmap.org.": {},
+ "a.tile.osm.org.": {},
+ "a011.casalemedia.com.": {},
+ "a014.casalemedia.com.": {},
+ "a016.casalemedia.com.": {},
+ "a017.casalemedia.com.": {},
+ "a018.casalemedia.com.": {},
+ "a019.casalemedia.com.": {},
+ "a020.casalemedia.com.": {},
+ "a021.casalemedia.com.": {},
+ "a022.casalemedia.com.": {},
+ "a023.casalemedia.com.": {},
+ "a025.casalemedia.com.": {},
+ "a026.casalemedia.com.": {},
+ "a027.casalemedia.com.": {},
+ "a028.casalemedia.com.": {},
+ "a029.casalemedia.com.": {},
+ "a030.casalemedia.com.": {},
+ "a031.casalemedia.com.": {},
+ "a032.casalemedia.com.": {},
+ "a033.casalemedia.com.": {},
+ "a034.casalemedia.com.": {},
+ "a035.casalemedia.com.": {},
+ "a037.casalemedia.com.": {},
+ "a038.casalemedia.com.": {},
+ "a039.casalemedia.com.": {},
+ "a040.casalemedia.com.": {},
+ "a041.casalemedia.com.": {},
+ "a042.casalemedia.com.": {},
+ "a044.casalemedia.com.": {},
+ "a045.casalemedia.com.": {},
+ "a046.casalemedia.com.": {},
+ "a050.casalemedia.com.": {},
+ "a054.casalemedia.com.": {},
+ "a058.casalemedia.com.": {},
+ "a074.casalemedia.com.": {},
+ "a075.casalemedia.com.": {},
+ "a080.casalemedia.com.": {},
+ "a088.casalemedia.com.": {},
+ "a091.casalemedia.com.": {},
+ "a095.casalemedia.com.": {},
+ "a096.casalemedia.com.": {},
+ "a097.casalemedia.com.": {},
+ "a098.casalemedia.com.": {},
+ "a099.casalemedia.com.": {},
+ "a119.casalemedia.com.": {},
+ "a121.casalemedia.com.": {},
+ "a123.casalemedia.com.": {},
+ "a124.casalemedia.com.": {},
+ "a126.casalemedia.com.": {},
+ "a127.casalemedia.com.": {},
+ "a128.casalemedia.com.": {},
+ "a129.casalemedia.com.": {},
+ "a131.casalemedia.com.": {},
+ "a132.casalemedia.com.": {},
+ "a133.casalemedia.com.": {},
+ "a134.casalemedia.com.": {},
+ "a138.casalemedia.com.": {},
+ "a140.casalemedia.com.": {},
+ "a141.casalemedia.com.": {},
+ "a143.casalemedia.com.": {},
+ "a144.casalemedia.com.": {},
+ "a145.casalemedia.com.": {},
+ "a1462.casalemedia.com.": {},
+ "a1463.casalemedia.com.": {},
+ "a1467.casalemedia.com.": {},
+ "a147.casalemedia.com.": {},
+ "a1474.casalemedia.com.": {},
+ "a1487.casalemedia.com.": {},
+ "a149.casalemedia.com.": {},
+ "a1498.casalemedia.com.": {},
+ "a150.casalemedia.com.": {},
+ "a151.casalemedia.com.": {},
+ "a1518.casalemedia.com.": {},
+ "a152.casalemedia.com.": {},
+ "a1521.casalemedia.com.": {},
+ "a1528.casalemedia.com.": {},
+ "a153.casalemedia.com.": {},
+ "a1531.casalemedia.com.": {},
+ "a156.casalemedia.com.": {},
+ "a1565.casalemedia.com.": {},
+ "a157.casalemedia.com.": {},
+ "a158.casalemedia.com.": {},
+ "a1587.casalemedia.com.": {},
+ "a159.casalemedia.com.": {},
+ "a1590.casalemedia.com.": {},
+ "a1591.casalemedia.com.": {},
+ "a1598.casalemedia.com.": {},
+ "a160.casalemedia.com.": {},
+ "a161.casalemedia.com.": {},
+ "a1649.casalemedia.com.": {},
+ "a1660.casalemedia.com.": {},
+ "a1671.casalemedia.com.": {},
+ "a1675.casalemedia.com.": {},
+ "a1676.casalemedia.com.": {},
+ "a169.casalemedia.com.": {},
+ "a1692.casalemedia.com.": {},
+ "a1693.casalemedia.com.": {},
+ "a1696.casalemedia.com.": {},
+ "a170.casalemedia.com.": {},
+ "a172.casalemedia.com.": {},
+ "a179.casalemedia.com.": {},
+ "a182.casalemedia.com.": {},
+ "a186.casalemedia.com.": {},
+ "a187.casalemedia.com.": {},
+ "a188.casalemedia.com.": {},
+ "a189.casalemedia.com.": {},
+ "a190.casalemedia.com.": {},
+ "a192.casalemedia.com.": {},
+ "a200.casalemedia.com.": {},
+ "a2001.casalemedia.com.": {},
+ "a2002.casalemedia.com.": {},
+ "a2003.casalemedia.com.": {},
+ "a2004.casalemedia.com.": {},
+ "a2005.casalemedia.com.": {},
+ "a2006.casalemedia.com.": {},
+ "a2007.casalemedia.com.": {},
+ "a2008.casalemedia.com.": {},
+ "a2009.casalemedia.com.": {},
+ "a201.casalemedia.com.": {},
+ "a2010.casalemedia.com.": {},
+ "a2011.casalemedia.com.": {},
+ "a2012.casalemedia.com.": {},
+ "a2013.casalemedia.com.": {},
+ "a2014.casalemedia.com.": {},
+ "a2015.casalemedia.com.": {},
+ "a2016.casalemedia.com.": {},
+ "a2017.casalemedia.com.": {},
+ "a2018.casalemedia.com.": {},
+ "a2019.casalemedia.com.": {},
+ "a2020.casalemedia.com.": {},
+ "a2021.casalemedia.com.": {},
+ "a2022.casalemedia.com.": {},
+ "a2023.casalemedia.com.": {},
+ "a2024.casalemedia.com.": {},
+ "a2025.casalemedia.com.": {},
+ "a2026.casalemedia.com.": {},
+ "a2027.casalemedia.com.": {},
+ "a2028.casalemedia.com.": {},
+ "a2030.casalemedia.com.": {},
+ "a2031.casalemedia.com.": {},
+ "a2032.casalemedia.com.": {},
+ "a2033.casalemedia.com.": {},
+ "a2034.casalemedia.com.": {},
+ "a2035.casalemedia.com.": {},
+ "a2036.casalemedia.com.": {},
+ "a2037.casalemedia.com.": {},
+ "a2038.casalemedia.com.": {},
+ "a2039.casalemedia.com.": {},
+ "a2040.casalemedia.com.": {},
+ "a2041.casalemedia.com.": {},
+ "a2042.casalemedia.com.": {},
+ "a2043.casalemedia.com.": {},
+ "a2044.casalemedia.com.": {},
+ "a2045.casalemedia.com.": {},
+ "a2046.casalemedia.com.": {},
+ "a2047.casalemedia.com.": {},
+ "a2048.casalemedia.com.": {},
+ "a2049.casalemedia.com.": {},
+ "a2051.casalemedia.com.": {},
+ "a2053.casalemedia.com.": {},
+ "a2054.casalemedia.com.": {},
+ "a2055.casalemedia.com.": {},
+ "a2056.casalemedia.com.": {},
+ "a2057.casalemedia.com.": {},
+ "a2058.casalemedia.com.": {},
+ "a2060.casalemedia.com.": {},
+ "a21018620252.cdn.optimizely.com.": {},
+ "a2561.casalemedia.com.": {},
+ "a2563.casalemedia.com.": {},
+ "a2566.casalemedia.com.": {},
+ "a2567.casalemedia.com.": {},
+ "a2568.casalemedia.com.": {},
+ "a2569.casalemedia.com.": {},
+ "a2570.casalemedia.com.": {},
+ "a2572.casalemedia.com.": {},
+ "a2573.casalemedia.com.": {},
+ "a2574.casalemedia.com.": {},
+ "a2575.casalemedia.com.": {},
+ "a2576.casalemedia.com.": {},
+ "a2577.casalemedia.com.": {},
+ "a2578.casalemedia.com.": {},
+ "a2579.casalemedia.com.": {},
+ "a2580.casalemedia.com.": {},
+ "a2581.casalemedia.com.": {},
+ "a2582.casalemedia.com.": {},
+ "a2583.casalemedia.com.": {},
+ "a2584.casalemedia.com.": {},
+ "a2585.casalemedia.com.": {},
+ "a2587.casalemedia.com.": {},
+ "a2588.casalemedia.com.": {},
+ "a2589.casalemedia.com.": {},
+ "a2590.casalemedia.com.": {},
+ "a2591.casalemedia.com.": {},
+ "a2592.casalemedia.com.": {},
+ "a2593.casalemedia.com.": {},
+ "a2594.casalemedia.com.": {},
+ "a2595.casalemedia.com.": {},
+ "a2596.casalemedia.com.": {},
+ "a2597.casalemedia.com.": {},
+ "a2598.casalemedia.com.": {},
+ "a2599.casalemedia.com.": {},
+ "a260.casalemedia.com.": {},
+ "a2600.casalemedia.com.": {},
+ "a2601.casalemedia.com.": {},
+ "a2602.casalemedia.com.": {},
+ "a2603.casalemedia.com.": {},
+ "a2605.casalemedia.com.": {},
+ "a2606.casalemedia.com.": {},
+ "a2607.casalemedia.com.": {},
+ "a2609.casalemedia.com.": {},
+ "a2610.casalemedia.com.": {},
+ "a2611.casalemedia.com.": {},
+ "a2612.casalemedia.com.": {},
+ "a2613.casalemedia.com.": {},
+ "a2614.casalemedia.com.": {},
+ "a2615.casalemedia.com.": {},
+ "a2616.casalemedia.com.": {},
+ "a2617.casalemedia.com.": {},
+ "a2618.casalemedia.com.": {},
+ "a2619.casalemedia.com.": {},
+ "a2620.casalemedia.com.": {},
+ "a2621.casalemedia.com.": {},
+ "a2622.casalemedia.com.": {},
+ "a2624.casalemedia.com.": {},
+ "a2625.casalemedia.com.": {},
+ "a2626.casalemedia.com.": {},
+ "a2627.casalemedia.com.": {},
+ "a2628.casalemedia.com.": {},
+ "a2629.casalemedia.com.": {},
+ "a2630.casalemedia.com.": {},
+ "a2631.casalemedia.com.": {},
+ "a2632.casalemedia.com.": {},
+ "a2633.casalemedia.com.": {},
+ "a2634.casalemedia.com.": {},
+ "a2635.casalemedia.com.": {},
+ "a2638.casalemedia.com.": {},
+ "a2639.casalemedia.com.": {},
+ "a2640.casalemedia.com.": {},
+ "a2641.casalemedia.com.": {},
+ "a2642.casalemedia.com.": {},
+ "a2643.casalemedia.com.": {},
+ "a2644.casalemedia.com.": {},
+ "a2646.casalemedia.com.": {},
+ "a2647.casalemedia.com.": {},
+ "a2648.casalemedia.com.": {},
+ "a2649.casalemedia.com.": {},
+ "a2650.casalemedia.com.": {},
+ "a2a5c7f9-3fa0-4182-889a-15aa61acf59b.prmutv.co.": {},
+ "a3.tuyacn.com.": {},
+ "a403.casalemedia.com.": {},
+ "a461.casalemedia.com.": {},
+ "a462.casalemedia.com.": {},
+ "a463.casalemedia.com.": {},
+ "a464.casalemedia.com.": {},
+ "a466.casalemedia.com.": {},
+ "a467.casalemedia.com.": {},
+ "a468.casalemedia.com.": {},
+ "a469.casalemedia.com.": {},
+ "a470.casalemedia.com.": {},
+ "a471.casalemedia.com.": {},
+ "a472.casalemedia.com.": {},
+ "a473.casalemedia.com.": {},
+ "a474.casalemedia.com.": {},
+ "a475.casalemedia.com.": {},
+ "a476.casalemedia.com.": {},
+ "a477.casalemedia.com.": {},
+ "a478.casalemedia.com.": {},
+ "a479.casalemedia.com.": {},
+ "a480.casalemedia.com.": {},
+ "a5552.casalemedia.com.": {},
+ "a5553.casalemedia.com.": {},
+ "a5554.casalemedia.com.": {},
+ "a5555.casalemedia.com.": {},
+ "a5556.casalemedia.com.": {},
+ "a5557.casalemedia.com.": {},
+ "a5558.casalemedia.com.": {},
+ "a5559.casalemedia.com.": {},
+ "a5560.casalemedia.com.": {},
+ "a5561.casalemedia.com.": {},
+ "a5562.casalemedia.com.": {},
+ "a5563.casalemedia.com.": {},
+ "a5564.casalemedia.com.": {},
+ "a5565.casalemedia.com.": {},
+ "a5566.casalemedia.com.": {},
+ "a5567.casalemedia.com.": {},
+ "a5568.casalemedia.com.": {},
+ "a5571.casalemedia.com.": {},
+ "a5572.casalemedia.com.": {},
+ "a5573.casalemedia.com.": {},
+ "a5574.casalemedia.com.": {},
+ "a5575.casalemedia.com.": {},
+ "a5576.casalemedia.com.": {},
+ "a5577.casalemedia.com.": {},
+ "a5578.casalemedia.com.": {},
+ "a5580.casalemedia.com.": {},
+ "a5582.casalemedia.com.": {},
+ "a5583.casalemedia.com.": {},
+ "a5584.casalemedia.com.": {},
+ "a5585.casalemedia.com.": {},
+ "a5586.casalemedia.com.": {},
+ "a5587.casalemedia.com.": {},
+ "a5588.casalemedia.com.": {},
+ "a5589.casalemedia.com.": {},
+ "a5590.casalemedia.com.": {},
+ "a5591.casalemedia.com.": {},
+ "a5592.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.": {},
+ "a5602.casalemedia.com.": {},
+ "a5604.casalemedia.com.": {},
+ "a5605.casalemedia.com.": {},
+ "a5606.casalemedia.com.": {},
+ "a5607.casalemedia.com.": {},
+ "a5608.casalemedia.com.": {},
+ "a5609.casalemedia.com.": {},
+ "a5610.casalemedia.com.": {},
+ "a5611.casalemedia.com.": {},
+ "a5612.casalemedia.com.": {},
+ "a5613.casalemedia.com.": {},
+ "a5614.casalemedia.com.": {},
+ "a5615.casalemedia.com.": {},
+ "a5617.casalemedia.com.": {},
+ "a5618.casalemedia.com.": {},
+ "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.": {},
+ "a5630.casalemedia.com.": {},
+ "a5631.casalemedia.com.": {},
+ "a5633.casalemedia.com.": {},
+ "a5634.casalemedia.com.": {},
+ "a5635.casalemedia.com.": {},
+ "a5636.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.": {},
+ "a5646.casalemedia.com.": {},
+ "a5647.casalemedia.com.": {},
+ "a5648.casalemedia.com.": {},
+ "a5649.casalemedia.com.": {},
+ "a5650.casalemedia.com.": {},
+ "a5651.casalemedia.com.": {},
+ "a5652.casalemedia.com.": {},
+ "a5653.casalemedia.com.": {},
+ "a5654.casalemedia.com.": {},
+ "a5655.casalemedia.com.": {},
+ "a5656.casalemedia.com.": {},
+ "a5657.casalemedia.com.": {},
+ "a5658.casalemedia.com.": {},
+ "a5659.casalemedia.com.": {},
+ "a5660.casalemedia.com.": {},
+ "a5661.casalemedia.com.": {},
+ "a5662.casalemedia.com.": {},
+ "a5663.casalemedia.com.": {},
+ "a5664.casalemedia.com.": {},
+ "a5665.casalemedia.com.": {},
+ "a5666.casalemedia.com.": {},
+ "a5667.casalemedia.com.": {},
+ "a5668.casalemedia.com.": {},
+ "a5669.casalemedia.com.": {},
+ "a5670.casalemedia.com.": {},
+ "a57.foxsports.com.": {},
+ "a578.casalemedia.com.": {},
+ "a5lng2-launches.appsflyersdk.com.": {},
+ "a9695278-4085-40b3-9f02-8d4c38a6ff01.prmutv.co.": {},
+ "aa-sync.quantummetric.com.": {},
+ "aa.online-metrix.net.": {},
+ "aa.quantummetric.com.": {},
+ "aaid.uyunad.com.": {},
+ "aarpsharex-my.sharepoint.com.": {},
+ "ab.qq.com.": {},
+ "abcsinsights.com.": {},
+ "abmmscloud-my.sharepoint.com.": {},
+ "abmmscloud.sharepoint.com.": {},
+ "abroad-lzd.apilocate.amap.com.": {},
+ "abroad.apilocate.amap.com.": {},
+ "academia.trendmd.com.": {},
+ "accela.com.": {},
+ "access.mp.lura.live.": {},
+ "account.box.com.": {},
+ "accountapi.agoda.com.": {},
+ "accounts.creditkarma.com.": {},
+ "accruent.com.": {},
+ "accscdn4public.m.taobao.com.": {},
+ "accurint.com.": {},
+ "ace.naver.com.": {},
+ "acgvideo.com.": {},
+ "achi.faceit.com.": {},
+ "acme-v02.api.letsencrypt.org.": {},
+ "acs4public.m.taobao.com.": {},
+ "activate.academy.com.": {},
+ "activation.cloud.techsmith.com.": {},
+ "acxiomapac.com.": {},
+ "ad.monetate.net.": {},
+ "ad.tencentmusic.com.": {},
+ "adapex.io.": {},
+ "adash-emas.cn-hangzhou.aliyuncs.com.": {},
+ "adash.man.aliyuncs.com.": {},
+ "adcell.com.": {},
+ "addictpodcast.com.": {},
+ "adguard.com.": {},
+ "adi-sin-c-4.cico.com.": {},
+ "adidas.com.multicdn.cloudinary.com.": {},
+ "adlook.me.": {},
+ "adm.wsms.haplat.net.": {},
+ "admatic.com.tr.": {},
+ "admbk.wsms.haplat.net.": {},
+ "admedo.com.": {},
+ "admin-fts.threekit.com.": {},
+ "adoric.com.": {},
+ "adquery.io.": {},
+ "adrs.org.cn.": {},
+ "adsco.re.": {},
+ "adtarget.market.": {},
+ "advantage.purpleguys.com.": {},
+ "advantage2.purpleguys.com.": {},
+ "adventisthealthwest-my.sharepoint.com.": {},
+ "adventisthealthwest.sharepoint.com.": {},
+ "adview.com.": {},
+ "advlion.com.": {},
+ "adx-sg-req.anythinktech.com.": {},
+ "adxinspidsp.com.": {},
+ "aepenergy-my.sharepoint.com.": {},
+ "aepenergy.sharepoint.com.": {},
+ "af.monetate.net.": {},
+ "afss.zhiqinyun.cn.": {},
+ "agent.marketingcloudfx.com.": {},
+ "agoda-my.sharepoint.com.": {},
+ "agt.p.360.cn.": {},
+ "agwp1.arbigo.com.": {},
+ "aicdn.com.": {},
+ "aid.send.microad.jp.": {},
+ "aidata.io.": {},
+ "aikenschools-my.sharepoint.com.": {},
+ "aikenschools.sharepoint.com.": {},
+ "ailunshenghuo.com.": {},
+ "ain1-my.sharepoint.com.": {},
+ "aiq-in.delish.com.": {},
+ "aiq-in.goodhousekeeping.com.": {},
+ "airasia.com.": {},
+ "airasia.gw-dv.vip.": {},
+ "aircanada.com.": {},
+ "airtame.com.": {},
+ "airtory.com.": {},
+ "aisee.tv.": {},
+ "ajcloud.net.": {},
+ "akronchildrens-my.sharepoint.com.": {},
+ "akronchildrens.sharepoint.com.": {},
+ "akulaku.com.": {},
+ "alarm.wsms.haplat.net.": {},
+ "alarmbk.wsms.haplat.net.": {},
+ "alarmnet.com.": {},
+ "albany.remotepc.com.": {},
+ "album-sg01a.ocloud.heytapmobi.com.": {},
+ "ali-picture-private-sg.oss-ap-southeast-1.aliyuncs.com.": {},
+ "ali-sgp-messpush.meetyouintl.com.": {},
+ "alialarm-sgp.oss-ap-southeast-1.aliyuncs.com.": {},
+ "alibaba-inc.com.": {},
+ "alibabachengdun.com.": {},
+ "alibabacorp.com.": {},
+ "alibabausercontent.com.": {},
+ "alicdn.com.": {},
+ "aliexpress.ru.": {},
+ "aliexpress.us.": {},
+ "alikunlun.com.": {},
+ "alikunlun.net.": {},
+ "aliyun.com.": {},
+ "aliyuncs.com.": {},
+ "allieduniversalservices-my.sharepoint.com.": {},
+ "alog-default.umeng.com.": {},
+ "am1.bumbcdn.com.": {},
+ "am3pap002.storage.live.com.": {},
+ "am3pap003.storage.live.com.": {},
+ "am3pap004.storage.live.com.": {},
+ "am3pap005.storage.live.com.": {},
+ "am3pap006.storage.live.com.": {},
+ "am3pap007.storage.live.com.": {},
+ "am4pap001.storage.live.com.": {},
+ "ama-app.quantummetric.com.": {},
+ "amap.com.": {},
+ "amc-theatres-res.cloudinary.com.": {},
+ "amcore-ens.rest.gti.trellix.com.": {},
+ "amd.com.": {},
+ "amdc-wapa.lazada.com.": {},
+ "amdc.lazada.com.": {},
+ "amdcopen.m.taobao.com.": {},
+ "amdcopen.m.taobao.com.gds.alibabadns.com.": {},
+ "americanchemicalsociety-my.sharepoint.com.": {},
+ "amp-account.apps.apple.com.": {},
+ "amp.dsg.bio.": {},
+ "amp.namequery.com.": {},
+ "amp.permutive.com.": {},
+ "ams-itm-radar-testobject.citrix.com.": {},
+ "ams02pap001.storage.live.com.": {},
+ "ams03pap001.storage.live.com.": {},
+ "ams03pap002.storage.live.com.": {},
+ "ams03pap003.storage.live.com.": {},
+ "ams03pap004.storage.live.com.": {},
+ "ams03pap005.storage.live.com.": {},
+ "ams06pap001.storage.live.com.": {},
+ "amsterdam.remotepc.com.": {},
+ "an.apps.seabroadnet.com.": {},
+ "analytics-2.athome.com.": {},
+ "analytics-batch.blitz.gg.": {},
+ "analytics-debugger.com.": {},
+ "analytics-ingress-global.bitmovin.com.": {},
+ "analytics.apps.seabroadnet.com.": {},
+ "analytics.belk.com.": {},
+ "analytics.coachoutlet.com.": {},
+ "analytics.duluthtrading.com.": {},
+ "analytics.fdown.net.": {},
+ "analytics.getshogun.com.": {},
+ "analytics.gnc.com.": {},
+ "analytics.katespade.com.": {},
+ "analytics.katespadeoutlet.com.": {},
+ "analytics.languagetoolplus.com.": {},
+ "analytics.qumucloud.com.": {},
+ "analytics.trovit.com.": {},
+ "android.crashsight.wetest.net.": {},
+ "ankara.remotepc.com.": {},
+ "anlian.co.": {},
+ "announce.torrentsmd.com.": {},
+ "anonymous.ads.brave.com.": {},
+ "ansellhealthcare-my.sharepoint.com.": {},
+ "ansys.sharepoint.com.": {},
+ "anthem-app.quantummetric.com.": {},
+ "anthropic.com.": {},
+ "anxbrylkt.accounts.ondemand.com.": {},
+ "anzuinfra.com.": {},
+ "aon.com.": {},
+ "ap-southeast-1.log.aliyuncs.com.": {},
+ "apcloud.xyz.": {},
+ "api-asyncgw-gcc-teams.usgovtrafficmanager.net.": {},
+ "api-eu1.hubspot.com.": {},
+ "api-glb-aapne1a.smoot.apple.com.": {},
+ "api-glb-aapne1c.smoot.apple.com.": {},
+ "api-glb-aaps1b.smoot.apple.com.": {},
+ "api-glb-aapse1c.smoot.apple.com.": {},
+ "api-glb-aeuc1a.smoot.apple.com.": {},
+ "api-glb-aeuc1b.smoot.apple.com.": {},
+ "api-glb-aeuw1b.smoot.apple.com.": {},
+ "api-glb-aeuw3b.smoot.apple.com.": {},
+ "api-glb-aeuw3c.smoot.apple.com.": {},
+ "api-glb-asae1b.smoot.apple.com.": {},
+ "api-glb-ause1a.smoot.apple.com.": {},
+ "api-glb-ause1b.smoot.apple.com.": {},
+ "api-glb-ause1c.smoot.apple.com.": {},
+ "api-glb-ause2a.smoot.apple.com.": {},
+ "api-glb-ause2b.smoot.apple.com.": {},
+ "api-glb-ause2c.smoot.apple.com.": {},
+ "api-glb-ausw2b.smoot.apple.com.": {},
+ "api-glb-ausw2c.smoot.apple.com.": {},
+ "api-mayi.django.t.taobao.com.": {},
+ "api-php1-ovh.keepsolid.com.": {},
+ "api-php2-ovh.keepsolid.com.": {},
+ "api-php3-ovh.keepsolid.com.": {},
+ "api-php4-ovh.keepsolid.com.": {},
+ "api-php8-ovh.keepsolid.com.": {},
+ "api-pos.titank12.com.": {},
+ "api-preview.luckyorange.com.": {},
+ "api-sec-us.didiglobal.com.": {},
+ "api-sh.django.t.taobao.com.gds.alibabadns.com.": {},
+ "api-v2.chimebank.com.": {},
+ "api.abcsinsights.com.": {},
+ "api.adquery.io.": {},
+ "api.ams.gcc.teams.microsoft.com.": {},
+ "api.asm.skype.com.": {},
+ "api.aty7j.com.": {},
+ "api.bosch-wdw.com.": {},
+ "api.box.com.": {},
+ "api.btloader.com.": {},
+ "api.bytello.com.": {},
+ "api.cloud.tenda.com.cn.": {},
+ "api.cnxdserv.com.": {},
+ "api.config-security.com.": {},
+ "api.crobox.com.": {},
+ "api.crush.163.com.": {},
+ "api.dashlane.com.": {},
+ "api.deepl.com.": {},
+ "api.do.buienalarm.nl.": {},
+ "api.eagllwin.com.": {},
+ "api.easeus.com.": {},
+ "api.foxnews.com.": {},
+ "api.gleap.io.": {},
+ "api.gravitec.media.": {},
+ "api.grow.me.": {},
+ "api.icalendars.app.": {},
+ "api.inboxsdk.com.": {},
+ "api.k.163.com.": {},
+ "api.lightboxcdn.com.": {},
+ "api.lytics.io.": {},
+ "api.moyoung.com.": {},
+ "api.mrg.agency.": {},
+ "api.my.jbi.global.": {},
+ "api.onedrive.com.": {},
+ "api.oxcok.com.": {},
+ "api.permutive.app.": {},
+ "api.permutive.com.": {},
+ "api.pgf-asw0zz.com.": {},
+ "api.playlnk.io.": {},
+ "api.popin.cc.": {},
+ "api.pushbullet.com.": {},
+ "api.queryly.com.": {},
+ "api.rethinkad.com.": {},
+ "api.reverso.net.": {},
+ "api.rewards.brave.com.": {},
+ "api.ringcentral.biz.": {},
+ "api.rollbar.com.": {},
+ "api.sbz.vn.": {},
+ "api.smartdeploy.com.": {},
+ "api.snapchat.com.": {},
+ "api.streak.com.": {},
+ "api.swishapps.ai.": {},
+ "api.t3be3280.pw.adn.cloud.": {},
+ "api.trackpush.com.": {},
+ "api.transitapp.com.": {},
+ "api.tuisong.baidu.com.": {},
+ "api.ultimaker.com.": {},
+ "api.ultimate-guitar.com.": {},
+ "api.unity.com.": {},
+ "api.userlike.com.": {},
+ "api.vfipayna2.com.": {},
+ "api.vieon.vn.": {},
+ "api.workjam.com.": {},
+ "api000.backblazeb2.com.": {},
+ "api001.backblazeb2.com.": {},
+ "api002.backblazeb2.com.": {},
+ "api004.backblazeb2.com.": {},
+ "api3.aoneroom.com.": {},
+ "apiisgp.ezvizlife.com.": {},
+ "apiisgp.hik-connect.com.": {},
+ "apis.discover.com.": {},
+ "apituner.ecbsn.com.": {},
+ "apk.v-mate.mobi.": {},
+ "apk.vidmate.net.": {},
+ "apne.pp.riotgames.com.": {},
+ "app-atl.five9.com.": {},
+ "app-eu1.hubspot.com.": {},
+ "app-goal.com.": {},
+ "app-scl.five9.com.": {},
+ "app.adoric-om.com.": {},
+ "app.box.com.": {},
+ "app.cart-bot.net.": {},
+ "app.cybba.solutions.": {},
+ "app.exitbee.com.": {},
+ "app.grow.me.": {},
+ "app.paces.jbi.global.": {},
+ "app.pendo.qgenda.com.": {},
+ "app.qbo.intuit.com.": {},
+ "app.retinavue.net.": {},
+ "app.sealsubscriptions.com.": {},
+ "app.snapchat.com.": {},
+ "app.talkjs.com.": {},
+ "app.trustev.com.": {},
+ "app.wdesk.com.": {},
+ "app.ynab.com.": {},
+ "app.zoom.us.": {},
+ "appcafe.gtm.starbucks.com.": {},
+ "appcafe.starbucks.com.": {},
+ "appconf.mail.163.com.": {},
+ "appcontent.collegeboard.org.": {},
+ "appgrowth.com.": {},
+ "appicad.net.": {},
+ "appleid.apple.com.": {},
+ "apponline.research.qq.com.": {},
+ "apps.wix.com.": {},
+ "appstoreonrt-stsdk.vivo.com.cn.": {},
+ "appstoreort-stsdk.vivo.com.cn.": {},
+ "apptrack.wrike.com.": {},
+ "apv-launcher.minute.ly.": {},
+ "apv-static.minute.ly.": {},
+ "apv-static.tldw.me.": {},
+ "ar2fnu-launches.appsflyersdk.com.": {},
+ "arbigo.com.": {},
+ "ardenthealthservices-my.sharepoint.com.": {},
+ "arenabg.com.": {},
+ "aristotleinsight.com.": {},
+ "arkdhs-my.sharepoint.com.": {},
+ "arkrecode.ero-labs.website.": {},
+ "armh-my.sharepoint.com.": {},
+ "armh.sharepoint.com.": {},
+ "arms-retcode-sg.aliyuncs.com.": {},
+ "arms-retcode.aliyuncs.com.": {},
+ "armstrongceilings-my.sharepoint.com.": {},
+ "arnoldclark1-my.sharepoint.com.": {},
+ "arthrex-my.sharepoint.com.": {},
+ "arup-my.sharepoint.com.": {},
+ "as-api.asm.skype.com.": {},
+ "as-prod.asyncgw.teams.microsoft.com.": {},
+ "as.xiaohongshu.com.": {},
+ "asheville.remotepc.com.": {},
+ "asia-001.azure-apim.net.": {},
+ "asia-adlog.vivoglobal.com.": {},
+ "asia-album-api.vivoglobal.com.": {},
+ "asia-assistant-sports.vivoglobal.com.": {},
+ "asia-browser.vivoglobal.com.": {},
+ "asia-browserplat.vivoglobal.com.": {},
+ "asia-config-appstore.vivoglobal.com.": {},
+ "asia-cota.vivoglobal.com.": {},
+ "asia-ex-adlog.vivoglobal.com.": {},
+ "asia-exmagazineunlock-proxy.vivoglobal.com.": {},
+ "asia-f-up.vivoglobal.com.": {},
+ "asia-gamecenter.vivoglobal.com.": {},
+ "asia-gsearch.vivoglobal.com.": {},
+ "asia-magazineunlock.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-sms-api.vivoglobal.com.": {},
+ "asia-st-romsp.vivoglobal.com.": {},
+ "asia-st-sl.vivoglobal.com.": {},
+ "asia-st-sysupgrade.vivoglobal.com.": {},
+ "asia-stp.vivoglobal.com.": {},
+ "asia-theme-api.vivoglobal.com.": {},
+ "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-weather.vivoglobal.com.": {},
+ "asia-wifi.vivoglobal.com.": {},
+ "asia.remotepc.com.": {},
+ "askprivate.com.": {},
+ "asm-api-golocal-geo-am-teams.trafficmanager.net.": {},
+ "asm-api-golocal-geo-as-teams.trafficmanager.net.": {},
+ "asm-api-golocal-geo-eu-teams.trafficmanager.net.": {},
+ "asm-api-golocal-geo-uk-teams.trafficmanager.net.": {},
+ "asm-api-prod-geo-am-skype.trafficmanager.net.": {},
+ "asm-api-prod-geo-as-skype.trafficmanager.net.": {},
+ "asm-api-prod-geo-eu-skype.trafficmanager.net.": {},
+ "asm-api-prod-version-skype.trafficmanager.net.": {},
+ "aspect-upush.umeng.com.": {},
+ "asset.fwcdn3.com.": {},
+ "asset.fwpub1.com.": {},
+ "assets.adobedtm.com.": {},
+ "assets.api.stairwell.com.": {},
+ "assets.apps.pa.gov.": {},
+ "assets.bale.ai.": {},
+ "assets.connatix.com.": {},
+ "assets.fastly.carvana.io.": {},
+ "assets.staples-static.com.": {},
+ "assets.the-independent.com.": {},
+ "assets0.procore.com.": {},
+ "assets1.procore.com.": {},
+ "assets2.procore.com.": {},
+ "assets3.procore.com.": {},
+ "assistant-dre.op.hicloud.com.": {},
+ "astat.bugly.qcloud.com.": {},
+ "async-motiondetection-us-1d.oss-us-west-1.aliyuncs.com.": {},
+ "asyncim.zoom.us.": {},
+ "atemda.com.": {},
+ "ati.com.": {},
+ "atipt-my.sharepoint.com.": {},
+ "atipt.sharepoint.com.": {},
+ "atlanta.remotepc.com.": {},
+ "atlanta2.remotepc.com.": {},
+ "atlanta3.remotepc.com.": {},
+ "atlanta4.remotepc.com.": {},
+ "atriaseniorliving-my.sharepoint.com.": {},
+ "au-prod.asyncgw.teams.microsoft.com.": {},
+ "auckland.remotepc.com.": {},
+ "audid.umeng.com.": {},
+ "audio.vivintsky.com.": {},
+ "audiostatlog.cc.easebar.com.": {},
+ "auth-asg-en.aliyuncs.com.": {},
+ "auth.bereal.team.": {},
+ "auth.hulu.com.": {},
+ "auth.ichano.cn.": {},
+ "auth.jbisumari.org.": {},
+ "automate.itsasap.com.": {},
+ "autonavi.com.": {},
+ "avalanche.autotrader.co.uk.": {},
+ "avn.innity.com.": {},
+ "avs-alexa-14-na.amazon.com.": {},
+ "axiom.co.": {},
+ "az1.qualtrics.com.": {},
+ "azchicago.remotepc.com.": {},
+ "azchicago2.remotepc.com.": {},
+ "azdcs-my.sharepoint.com.": {},
+ "azioncdn.net.": {},
+ "azmatch.adsrvr.org.": {},
+ "azureford-my.sharepoint.com.": {},
+ "azureford.sharepoint.com.": {},
+ "b.ns.c10r.facebook.com.": {},
+ "b.ns.cdn.whatsapp.net.": {},
+ "b.ns.xx.fbcdn.net.": {},
+ "b.tile.osm.org.": {},
+ "bablosoft.com.": {},
+ "backend-l.deepl.com.": {},
+ "badambiz.com.": {},
+ "badoo.app.": {},
+ "bahrain.remotepc.com.": {},
+ "baishan-cloud.net.": {},
+ "bale.ai.": {},
+ "ballstate-my.sharepoint.com.": {},
+ "baltimore.remotepc.com.": {},
+ "bam.nr-data.net.cdn.cloudflare.net.": {},
+ "bangalore2.remotepc.com.": {},
+ "bangalore3.remotepc.com.": {},
+ "bangalore4.remotepc.com.": {},
+ "bangkok.remotepc.com.": {},
+ "bankozarks-my.sharepoint.com.": {},
+ "bankwithunited-my.sharepoint.com.": {},
+ "bankwithunited.sharepoint.com.": {},
+ "baptisthealth-my.sharepoint.com.": {},
+ "baptisthealth.sharepoint.com.": {},
+ "barstoolsports.com.": {},
+ "basised-my.sharepoint.com.": {},
+ "basspro-app.quantummetric.com.": {},
+ "basspro-my.sharepoint.com.": {},
+ "basspro.sharepoint.com.": {},
+ "batch.pbisrewards.com.": {},
+ "bb.eight-cdn.com.": {},
+ "bbw-app.quantummetric.com.": {},
+ "bd1cec50-00d1-4ce9-9572-785857419a1e.prmutv.co.": {},
+ "beacon-api.aliyuncs.com.": {},
+ "beacon.qq.com.": {},
+ "beacons4.gvt2.com.": {},
+ "beacons5.gvt2.com.": {},
+ "becn-my.sharepoint.com.": {},
+ "becn.sharepoint.com.": {},
+ "becpsn-my.sharepoint.com.": {},
+ "becpsn.sharepoint.com.": {},
+ "beizi.biz.": {},
+ "belgium.remotepc.com.": {},
+ "belgrad.remotepc.com.": {},
+ "bend.remotepc.com.": {},
+ "bergankdv.com.": {},
+ "bgtfs.transitapp.com.": {},
+ "bidder.adquery.io.": {},
+ "bifrost.vivaldi.com.": {},
+ "bik.gov.tr.": {},
+ "biomerieux-my.sharepoint.com.": {},
+ "biomerieux.sharepoint.com.": {},
+ "bisdus-my.sharepoint.com.": {},
+ "bitwarden.com.": {},
+ "bkdllp-my.sharepoint.com.": {},
+ "bl.listrakbi.com.cdn.cloudflare.net.": {},
+ "bl6pap003.storage.live.com.": {},
+ "bl6pap003files.storage.live.com.": {},
+ "bl6pap004.storage.live.com.": {},
+ "bl6pap004files.storage.live.com.": {},
+ "black-cat.crypto.com.": {},
+ "blackboard.com.": {},
+ "blackbox.dropbox-dns.com.": {},
+ "blk-app.com.": {},
+ "blocksi-screenshots-us.s3.us-east-005.backblazeb2.com.": {},
+ "blooket.com.multicdn.cloudinary.com.": {},
+ "bloombergdfpcw4670778881.s.moatpixel.com.": {},
+ "blueshieldca.sharepoint.com.": {},
+ "bluffdale.remotepc.com.": {},
+ "blz04pap001.storage.live.com.": {},
+ "blz04pap001files.storage.live.com.": {},
+ "blz04pap002.storage.live.com.": {},
+ "blz04pap002files.storage.live.com.": {},
+ "blz04pap003.storage.live.com.": {},
+ "blz04pap003files.storage.live.com.": {},
+ "blz04pap004.storage.live.com.": {},
+ "blz04pap005.storage.live.com.": {},
+ "blz04pap005files.storage.live.com.": {},
+ "blz04pap006.storage.live.com.": {},
+ "blz04pap006files.storage.live.com.": {},
+ "bmaus.bumble.com.": {},
+ "bmoolbb-app.quantummetric.com.": {},
+ "bmrn-my.sharepoint.com.": {},
+ "bmrn.sharepoint.com.": {},
+ "bn02pap001.storage.live.com.": {},
+ "bn02pap001files.storage.live.com.": {},
+ "bn02pap090files.storage.live.com.": {},
+ "bn1ap000files.storage.live.com.": {},
+ "bn1files.storage.live.com.": {},
+ "bn3pap090files.storage.live.com.": {},
+ "bna365-my.sharepoint.com.": {},
+ "bnz05pap001.storage.live.com.": {},
+ "bnz05pap001files.storage.live.com.": {},
+ "bnz05pap002.storage.live.com.": {},
+ "bnz06pap001.storage.live.com.": {},
+ "bnz06pap001files.storage.live.com.": {},
+ "bnz06pap002.storage.live.com.": {},
+ "bnz06pap002files.storage.live.com.": {},
+ "bnz06pap003.storage.live.com.": {},
+ "bnz06pap003files.storage.live.com.": {},
+ "bnz06pap004.storage.live.com.": {},
+ "bnz06pap004files.storage.live.com.": {},
+ "bnz07pap001.storage.live.com.": {},
+ "bnz07pap001files.storage.live.com.": {},
+ "books-analytics-events.apple.com.": {},
+ "books-personalization-server.apple.com.": {},
+ "booksy.com.": {},
+ "bootstrapcdn.com.": {},
+ "borgwarner-my.sharepoint.com.": {},
+ "borgwarner.sharepoint.com.": {},
+ "bosecorp-my.sharepoint.com.": {},
+ "boston.remotepc.com.": {},
+ "boston2.remotepc.com.": {},
+ "bostonglobe.com.": {},
+ "box.com.": {},
+ "box.net.": {},
+ "boxcloud.com.": {},
+ "br.chat.si.riotgames.com.": {},
+ "brand-assets.ringcentral.com.": {},
+ "bratislava.remotepc.com.": {},
+ "breitbart.com.": {},
+ "brenntag01-my.sharepoint.com.": {},
+ "brenntag01.sharepoint.com.": {},
+ "brenntag01nam-my.sharepoint.com.": {},
+ "bridge2bwb.com.": {},
+ "broadstreetads.com.": {},
+ "brocktonpublicschools-my.sharepoint.com.": {},
+ "bryantu-my.sharepoint.com.": {},
+ "bscedge.com.": {},
+ "bshr.ezodn.com.": {},
+ "bsprings.remotepc.com.": {},
+ "bt.moack.co.kr.": {},
+ "btrace.qq.com.": {},
+ "bucharest.remotepc.com.": {},
+ "bucharest1.remotepc.com.": {},
+ "budapest.remotepc.com.": {},
+ "buenosaires.remotepc.com.": {},
+ "bugly.qcloud.com.": {},
+ "bugreport.huorong.cn.": {},
+ "bundler.nice-team.net.": {},
+ "bunny.net.": {},
+ "burningmay.info.": {},
+ "burnsmcd-my.sharepoint.com.": {},
+ "burnsmcd.sharepoint.com.": {},
+ "business.apple.com.": {},
+ "bw1-my.sharepoint.com.": {},
+ "bw1.sharepoint.com.": {},
+ "bwatch.bergankdv.com.": {},
+ "bytecdn.cn.": {},
+ "bytedance.net.": {},
+ "bytello.com.": {},
+ "c.4dex.io.": {},
+ "c.fakespot.io.": {},
+ "c.footprint.net.": {},
+ "c.ns.c10r.facebook.com.": {},
+ "c.ns.xx.fbcdn.net.": {},
+ "c.pub.network.": {},
+ "c.tile.openstreetmap.org.": {},
+ "c1.ttcache.com.": {},
+ "c104216-dcdn.mp.lura.live.": {},
+ "c2.ttcache.com.": {},
+ "c2c.wechat.com.": {},
+ "c3.ttcache.com.": {},
+ "c4.ttcache.com.": {},
+ "c4cfly.rbxcdn.com.": {},
+ "c4cstatic.hana.ondemand.com.": {},
+ "c7l.cyberhaven.io.": {},
+ "ca-prod.asyncgw.teams.microsoft.com.": {},
+ "ca.gov.": {},
+ "ca000.backblaze.com.": {},
+ "ca001.backblaze.com.": {},
+ "ca002.backblaze.com.": {},
+ "ca80a1adb12a4fbdac5ffcbc944e9a61.pacloudflare.com.": {},
+ "cachenetworks.com.": {},
+ "cachingserviceapimpme.azure-api.net.": {},
+ "caeportal-my.sharepoint.com.": {},
+ "cainiao.com.": {},
+ "caixa.gov.br.": {},
+ "cajacr-my.sharepoint.com.": {},
+ "cajacr.sharepoint.com.": {},
+ "california.remotepc.com.": {},
+ "california2.remotepc.com.": {},
+ "caltech.edu.": {},
+ "camoe.cn.": {},
+ "camsoda.com.": {},
+ "canada.remotepc.com.": {},
+ "canberra.remotepc.com.": {},
+ "capetown.remotepc.com.": {},
+ "car-cloud-cn.net.": {},
+ "cardiff.remotepc.com.": {},
+ "cardinalcorp-my.sharepoint.com.": {},
+ "caridisini.site.": {},
+ "carystudio.com.": {},
+ "cat.hbwrapper.com.": {},
+ "cat2.hbwrapper.com.": {},
+ "cavai.com.": {},
+ "cc.trendmd.co.": {},
+ "ccboemd-my.sharepoint.com.": {},
+ "cchmc-my.sharepoint.com.": {},
+ "cchmc.sharepoint.com.": {},
+ "ccs.umeng.com.": {},
+ "ccsyncuuid.net.": {},
+ "cd.xdplt.com.": {},
+ "cdn-audio-gcp-media.getepic.com.": {},
+ "cdn-gcp-media-drm.getepic.com.": {},
+ "cdn-gcp.getepic.com.": {},
+ "cdn-goo.bereal.com.": {},
+ "cdn-resize.bereal.network.": {},
+ "cdn-sic.33across.com.": {},
+ "cdn-static.hulu.com.": {},
+ "cdn-t.vb17123filippaaniketos.pw.": {},
+ "cdn-us.algoliaradar.com.": {},
+ "cdn-us1.bereal.network.": {},
+ "cdn-video-gcp-media.getepic.com.": {},
+ "cdn.adjust.com.": {},
+ "cdn.adlook.me.": {},
+ "cdn.arstechnica.net.": {},
+ "cdn.bereal.network.": {},
+ "cdn.conveythis.com.": {},
+ "cdn.doubleverify.com.": {},
+ "cdn.fera.ai.": {},
+ "cdn.ftd.agency.": {},
+ "cdn.getambassador.com.": {},
+ "cdn.groupbycloud.com.": {},
+ "cdn.hubblecontent.osi.office.net.": {},
+ "cdn.instapagemetrics.com.": {},
+ "cdn.izooto.com.": {},
+ "cdn.knightlab.com.": {},
+ "cdn.md-ace-b.online.": {},
+ "cdn.ndtv.com.": {},
+ "cdn.qq.com.": {},
+ "cdn.rebuyengine.com.": {},
+ "cdn.sierrapacificgroup.com.": {},
+ "cdn.skcrtxr.com.": {},
+ "cdn.special-offers.online.": {},
+ "cdn.support.services.microsoft.com.": {},
+ "cdn.tapdb-dev.com.": {},
+ "cdn.theathletic.com.": {},
+ "cdn.uxfeedback.ru.": {},
+ "cdn0.wixdns.net.": {},
+ "cdn07.boxcdn.net.": {},
+ "cdn1.iconfinder.com.": {},
+ "cdn1.parksmedia.wdprapps.disney.com.": {},
+ "cdn1.wixdns.net.": {},
+ "cdnbye.com.": {},
+ "cdnetworks.net.": {},
+ "cdngarenanow-a.akamaihd.net.": {},
+ "cdngslb.com.": {},
+ "cdntm.hsbc.co.uk.": {},
+ "cdnwidget.com.": {},
+ "cds.apple.com.": {},
+ "cds.swishapps.ai.": {},
+ "cdt.ca.gov.": {},
+ "cedexis-test.com.": {},
+ "cedock.com.": {},
+ "centertime.ksyun.com.": {},
+ "centralmichigan-my.sharepoint.com.": {},
+ "centrexit.com.": {},
+ "cfa.fidelity.com.": {},
+ "changehealthcare.com.": {},
+ "charlotte.remotepc.com.": {},
+ "chat.openai.com.": {},
+ "check.unity.cn.": {},
+ "check2.lloydsbank.co.uk.": {},
+ "chennai.remotepc.com.": {},
+ "chi01pap001.storage.live.com.": {},
+ "chi01pap001files.storage.live.com.": {},
+ "chi01pap002.storage.live.com.": {},
+ "chi01pap002files.storage.live.com.": {},
+ "chicago.remotepc.com.": {},
+ "chicago2.remotepc.com.": {},
+ "chinacache.net.": {},
+ "chinaccl-a.haplat.net.": {},
+ "chinaccl-b.haplat.net.": {},
+ "chinaccl-c.haplat.net.": {},
+ "chippewavalley-my.sharepoint.com.": {},
+ "chippewavalley.sharepoint.com.": {},
+ "choctawnationofoklahoma-my.sharepoint.com.": {},
+ "chopo-my.sharepoint.com.": {},
+ "chtsite.com.": {},
+ "chubbgroup-my.sharepoint.com.": {},
+ "chubbgroup.sharepoint.com.": {},
+ "chumley.barstoolsports.com.": {},
+ "cico.com.": {},
+ "cinarra.com.": {},
+ "cintas1-my.sharepoint.com.": {},
+ "cintas1.sharepoint.com.": {},
+ "cis.citrix.com.": {},
+ "cisco-my.sharepoint.com.": {},
+ "cisco.app.box.com.": {},
+ "cisco.sharepoint.com.": {},
+ "citrix.com.": {},
+ "cityofelpaso-my.sharepoint.com.": {},
+ "ckmap.mediav.com.": {},
+ "cl-data.ads.heytapmobi.com.": {},
+ "cl2009.com.": {},
+ "claude.ai.": {},
+ "clearesult5-my.sharepoint.com.": {},
+ "clearesult5.sharepoint.com.": {},
+ "cleveland.remotepc.com.": {},
+ "click.e.petco.com.": {},
+ "click.victoriassecret.com.": {},
+ "client-log.box.com.": {},
+ "client-strings.xboxlive.com.": {},
+ "client-tracking.omiapp.me.": {},
+ "client-updates.lumu.io.": {},
+ "cliftonlarsonallen.sharepoint.com.": {},
+ "cloud-rest.lenovomm.com.": {},
+ "cloud.browser.360.cn.": {},
+ "cloud.ibm.com.": {},
+ "cloud.typenetwork.com.": {},
+ "cloud.vmp.onezapp.com.": {},
+ "cloud.zoom.us.": {},
+ "cloudauth-device.aliyuncs.com.": {},
+ "clouddata.turbowarp.org.": {},
+ "cloudlinks.cn.": {},
+ "cloudsvc.policypak.com.": {},
+ "cloverparksd-my.sharepoint.com.": {},
+ "cm-x.mgid.com.": {},
+ "cmgate.vip.qq.com.": {},
+ "cmpassport.com.": {},
+ "cms.ejoyspace.com.": {},
+ "cms.thescore.com.": {},
+ "cn-beijing.log.aliyuncs.com.": {},
+ "cn-hangzhou.log.aliyuncs.com.": {},
+ "cn-hangzhou.oss-cdn.aliyun-inc.com.": {},
+ "cn-hongkong.log.aliyuncs.com.": {},
+ "cn-shanghai.log.aliyuncs.com.": {},
+ "cn.gcloudcs.com.": {},
+ "cn.mvconf.50union.com.": {},
+ "cname.pendo.io.": {},
+ "cnmc.sharepoint.com.": {},
+ "cnv2.mclean.50union.com.": {},
+ "cnzz.com.": {},
+ "co.amx.rcs.telephony.goog.": {},
+ "cocoapi.bmwgroup.com.": {},
+ "cocoapi.bmwgroup.us.": {},
+ "code.etracker.com.": {},
+ "codepush.akulaku.net.": {},
+ "codis-bak.ngb.haplat.net.": {},
+ "codis.ngb.haplat.net.": {},
+ "cofile.net.": {},
+ "collect-v6.51.la.": {},
+ "collector.wdp.brave.com.": {},
+ "colliervilleschools-my.sharepoint.com.": {},
+ "columbus.remotepc.com.": {},
+ "columbusk12oh-my.sharepoint.com.": {},
+ "columbusk12oh.sharepoint.com.": {},
+ "com-healthgrade.netmng.com.": {},
+ "com.multicdn.cloudinary.com.": {},
+ "com.yangyi19.com.": {},
+ "com.zjgslb.com.": {},
+ "cometglobal.cf.t3cloud.pb.com.": {},
+ "cometservd1.pb.com.": {},
+ "commerce.safe.360.cn.": {},
+ "commercial.ocsp.identrust.com.": {},
+ "common-afd.fe.1drv.com.": {},
+ "common-afdrk.fe.1drv.com.": {},
+ "common.xshareapp.com.": {},
+ "company.rt.ru.": {},
+ "compiles.overleafusercontent.com.": {},
+ "computerhelpnj.com.": {},
+ "comserver.eu1.mspa.n-able.com.": {},
+ "config-inmobi-comtm.trafficmanager.net.": {},
+ "config-junion.jpush.cn.": {},
+ "config-security.com.": {},
+ "config.a-m-p.xyz.": {},
+ "config.inmobi.com.": {},
+ "config.office.net.": {},
+ "config.reasonsecurity.com.": {},
+ "config.y5en.com.": {},
+ "configdl.teamviewer.com.": {},
+ "conn-service-cn-03.allawntech.com.": {},
+ "connect.garenanow.com.": {},
+ "consentcdn.cookiebot.com.": {},
+ "consist.co.il.": {},
+ "constel1-my.sharepoint.com.": {},
+ "content.citizensbankonline.com.": {},
+ "content.discover.com.": {},
+ "content.discovercard.com.": {},
+ "content.ebanking-services.com.": {},
+ "content.gap.com.": {},
+ "content.ibanking-services.com.": {},
+ "content.maxconnector.com.": {},
+ "content.production.cdn.art19.com.": {},
+ "content.ssctech.com.": {},
+ "content.usaa.com.": {},
+ "content22.bancanet.banamex.com.": {},
+ "content22.bmo.com.": {},
+ "content22.citibankonline.com.": {},
+ "content22.citicards.com.": {},
+ "content22.online.citi.com.": {},
+ "context.reverso.net.": {},
+ "control-out.mna.qq.com.": {},
+ "conversion-assistant.apps.seabroadnet.com.": {},
+ "conveythis.com.": {},
+ "cookie-sync.bidmyadz.com.": {},
+ "coolkit.cc.": {},
+ "copenhagen.remotepc.com.": {},
+ "cordial.com.": {},
+ "core.cdn.procore.com.": {},
+ "core.gssv-play-prod.xboxlive.com.": {},
+ "core.iprom.net.": {},
+ "core.omiapp.me.": {},
+ "coresite.mm.fcix.net.": {},
+ "corndel.aptem.co.uk.": {},
+ "cpm.aserve1.net.": {},
+ "cpm.qortex.ai.": {},
+ "cpm.vuukle.net.": {},
+ "cpm.xrtb.io.": {},
+ "cpr-pusa01.app.blackbaud.net.": {},
+ "cpx-research.com.": {},
+ "cr00.biz.": {},
+ "crash.xiaohongshu.com.": {},
+ "crashlytics.com.": {},
+ "crashsight.qq.com.": {},
+ "crashsight.wetest.net.": {},
+ "crobox.com.": {},
+ "crobox.io.": {},
+ "crossforward.com.": {},
+ "crowd.transitapp.com.": {},
+ "crowncastle-my.sharepoint.com.": {},
+ "crowncastle.sharepoint.com.": {},
+ "croxyproxy.com.": {},
+ "crush.163.com.": {},
+ "crutchfield.com.": {},
+ "cs.globalsun.io.": {},
+ "csdn.net.": {},
+ "cshealthforlife.com.": {},
+ "cstse02.ultipro.com.": {},
+ "cstsew02.ultipro.com.": {},
+ "cstsn02.ultipro.com.": {},
+ "cta-eu1.hubspot.com.": {},
+ "ctm.server.absolute.com.": {},
+ "cuco.softi9.pt.": {},
+ "cummins365.sharepoint.com.": {},
+ "customer.homedepot.com.": {},
+ "customerfeedback.superunlimited.com.": {},
+ "cvs.quantummetric.com.": {},
+ "cwc-my.sharepoint.com.": {},
+ "cwc.sharepoint.com.": {},
+ "cx.soft.360.cn.": {},
+ "cxcs.cdn.office.net.edgekey.net.": {},
+ "czechrepublic.remotepc.com.": {},
+ "d.applvn.com.": {},
+ "d.docs.live.net.": {},
+ "d.nativendo.de.": {},
+ "d.ns.c10r.facebook.com.": {},
+ "d.ns.xx.fbcdn.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.": {},
+ "d3tracking.rbc.com.": {},
+ "d6-gd.static.yximgs.com.": {},
+ "d6691a17-6fdb-4d26-85d6-b3dd27f55f08.prmutv.co.": {},
+ "d82f7a30-751a-4689-b7e9-19336a89ab46.prmutv.co.": {},
+ "d837da8d.cloudsrv.minerva-labs.com.": {},
+ "daemon.nanoleaf.me.": {},
+ "daf.xp.apple.com.": {},
+ "dallas.remotepc.com.": {},
+ "dallas2.remotepc.com.": {},
+ "dallas3.remotepc.com.": {},
+ "dallas4.remotepc.com.": {},
+ "dallas5.remotepc.com.": {},
+ "dallaschildrens-my.sharepoint.com.": {},
+ "dallaschildrens.sharepoint.com.": {},
+ "dalu-my.sharepoint.com.": {},
+ "dantri.com.vn.": {},
+ "darden-sync.quantummetric.com.": {},
+ "data-detect.nie.easebar.com.": {},
+ "data-feed.flightradar24.com.": {},
+ "data.analytics.thomsonreuters.com.": {},
+ "data.analytics.ux.quickbase.com.": {},
+ "data.assist.chromeriver.com.": {},
+ "data.bhrpendo.bamboohr.com.": {},
+ "data.cw.edgenuity.com.": {},
+ "data.dap.paylocity.com.": {},
+ "data.data.achieve3000.com.": {},
+ "data.data.aleks.com.": {},
+ "data.data.mheducation.com.": {},
+ "data.guide-app.zoominfo.co.": {},
+ "data.guides.oncoursesystems.com.": {},
+ "data.guides.patreon.com.": {},
+ "data.guides.percipio.com.": {},
+ "data.guides.wdesk.com.": {},
+ "data.hockeystack.com.": {},
+ "data.ipd.goto.com.": {},
+ "data.kuiniuca.com.": {},
+ "data.meitu.com.": {},
+ "data.pendo-tracking.seismic.com.": {},
+ "data.pendo.careporthealth.com.": {},
+ "data.pendo.looker.app.": {},
+ "data.pendo.navimedix.com.": {},
+ "data.pendo.progresslearning.com.": {},
+ "data.pendo.statista.com.": {},
+ "data.pendo.udsrv.com.": {},
+ "data.pnd.liveperson.com.": {},
+ "data.productanalytics.coconutcalendar.com.": {},
+ "data.tracking.billtrust.com.": {},
+ "data00.adlooxtracking.com.": {},
+ "datamma.guides.nelnet.com.": {},
+ "datasec-kmsex-cn.heytapmobi.com.": {},
+ "datasite.com.": {},
+ "datatheorem.com.": {},
+ "datto.com.": {},
+ "db3pap002.storage.live.com.": {},
+ "db3pap003.storage.live.com.": {},
+ "db3pap004.storage.live.com.": {},
+ "db3pap005.storage.live.com.": {},
+ "db3pap006.storage.live.com.": {},
+ "db3pap007.storage.live.com.": {},
+ "db5pap001.storage.live.com.": {},
+ "dc-o.api.leiniao.com.": {},
+ "dc.di.atlas.samsung.com.": {},
+ "dc.dqa.samsung.com.": {},
+ "dc.wondershare.com.": {},
+ "dci.sophosupd.net.": {},
+ "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.mp.lura.live.": {},
+ "dd.browser.360.cn.": {},
+ "ddata.huntingtonbank.com.": {},
+ "de-core.iprom.net.": {},
+ "de-idm.api.io.mi.com.": {},
+ "de-prod.asyncgw.teams.microsoft.com.": {},
+ "de.dt.rcs.telephony.goog.": {},
+ "dealmoon.com.": {},
+ "deepl.com.": {},
+ "deliver.feedify.org.": {},
+ "delivery-integration.myq-cloud.com.": {},
+ "delivery.upremium.asia.": {},
+ "deliveryhero.io.": {},
+ "dell-prod.actioniq.mr-in.com.": {},
+ "delta-app.quantummetric.com.": {},
+ "delta-sync.quantummetric.com.": {},
+ "delta.quantummetric.com.": {},
+ "demant.sharepoint.com.": {},
+ "dentonstudent-my.sharepoint.com.": {},
+ "denver.remotepc.com.": {},
+ "deploy.preveil.com.": {},
+ "desmoines.remotepc.com.": {},
+ "dev2.keepsolid.com.": {},
+ "deviceops.hstgps.com.": {},
+ "dewmobile.net.": {},
+ "dfaklj.tech.": {},
+ "dfamilk.sharepoint.com.": {},
+ "dickssportinggoods-app.quantummetric.com.": {},
+ "dict.deepl.com.": {},
+ "digiapp.vietcombank.com.vn.": {},
+ "digiboy.ir.": {},
+ "dingtalk.com.": {},
+ "dir.4.401402081.west-gcloud.codm.activision.com.": {},
+ "discovery.ringcentral.biz.": {},
+ "dispatcher.factset.com.": {},
+ "dispatcher.omiapp.me.": {},
+ "dispatchosglobal.yuanshen.com.": {},
+ "display.popt.in.": {},
+ "displaynote.com.": {},
+ "distservp1.pb.com.": {},
+ "dl.acronis.com.cdn.cloudflare.net.": {},
+ "dl.boxcloud.com.": {},
+ "dls-udc.dqa.samsung.com.": {},
+ "dm-us.hybrid.ai.": {},
+ "dm1files.storage.live.com.": {},
+ "dm2pap090files.storage.live.com.": {},
+ "dmp.nativendo.de.": {},
+ "dmp.suntcontent.se.": {},
+ "dns-tunnel-check.googlezip.net.": {},
+ "dns.alidns.com.": {},
+ "dns.jdbcdn.net.": {},
+ "dns101.register.com.": {},
+ "dns23.llnwi.net.": {},
+ "dns24.llnwi.net.": {},
+ "doceditor.wrike.com.": {},
+ "docs.live.net.": {},
+ "document360.io.": {},
+ "domaincfg.vivoglobal.com.": {},
+ "donaldson-my.sharepoint.com.": {},
+ "donaldson.sharepoint.com.": {},
+ "donewyork1.remotepc.com.": {},
+ "donewyork2.remotepc.com.": {},
+ "donewyork3.remotepc.com.": {},
+ "doppiocdn.net.": {},
+ "dosfo1.remotepc.com.": {},
+ "dosfo2.remotepc.com.": {},
+ "download.2.401402081.west-gcloud.codm.activision.com.": {},
+ "download.autodesk.com.": {},
+ "downloadcenter.genetec.com.": {},
+ "downloads.vivaldi.com.": {},
+ "dp.barclaysus.com.": {},
+ "dp.im.weibo.cn.": {},
+ "dpf.authorize.net.": {},
+ "dpkg.mp.lura.live.": {},
+ "dpool.sina.com.cn.": {},
+ "dprprod-my.sharepoint.com.": {},
+ "dprprod.sharepoint.com.": {},
+ "dps.jp.cinarra.com.": {},
+ "dr.netease.im.": {},
+ "drfdisvc.walmart.com.": {},
+ "drfirst.com.": {},
+ "drm.mp.lura.live.": {},
+ "drops-register.ubi.com.": {},
+ "dsa-eu.hybrid.ai.": {},
+ "dsg.bio.": {},
+ "dsm01pap001.storage.live.com.": {},
+ "dsm01pap001files.storage.live.com.": {},
+ "dsm01pap002.storage.live.com.": {},
+ "dsm01pap002files.storage.live.com.": {},
+ "dsm01pap003.storage.live.com.": {},
+ "dsm01pap003files.storage.live.com.": {},
+ "dsm01pap004.storage.live.com.": {},
+ "dsm01pap004files.storage.live.com.": {},
+ "dsm01pap005.storage.live.com.": {},
+ "dsm01pap005files.storage.live.com.": {},
+ "dsm01pap006.storage.live.com.": {},
+ "dsm01pap006files.storage.live.com.": {},
+ "dsm01pap007.storage.live.com.": {},
+ "dsm01pap007files.storage.live.com.": {},
+ "dsm01pap008.storage.live.com.": {},
+ "dsm01pap008files.storage.live.com.": {},
+ "dsm01pap009.storage.live.com.": {},
+ "dsm01pap009files.storage.live.com.": {},
+ "dsm01pap090files.storage.live.com.": {},
+ "dsm04pap001.storage.live.com.": {},
+ "dsm04pap001files.storage.live.com.": {},
+ "dsm04pap002.storage.live.com.": {},
+ "dsm04pap002files.storage.live.com.": {},
+ "dsm04pap003.storage.live.com.": {},
+ "dsm04pap003files.storage.live.com.": {},
+ "dss.hybrid.ai.": {},
+ "dstillery.com.": {},
+ "dtscout.com.": {},
+ "dualspaceapi.com.": {},
+ "dub01pap001.storage.live.com.": {},
+ "dub01pap002.storage.live.com.": {},
+ "dub01pap003.storage.live.com.": {},
+ "dub06pap001.storage.live.com.": {},
+ "dub07pap001.storage.live.com.": {},
+ "dub07pap002.storage.live.com.": {},
+ "dub07pap003.storage.live.com.": {},
+ "dubai.remotepc.com.": {},
+ "dublin.remotepc.com.": {},
+ "dun.163yun.com.": {},
+ "dwhyn.top.": {},
+ "dynaimage.cdn.cnn.com.": {},
+ "dypnsapi.aliyuncs.com.": {},
+ "dz.cyberhaven.io.": {},
+ "e-189.21cn.com.": {},
+ "e.189.cn.": {},
+ "e.calibermind.com.": {},
+ "e.cdnwidget.com.": {},
+ "e.petco.com.": {},
+ "e.userflow.com.": {},
+ "e.viously.com.": {},
+ "e1cef1f0-495f-4973-ba1c-880786e73a66.prmutv.co.": {},
+ "e2c1.gcp.gvt2.com.": {},
+ "e2c10.gcp.gvt2.com.": {},
+ "e2c11.gcp.gvt2.com.": {},
+ "e2c12.gcp.gvt2.com.": {},
+ "e2c13.gcp.gvt2.com.": {},
+ "e2c14.gcp.gvt2.com.": {},
+ "e2c15.gcp.gvt2.com.": {},
+ "e2c16.gcp.gvt2.com.": {},
+ "e2c17.gcp.gvt2.com.": {},
+ "e2c18.gcp.gvt2.com.": {},
+ "e2c19.gcp.gvt2.com.": {},
+ "e2c2.gcp.gvt2.com.": {},
+ "e2c20.gcp.gvt2.com.": {},
+ "e2c21.gcp.gvt2.com.": {},
+ "e2c22.gcp.gvt2.com.": {},
+ "e2c23.gcp.gvt2.com.": {},
+ "e2c24.gcp.gvt2.com.": {},
+ "e2c25.gcp.gvt2.com.": {},
+ "e2c26.gcp.gvt2.com.": {},
+ "e2c27.gcp.gvt2.com.": {},
+ "e2c28.gcp.gvt2.com.": {},
+ "e2c29.gcp.gvt2.com.": {},
+ "e2c3.gcp.gvt2.com.": {},
+ "e2c30.gcp.gvt2.com.": {},
+ "e2c31.gcp.gvt2.com.": {},
+ "e2c32.gcp.gvt2.com.": {},
+ "e2c33.gcp.gvt2.com.": {},
+ "e2c34.gcp.gvt2.com.": {},
+ "e2c35.gcp.gvt2.com.": {},
+ "e2c36.gcp.gvt2.com.": {},
+ "e2c37.gcp.gvt2.com.": {},
+ "e2c38.gcp.gvt2.com.": {},
+ "e2c39.gcp.gvt2.com.": {},
+ "e2c4.gcp.gvt2.com.": {},
+ "e2c40.gcp.gvt2.com.": {},
+ "e2c41.gcp.gvt2.com.": {},
+ "e2c42.gcp.gvt2.com.": {},
+ "e2c43.gcp.gvt2.com.": {},
+ "e2c44.gcp.gvt2.com.": {},
+ "e2c45.gcp.gvt2.com.": {},
+ "e2c46.gcp.gvt2.com.": {},
+ "e2c47.gcp.gvt2.com.": {},
+ "e2c48.gcp.gvt2.com.": {},
+ "e2c49.gcp.gvt2.com.": {},
+ "e2c5.gcp.gvt2.com.": {},
+ "e2c50.gcp.gvt2.com.": {},
+ "e2c51.gcp.gvt2.com.": {},
+ "e2c52.gcp.gvt2.com.": {},
+ "e2c53.gcp.gvt2.com.": {},
+ "e2c54.gcp.gvt2.com.": {},
+ "e2c55.gcp.gvt2.com.": {},
+ "e2c56.gcp.gvt2.com.": {},
+ "e2c57.gcp.gvt2.com.": {},
+ "e2c58.gcp.gvt2.com.": {},
+ "e2c59.gcp.gvt2.com.": {},
+ "e2c6.gcp.gvt2.com.": {},
+ "e2c60.gcp.gvt2.com.": {},
+ "e2c61.gcp.gvt2.com.": {},
+ "e2c62.gcp.gvt2.com.": {},
+ "e2c63.gcp.gvt2.com.": {},
+ "e2c64.gcp.gvt2.com.": {},
+ "e2c65.gcp.gvt2.com.": {},
+ "e2c66.gcp.gvt2.com.": {},
+ "e2c67.gcp.gvt2.com.": {},
+ "e2c68.gcp.gvt2.com.": {},
+ "e2c69.gcp.gvt2.com.": {},
+ "e2c7.gcp.gvt2.com.": {},
+ "e2c70.gcp.gvt2.com.": {},
+ "e2c71.gcp.gvt2.com.": {},
+ "e2c72.gcp.gvt2.com.": {},
+ "e2c8.gcp.gvt2.com.": {},
+ "e2c9.gcp.gvt2.com.": {},
+ "e2cs01.gcp.gvt2.com.": {},
+ "e2cs02.gcp.gvt2.com.": {},
+ "e2cs03.gcp.gvt2.com.": {},
+ "e2cs04.gcp.gvt2.com.": {},
+ "e2cs05.gcp.gvt2.com.": {},
+ "e2cs06.gcp.gvt2.com.": {},
+ "e2cs07.gcp.gvt2.com.": {},
+ "e2cs08.gcp.gvt2.com.": {},
+ "e2cs09.gcp.gvt2.com.": {},
+ "e2cs10.gcp.gvt2.com.": {},
+ "e2cs11.gcp.gvt2.com.": {},
+ "e2cs12.gcp.gvt2.com.": {},
+ "e2cs13.gcp.gvt2.com.": {},
+ "e2cs14.gcp.gvt2.com.": {},
+ "e2cs15.gcp.gvt2.com.": {},
+ "e2cs16.gcp.gvt2.com.": {},
+ "e2cs17.gcp.gvt2.com.": {},
+ "e2cs18.gcp.gvt2.com.": {},
+ "e2cs19.gcp.gvt2.com.": {},
+ "e2cs20.gcp.gvt2.com.": {},
+ "e2cs21.gcp.gvt2.com.": {},
+ "e2cs22.gcp.gvt2.com.": {},
+ "e2cs23.gcp.gvt2.com.": {},
+ "e2cs24.gcp.gvt2.com.": {},
+ "e2cs25.gcp.gvt2.com.": {},
+ "e2cs26.gcp.gvt2.com.": {},
+ "e2cs27.gcp.gvt2.com.": {},
+ "e2cs28.gcp.gvt2.com.": {},
+ "e2cs29.gcp.gvt2.com.": {},
+ "e2cs30.gcp.gvt2.com.": {},
+ "e2cs31.gcp.gvt2.com.": {},
+ "e2cs32.gcp.gvt2.com.": {},
+ "e2cs33.gcp.gvt2.com.": {},
+ "e2cs34.gcp.gvt2.com.": {},
+ "e2cs35.gcp.gvt2.com.": {},
+ "e2cs36.gcp.gvt2.com.": {},
+ "e2cs37.gcp.gvt2.com.": {},
+ "e2cs38.gcp.gvt2.com.": {},
+ "e2cs39.gcp.gvt2.com.": {},
+ "e2cs40.gcp.gvt2.com.": {},
+ "e2cs41.gcp.gvt2.com.": {},
+ "e2cs42.gcp.gvt2.com.": {},
+ "e2cs43.gcp.gvt2.com.": {},
+ "e2cs44.gcp.gvt2.com.": {},
+ "e2cs45.gcp.gvt2.com.": {},
+ "e2cs46.gcp.gvt2.com.": {},
+ "e2cs47.gcp.gvt2.com.": {},
+ "e2cs48.gcp.gvt2.com.": {},
+ "e2cs49.gcp.gvt2.com.": {},
+ "e2cs50.gcp.gvt2.com.": {},
+ "e2cs51.gcp.gvt2.com.": {},
+ "e2cs52.gcp.gvt2.com.": {},
+ "e2cs53.gcp.gvt2.com.": {},
+ "e2cs54.gcp.gvt2.com.": {},
+ "e2cs55.gcp.gvt2.com.": {},
+ "e43.ultipro.com.": {},
+ "e488cdb0-e7cb-4d91-9648-60d437d8e491.prmutv.co.": {},
+ "e5de3d23065c4748b155c28e6fa36f3e.pacloudflare.com.": {},
+ "e99.cyberhaven.io.": {},
+ "eafd-footprint.elasticafd.net.": {},
+ "eafddirect.msedge.net.": {},
+ "eagle-my.sharepoint.com.": {},
+ "eagle.sharepoint.com.": {},
+ "eago9.cyberhaven.io.": {},
+ "easeus.com.": {},
+ "eastmoney.com.": {},
+ "eastus2-gas.guestconfiguration.azure.com.": {},
+ "eb.cently.com.": {},
+ "ebaypay-app.quantummetric.com.": {},
+ "ebaypay-sync.quantummetric.com.": {},
+ "ec.instapagemetrics.com.": {},
+ "ecatholic.com.": {},
+ "ecom.wixapps.net.": {},
+ "ecommerce.iap.unity3d.com.": {},
+ "ecs-gallatin-c2s.trafficmanager.net.": {},
+ "edge-iad.txryan.com.": {},
+ "edge-pdx.txryan.com.": {},
+ "edge.api.brightcove.com.": {},
+ "edge.zimperium.com.": {},
+ "edgecdn.ru.": {},
+ "edgedl.me.gvt1.com.": {},
+ "edgelocation.ivanticloud.com.": {},
+ "edisonintl-my.sharepoint.com.": {},
+ "edisonintl.sharepoint.com.": {},
+ "editor.wix.com.": {},
+ "editorial.femaledaily.com.": {},
+ "efercro.com.": {},
+ "efs.ultipro.com.": {},
+ "egateway.ultipro.com.": {},
+ "ehmc-my.sharepoint.com.": {},
+ "ehmc.sharepoint.com.": {},
+ "eisaihhc-my.sharepoint.com.": {},
+ "ejoyspace.com.": {},
+ "ek6rpv-launches.appsflyersdk.com.": {},
+ "elal-service.consist.co.il.": {},
+ "elaracaring-my.sharepoint.com.": {},
+ "ele.me.": {},
+ "elemecdn.com.": {},
+ "ellingtoncms.com.": {},
+ "em.journeys.com.": {},
+ "email.plumbenefits.com.": {},
+ "email.ticketsatwork.com.": {},
+ "emailaptitude.com.": {},
+ "emailsignatures365-addin.codetwo.com.": {},
+ "emerson-my.sharepoint.com.": {},
+ "emerson.sharepoint.com.": {},
+ "emo.v-mate.mobi.": {},
+ "empirecat-my.sharepoint.com.": {},
+ "en.gifshow.com.": {},
+ "endpointprotector.com.": {},
+ "engage.wixapps.net.": {},
+ "engagementapi.skype.com.": {},
+ "ent.box.com.": {},
+ "enterprise-app.quantummetric.com.": {},
+ "envoy-ios-prod.getepic.com.": {},
+ "ep.bale.ai.": {},
+ "epic1-my.sharepoint.com.": {},
+ "epic1.sharepoint.com.": {},
+ "eplists.xboxlive.com.": {},
+ "epoch.cloud.": {},
+ "eponesh.com.": {},
+ "eportal.fda.gov.ph.": {},
+ "epsnj.sharepoint.com.": {},
+ "errlog.umeng.com.": {},
+ "errlogos.umeng.com.": {},
+ "errnewlog.umeng.com.": {},
+ "errnewlogos.umeng.com.": {},
+ "errortracking.deepl.com.": {},
+ "esm.archive.org.": {},
+ "esosuite.net.": {},
+ "etracker.com.": {},
+ "etsv2.datalake.gameloft.com.": {},
+ "eu-aa.online-metrix.net.": {},
+ "eu-api.asm.skype.com.": {},
+ "eu-device.ymcs.yealink.com.": {},
+ "eu-gamecenter.api.intl.miui.com.": {},
+ "eu-prod.asyncgw.teams.microsoft.com.": {},
+ "eu-push.api.intl.miui.com.": {},
+ "eu.galleryapi.micloud.xiaomi.net.": {},
+ "eu.hismileteeth.com.": {},
+ "eu.iceporn.xxx.": {},
+ "eu.statusapi.micloud.xiaomi.net.": {},
+ "eu1.badoo.com.": {},
+ "eu1.bumbcdn.com.": {},
+ "eu1.bumble.com.": {},
+ "eu2a-excel-collab.officeapps.live.com.": {},
+ "euc-excel-collab.officeapps.live.com.": {},
+ "euc-powerpoint-collab.officeapps.live.com.": {},
+ "euc1-green.pp.sgp.pvp.net.": {},
+ "europe.remotepc.com.": {},
+ "eus.his.arc.azure.com.": {},
+ "eus.his.hybridcompute.trafficmanager.net.": {},
+ "eus2.guestagentsvc-prod.trafficmanager.net.": {},
+ "eus2.his.arc.azure.com.": {},
+ "euw1.chat.si.riotgames.com.": {},
+ "eve.gameloft.com.": {},
+ "event-tracking-project.ap-southeast-1.log.aliyuncs.com.": {},
+ "event.evtm.53.com.": {},
+ "event.togothermany.com.": {},
+ "events.swishapps.ai.": {},
+ "evtrust.com.": {},
+ "exappupgrade.vivoglobal.com.": {},
+ "excel-collab.officeapps.live.com.": {},
+ "exodus.desync.com.": {},
+ "exp.host.": {},
+ "expedia-api.arkoselabs.com.": {},
+ "expedia-app.quantummetric.com.": {},
+ "experimental-api.asm.skype.com.": {},
+ "explicit-explicit.bing.net.trafficmanager.net.": {},
+ "exponential.com.": {},
+ "extension.faro.speechify.dev.": {},
+ "external-ams2-1.xx.fbcdn.net.": {},
+ "external-ams4-1.xx.fbcdn.net.": {},
+ "external-atl3-1.xx.fbcdn.net.": {},
+ "external-atl3-2.xx.fbcdn.net.": {},
+ "external-ber1-1.xx.fbcdn.net.": {},
+ "external-bos5-1.xx.fbcdn.net.": {},
+ "external-bru2-1.xx.fbcdn.net.": {},
+ "external-den4-1.xx.fbcdn.net.": {},
+ "external-dfw5-1.xx.fbcdn.net.": {},
+ "external-dfw5-2.xx.fbcdn.net.": {},
+ "external-dus1-1.xx.fbcdn.net.": {},
+ "external-fra3-1.xx.fbcdn.net.": {},
+ "external-fra3-2.xx.fbcdn.net.": {},
+ "external-fra5-1.xx.fbcdn.net.": {},
+ "external-fra5-2.xx.fbcdn.net.": {},
+ "external-ham3-1.xx.fbcdn.net.": {},
+ "external-hou1-1.xx.fbcdn.net.": {},
+ "external-iad3-1.xx.fbcdn.net.": {},
+ "external-iad3-2.xx.fbcdn.net.": {},
+ "external-lax3-1.xx.fbcdn.net.": {},
+ "external-lax3-2.xx.fbcdn.net.": {},
+ "external-lga3-1.xx.fbcdn.net.": {},
+ "external-lga3-2.xx.fbcdn.net.": {},
+ "external-lhr6-1.xx.fbcdn.net.": {},
+ "external-lhr6-2.xx.fbcdn.net.": {},
+ "external-lhr8-1.xx.fbcdn.net.": {},
+ "external-lhr8-2.xx.fbcdn.net.": {},
+ "external-man2-1.xx.fbcdn.net.": {},
+ "external-mia3-1.xx.fbcdn.net.": {},
+ "external-mia3-2.xx.fbcdn.net.": {},
+ "external-msp1-1.xx.fbcdn.net.": {},
+ "external-muc2-1.xx.fbcdn.net.": {},
+ "external-ord5-1.xx.fbcdn.net.": {},
+ "external-ord5-2.xx.fbcdn.net.": {},
+ "external-sea1-1.xx.fbcdn.net.": {},
+ "external-sjc3-1.xx.fbcdn.net.": {},
+ "external-vie1-1.xx.fbcdn.net.": {},
+ "external-waw1-1.xx.fbcdn.net.": {},
+ "external-yyz1-1.xx.fbcdn.net.": {},
+ "extranet-ipv4-pub-azams.alb.xiaomi.com.": {},
+ "ezcollab-my.sharepoint.com.": {},
+ "ezmob.com.": {},
+ "f35b59fc-90c6-428e-a9e4-494353d0f0e1.prmutv.co.": {},
+ "fa000000068.resources.office.net.": {},
+ "fa000000131.resources.office.net.": {},
+ "faas.marktplaats.nl.": {},
+ "faceunity.com.": {},
+ "factor.reg.163.com.": {},
+ "factset.com.": {},
+ "fandango-app.quantummetric.com.": {},
+ "fastly-cloud.typenetwork.com.": {},
+ "fastly.cedexis-test.com.": {},
+ "faves.grow.me.": {},
+ "fb88d35d-3299-4a2e-bf5c-ebb4e5b5938a.resources.office.net.": {},
+ "fbinsmi-my.sharepoint.com.": {},
+ "fbinsmi.sharepoint.com.": {},
+ "fdccpaadaptor.forddirectservices.com.": {},
+ "fe.xiaohongshu.com.": {},
+ "fed.federate365.com.": {},
+ "feed.podbean.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.": {},
+ "femaledaily.com.": {},
+ "fengkongcloud.com.": {},
+ "fenxi.com.": {},
+ "ferringgroup-my.sharepoint.com.": {},
+ "ferringgroup.sharepoint.com.": {},
+ "ffs.thescore.com.": {},
+ "fgwn01.ultipro.com.": {},
+ "fhmc-my.sharepoint.com.": {},
+ "fi.telephony.goog.": {},
+ "field59.com.": {},
+ "files.jotform.com.": {},
+ "finalsite.com.": {},
+ "finalsite.net.": {},
+ "firebase.sgp1.digitaloceanspaces.com.": {},
+ "fireeye.com.": {},
+ "firefox.com.cn.": {},
+ "fitchburg.remotepc.com.": {},
+ "five9.com.": {},
+ "fleet.todyl.com.": {},
+ "flip.to.": {},
+ "flixcdn.com.": {},
+ "flocktory.com.": {},
+ "flypgs.com.": {},
+ "flyspirit-my.sharepoint.com.": {},
+ "flyspirit.sharepoint.com.": {},
+ "fm.printaudit.com.": {},
+ "fmcna-my.sharepoint.com.": {},
+ "fmcschedule.com.": {},
+ "fn.us.ipqscdn.com.": {},
+ "forcesafesearch.google.com.": {},
+ "forksystems.mm.fcix.net.": {},
+ "form.jotform.com.": {},
+ "forms-eu1.hscollectedforms.net.": {},
+ "forms-eu1.hsforms.com.": {},
+ "forms-eu1.hubspot.com.": {},
+ "forms.aweber.com.": {},
+ "forthepeople0.sharepoint.com.": {},
+ "fortworth.remotepc.com.": {},
+ "fpbns.net.": {},
+ "fpdlp.applxweb.com.": {},
+ "fr-prod.asyncgw.teams.microsoft.com.": {},
+ "fr1.bumbcdn.com.": {},
+ "fragrancenet.com.": {},
+ "fran.frvr.com.": {},
+ "franecki.net.": {},
+ "frankfurt.remotepc.com.": {},
+ "fremont.remotepc.com.": {},
+ "freseniusmedicalcare.com.": {},
+ "fresnocounty-my.sharepoint.com.": {},
+ "friendly.io.": {},
+ "fs.ultiproworkplace.com.": {},
+ "fsu-my.sharepoint.com.": {},
+ "fsu.sharepoint.com.": {},
+ "fticonsulting-my.sharepoint.com.": {},
+ "fticonsulting.sharepoint.com.": {},
+ "ftke02.ultipro.com.": {},
+ "ftkew02.ultipro.com.": {},
+ "ftkn01.ultipro.com.": {},
+ "ftkn02.ultipro.com.": {},
+ "funshion.com.": {},
+ "fusd-my.sharepoint.com.": {},
+ "g-zgroup.pstatic.net.": {},
+ "g.fastcdn.co.": {},
+ "g.tivan.naver.com.": {},
+ "g9hc4.cn.": {},
+ "ga.badambiz.com.": {},
+ "ga.jspm.io.": {},
+ "galaxy.safe.360.cn.": {},
+ "galaxyappstore.com.": {},
+ "gameloft.com.": {},
+ "gamemonkey.org.": {},
+ "gamepigeon.net.": {},
+ "games-dailycheckin.shopee.co.id.": {},
+ "gamestop-app.quantummetric.com.": {},
+ "gateway.ultiproworkplace.com.": {},
+ "gb.ee.rcs.telephony.goog.": {},
+ "gb.o2.rcs.telephony.goog.": {},
+ "gbc-word-edit.officeapps.live.com.": {},
+ "gc.apple.com.": {},
+ "gcash-api.pulseid.com.": {},
+ "gccmod.ecs.office.com.": {},
+ "gcdn.co.": {},
+ "gcloud.qq.com.": {},
+ "gcloudcs.com.": {},
+ "gcloudsdk.com.": {},
+ "gcs.garmin.com.": {},
+ "gcs.sc-cdn.net.": {},
+ "gdfpsec.gifshow.com.": {},
+ "gdid.datalake.gameloft.com.": {},
+ "gdpr.loopme.com.": {},
+ "gdsns1.alibabadns.com.": {},
+ "geappl.io.": {},
+ "geappliances-my.sharepoint.com.": {},
+ "geappliances.sharepoint.com.": {},
+ "geico-app.quantummetric.com.": {},
+ "geico-sync.quantummetric.com.": {},
+ "geniusmonkey.com.": {},
+ "geometrydashlite.co.": {},
+ "getadmiral.com.": {},
+ "getbutton.io.": {},
+ "gettopple.com.": {},
+ "getui.net.": {},
+ "giraff.io.": {},
+ "githack.com.": {},
+ "gitlab.com.": {},
+ "gla.gameloft.com.": {},
+ "glic-my.sharepoint.com.": {},
+ "glic.sharepoint.com.": {},
+ "global-tokenserver-la.headline.uodoo.com.": {},
+ "global.datasite.com.": {},
+ "globalcdn.co.": {},
+ "globalsigncdn.com.cdn.cloudflare.net.": {},
+ "globalsun.io.": {},
+ "gme.qcloud.com.": {},
+ "gnc.com.": {},
+ "go.gale.com.": {},
+ "go.hpyjmp.com.": {},
+ "go.pdfforge.org.": {},
+ "goaffpro.com.": {},
+ "goconfluent-my.sharepoint.com.": {},
+ "gohealthuc.okta.com.": {},
+ "golf.com.": {},
+ "goodrxcore-app.quantummetric.com.": {},
+ "google.org.": {},
+ "googledomains.com.": {},
+ "gosport.remotepc.com.": {},
+ "gov-bam.nr-data.net.": {},
+ "gov-bam.nr-data.net.cdn.cloudflare.net.": {},
+ "gowustl-my.sharepoint.com.": {},
+ "grab.zoom.us.": {},
+ "grainger.com.": {},
+ "gravitec.net.": {},
+ "gravityzone.bitdefender.com.": {},
+ "graylog.com.": {},
+ "greenhousegroup.com.": {},
+ "greenville.remotepc.com.": {},
+ "group-ib.com.": {},
+ "grouponeauto-my.sharepoint.com.": {},
+ "grpc.vivintsky.com.": {},
+ "gsgrpt.adview.com.": {},
+ "gslb.finzfin.com.": {},
+ "gslb.sgw.shopeemobile.com.": {},
+ "gslb.xiaohongshu.com.": {},
+ "gstbocessscta-my.sharepoint.com.": {},
+ "gstbocessscta.sharepoint.com.": {},
+ "gtimg.cn.": {},
+ "guid.tpns.sgp.tencent.com.": {},
+ "gut.bmj.com.": {},
+ "gw5.push.mcp.weibo.cn.": {},
+ "gwadar.cn.": {},
+ "gx-api.geniex.com.": {},
+ "gyazo.com.": {},
+ "gz0.googleusercontent.com.": {},
+ "h-5h8i3ud8.online-metrix.net.": {},
+ "h-adp.online-metrix.net.": {},
+ "h-citibankonline.online-metrix.net.": {},
+ "h-citicards.online-metrix.net.": {},
+ "h-discover.online-metrix.net.": {},
+ "h-e04kqxof.online-metrix.net.": {},
+ "h-ebay.online-metrix.net.": {},
+ "h-fisglobal.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-uptodate.online-metrix.net.": {},
+ "h-v60nf4oj-qfp.online-metrix.net.": {},
+ "h-walmart.online-metrix.net.": {},
+ "h.app.wdesk.com.": {},
+ "h.online-metrix.net.": {},
+ "h107833-ecdn.mp.lura.live.": {},
+ "haka.ruselabs.com.": {},
+ "hamina.remotepc.com.": {},
+ "handmadewithjoann.com.": {},
+ "hanoi.remotepc.com.": {},
+ "hapsee.cn.": {},
+ "harmonyoffice-my.sharepoint.com.": {},
+ "harrisonk12msus-my.sharepoint.com.": {},
+ "harry.lu.": {},
+ "hawaii.remotepc.com.": {},
+ "hbe199.hybrid.ai.": {},
+ "hbopenbid-apac-v2.pubmnet.com.": {},
+ "hbwrapper.com.": {},
+ "hd-ext-v1.log.mgtv.com.": {},
+ "hdi365.sharepoint.com.": {},
+ "hdsupplyinc-my.sharepoint.com.": {},
+ "hdsupplyinc.sharepoint.com.": {},
+ "healthequity-my.sharepoint.com.": {},
+ "healthequity.sharepoint.com.": {},
+ "hearst-prod.actioniq.mr-in.com.": {},
+ "heatmap-events-collector.instapage.com.": {},
+ "hecheck.bitmyanmar.info.": {},
+ "helloid.com.": {},
+ "help.ea.com.": {},
+ "hengam.io.": {},
+ "hermes-us.inspidspad.com.": {},
+ "hetangsmart.com.": {},
+ "heytapdownload.com.": {},
+ "heytapmobi.com.": {},
+ "highwebmedia.com.": {},
+ "highwire.org.": {},
+ "hillsboroughcounty-my.sharepoint.com.": {},
+ "hilton-my.sharepoint.com.": {},
+ "hisearch-dra.dt.dbankcloud.com.": {},
+ "hismarttv.com.": {},
+ "hismileteeth.com.": {},
+ "hits.getelevar.com.": {},
+ "hiya.browser.360.cn.": {},
+ "hk.gcloudcs.com.": {},
+ "hk.voice.gcloudcs.com.": {},
+ "hk.wechat.com.": {},
+ "hls-video-ynet.ynethd.com.": {},
+ "home.highwire.org.": {},
+ "homedepot.quantummetric.com.": {},
+ "honeywell.com.": {},
+ "honeywellprod-my.sharepoint.com.": {},
+ "hongkong.remotepc.com.": {},
+ "hostedimages-cdn.aweber-static.com.": {},
+ "houstonmethodist1.sharepoint.com.": {},
+ "houtx-my.sharepoint.com.": {},
+ "houtx.sharepoint.com.": {},
+ "hpfalkaj.deepl.com.": {},
+ "hpkaj.deepl.com.": {},
+ "hpplay.cn.": {},
+ "hstgps.com.": {},
+ "html.it.": {},
+ "html5.qq.com.": {},
+ "htms.heytapmobi.com.": {},
+ "httpdns.y5en.com.": {},
+ "huan.tv.": {},
+ "huaweicloud.com.": {},
+ "hub.turbo.net.": {},
+ "hubble.netease.com.": {},
+ "hubble.officeapps.live.com.": {},
+ "hubcloud.com.cn.": {},
+ "hubspotemail.net.": {},
+ "huion.cn.": {},
+ "huorong.cn.": {},
+ "hwapps-o.api.leiniao.com.": {},
+ "hysteryale-my.sharepoint.com.": {},
+ "hysteryale.sharepoint.com.": {},
+ "hzmk.site.": {},
+ "hzmklvdieo.com.": {},
+ "i-sg01a.ocloud.heytapmobi.com.": {},
+ "i.magazine.heytapmobi.com.": {},
+ "i.omiapp.me.": {},
+ "i6-vn.weather.oppomobile.com.": {},
+ "iaas.jdcloud.com.": {},
+ "ibm.account.box.com.": {},
+ "ibm.box.com.": {},
+ "ibsrv.net.": {},
+ "icalendars.app.": {},
+ "ichano.cn.": {},
+ "iconmonstr.com.": {},
+ "icons.bitwarden.net.": {},
+ "id-ooredoo.rcs.telephony.goog.": {},
+ "id-telkom.rcs.telephony.goog.": {},
+ "id-timer-appstore.vivoglobal.com.": {},
+ "id.maxon.net.": {},
+ "idahofalls.remotepc.com.": {},
+ "idamsdm.remotepc.com.": {},
+ "idchicago1.remotepc.com.": {},
+ "iddallas1.remotepc.com.": {},
+ "iddenver.remotepc.com.": {},
+ "iddetroit.remotepc.com.": {},
+ "idhw-my.sharepoint.com.": {},
+ "idhw.sharepoint.com.": {},
+ "idlondon.remotepc.com.": {},
+ "idmadrid.remotepc.com.": {},
+ "idmsa.apple.com.": {},
+ "idnewyork1.remotepc.com.": {},
+ "idqqimg.com.": {},
+ "idr.cdnwidget.com.": {},
+ "ids.cdnwidget.com.": {},
+ "ifconfig.io.": {},
+ "igame.gcloudcs.com.": {},
+ "ijoysoftconnect.com.": {},
+ "ikioenjm3e9.ioriveredge.net.": {},
+ "ilandcloud.com.": {},
+ "illinoisstateuniversity-my.sharepoint.com.": {},
+ "ilmn-my.sharepoint.com.": {},
+ "ilmn.sharepoint.com.": {},
+ "ilog-sea-aliyun.alipayplus.com.": {},
+ "im.bluevoox.com.": {},
+ "image-us.samsung.com.": {},
+ "image.e.affirm.com.": {},
+ "image.myqcloud.com.": {},
+ "image.online.adp.com.": {},
+ "images.crazygames.com.": {},
+ "images.getadmiral.com.": {},
+ "images.twoplayergames.org.": {},
+ "images.wixstatic.com.": {},
+ "imagetrendelite.com.": {},
+ "imanageshare.com.": {},
+ "imap.163.com.": {},
+ "imap.earthlink.net.": {},
+ "imeffkdword.openspeech.cn.": {},
+ "img.asuracomics.com.": {},
+ "img.avery.com.": {},
+ "img.stripcdn.com.": {},
+ "img.washingtonpost.com.": {},
+ "img1.od-cdn.com.": {},
+ "img9.target.com.": {},
+ "imghst-de.com.": {},
+ "imgix.ranker.com.": {},
+ "imgproxy.leaflets.schwarz.": {},
+ "imgs.michaels.com.": {},
+ "imgs.signifyd.com.": {},
+ "imgsct.cookiebot.com.": {},
+ "imoim.net.": {},
+ "imolive2.com.": {},
+ "imou-sg-ali-online-paas-private-cloud-picture.oss-ap-southeast-1.aliyuncs.com.": {},
+ "imou-sg3-ali-online-paas-private-picture.oss-ap-southeast-1.aliyuncs.com.": {},
+ "imoulife.com.": {},
+ "imp.impdelivery.xyz.": {},
+ "impactify.media.": {},
+ "impdesk.com.": {},
+ "imptrk.siteplug.com.": {},
+ "in-api.asm.skype.com.": {},
+ "in-prod.asyncgw.teams.microsoft.com.": {},
+ "in-vcode-od.vivoglobal.com.": {},
+ "in.gov.": {},
+ "in.visitors.live.": {},
+ "inapp.gonitro.com.": {},
+ "inbound.americanexpress.com.": {},
+ "indianapolis.remotepc.com.": {},
+ "indoorloc.map.qq.com.": {},
+ "inf.miui.com.": {},
+ "ingka.com.": {},
+ "ingov-my.sharepoint.com.": {},
+ "ingov.sharepoint.com.": {},
+ "inneraudioms.cc.easebar.com.": {},
+ "innersloth.com.": {},
+ "innity.com.": {},
+ "innity.net.": {},
+ "inoreader.com.": {},
+ "ins-qw3q8ofk.ias.tencent-cloud.net.": {},
+ "inscr-my.sharepoint.com.": {},
+ "inscr.sharepoint.com.": {},
+ "insidemedia-my.sharepoint.com.": {},
+ "insidemedia.sharepoint.com.": {},
+ "insightgloballlc-my.sharepoint.com.": {},
+ "insights.ovid.com.": {},
+ "inskinad.com.": {},
+ "inspidspad.com.": {},
+ "inspirebrands-app.quantummetric.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.": {},
+ "instantmessaging-pa-us.googleapis.com.": {},
+ "int.dpool.sina.com.cn.": {},
+ "intel-my.sharepoint.com.": {},
+ "intel.sharepoint.com.": {},
+ "internetdownloadmanager.com.": {},
+ "intl-im-conn.iq.com.": {},
+ "ios.crashsight.wetest.net.": {},
+ "iot-as-http.cn-shanghai.aliyuncs.com.": {},
+ "iot.hillrom.com.": {},
+ "iowa.remotepc.com.": {},
+ "ip-api.com.": {},
+ "ip.acmeaom.com.": {},
+ "ipa-business-sg.ap-southeast-1.log.aliyuncs.com.": {},
+ "ipa-business.cn-hangzhou.log.aliyuncs.com.": {},
+ "ipify.org.": {},
+ "ipinyou.com.": {},
+ "iplayer-web.files.bbci.co.uk.": {},
+ "ipm.adblockplus.dev.": {},
+ "iprofiles.apple.com.": {},
+ "iprom.net.": {},
+ "ipv4.tracker.harry.lu.": {},
+ "irvine.remotepc.com.": {},
+ "isap.inskinad.com.": {},
+ "iscorp.com.": {},
+ "isjike.com.": {},
+ "ispeech.org.": {},
+ "istanbul.remotepc.com.": {},
+ "itc.cn.": {},
+ "itch.io.": {},
+ "itch.zone.": {},
+ "itinfoalvarezandmarsal-my.sharepoint.com.": {},
+ "itinfoalvarezandmarsal.sharepoint.com.": {},
+ "itm.cloud.com.": {},
+ "itoon.org.": {},
+ "itzmx.com.": {},
+ "ive8im-inapps.appsflyersdk.com.": {},
+ "ivview.com.": {},
+ "ivview.net.": {},
+ "ivytechccofindiana-my.sharepoint.com.": {},
+ "ixav-cse.avlyun.com.": {},
+ "izooto.com.": {},
+ "jabfm.org.": {},
+ "jazzpharma-my.sharepoint.com.": {},
+ "jazzpharmaceuticals.cyberhaven.io.": {},
+ "jbhunt-my.sharepoint.com.": {},
+ "jbhunt.sharepoint.com.": {},
+ "jcp-app.quantummetric.com.": {},
+ "jdcloud.com.": {},
+ "jeldweninc1-my.sharepoint.com.": {},
+ "jeldweninc1.sharepoint.com.": {},
+ "jgw-dra.jos.dbankcloud.cn.": {},
+ "jjcloud.box.com.": {},
+ "jjcloud.ent.box.com.": {},
+ "jjkeller-my.sharepoint.com.": {},
+ "jnj-my.sharepoint.com.": {},
+ "johannesburg.remotepc.com.": {},
+ "johnmuirhealth-my.sharepoint.com.": {},
+ "johnmuirhealth.sharepoint.com.": {},
+ "johnsmanville365-my.sharepoint.com.": {},
+ "joox.com.": {},
+ "jotfor.ms.": {},
+ "jp-prod.asyncgw.teams.microsoft.com.": {},
+ "jp.cinarra.com.": {},
+ "jp1.chat.si.riotgames.com.": {},
+ "jpost.com.": {},
+ "jpush.cn.": {},
+ "jpush.io.": {},
+ "js-eu1.hs-analytics.net.": {},
+ "js-eu1.hs-banner.com.": {},
+ "js-eu1.hs-scripts.com.": {},
+ "js-eu1.hsadspixel.net.": {},
+ "js-eu1.hscollectedforms.net.": {},
+ "js-eu1.hsforms.net.": {},
+ "js-eu1.hsleadflows.net.": {},
+ "js-eu1.hubspot.com.": {},
+ "js-eu1.hubspotfeedback.com.": {},
+ "js.hcaptcha.com.": {},
+ "js.users.51.la.": {},
+ "junglefrog.com.": {},
+ "k-aeu1.contentsquare.net.": {},
+ "k.163.com.": {},
+ "k8s1-event-tracker-am.indexexchange.com.": {},
+ "k8s1-event-tracker-la.indexexchange.com.": {},
+ "k8s1-event-tracker-ny.indexexchange.com.": {},
+ "k8s1-event-tracker-sj.indexexchange.com.": {},
+ "k8s1-event-tracker-va.indexexchange.com.": {},
+ "k8svkbknrqzgecxff.ay.delivery.": {},
+ "kajicam.com.": {},
+ "kaleidousercontent.com.": {},
+ "kameleoon.com.": {},
+ "kc1-my.sharepoint.com.": {},
+ "kc1.sharepoint.com.": {},
+ "keells-my.sharepoint.com.": {},
+ "kenh14.vn.": {},
+ "khsd-my.sharepoint.com.": {},
+ "kiev.remotepc.com.": {},
+ "kit-pro.fontawesome.com.": {},
+ "kiwisizing.com.": {},
+ "klagenfurt.remotepc.com.": {},
+ "klikan.caridisini.site.": {},
+ "kmovie.gifshow.com.": {},
+ "knightlab.com.": {},
+ "knock.app.": {},
+ "knoxville.remotepc.com.": {},
+ "komect.com.": {},
+ "kootenaihealth-my.sharepoint.com.": {},
+ "kornferry-my.sharepoint.com.": {},
+ "kornferry.sharepoint.com.": {},
+ "kstatic.googleusercontent.com.": {},
+ "kumed-my.sharepoint.com.": {},
+ "kumed.sharepoint.com.": {},
+ "kunlunaq.com.": {},
+ "kunlunar.com.": {},
+ "kunluncan.com.": {},
+ "kunlungem.com.": {},
+ "kunlungr.com.": {},
+ "kunlunhuf.com.": {},
+ "kunlunno.com.": {},
+ "kunlunsl.com.": {},
+ "kunlunso.com.": {},
+ "kv801.prod.do.dsp.mp.microsoft.com.": {},
+ "kw3qbu-cdn-settings.appsflyersdk.com.": {},
+ "kwimgs.com.": {},
+ "kzhi.tech.": {},
+ "l1-1.anzu.io.": {},
+ "la.remotepc.com.": {},
+ "la1.chat.si.riotgames.com.": {},
+ "la10.remotepc.com.": {},
+ "la11.remotepc.com.": {},
+ "la12.remotepc.com.": {},
+ "la2.remotepc.com.": {},
+ "la3.remotepc.com.": {},
+ "la4.remotepc.com.": {},
+ "la4lbg.uae2grp.ucweb.com.": {},
+ "la8.remotepc.com.": {},
+ "la9.remotepc.com.": {},
+ "labtech.iwsit.com.": {},
+ "lacounty-my.sharepoint.com.": {},
+ "lacounty.sharepoint.com.": {},
+ "lahuashanbx.com.": {},
+ "lan.sdk.linkedin.com.": {},
+ "lansing.remotepc.com.": {},
+ "laptop-updates.brave.com.": {},
+ "larksuite.com.": {},
+ "lasd-my.sharepoint.com.": {},
+ "lasd.sharepoint.com.": {},
+ "laureatelatammx-my.sharepoint.com.": {},
+ "lax.remotepc.com.": {},
+ "layerxsecurity.com.": {},
+ "layouthub.com.": {},
+ "lazada-mobile.oss-ap-southeast-1.aliyuncs.com.": {},
+ "lazada-msgacs.m.taobao.com.": {},
+ "lazada.co.id.": {},
+ "lazada.co.th.": {},
+ "lazada.com.": {},
+ "lazada.com.my.": {},
+ "lazada.com.ph.": {},
+ "lazada.sg.": {},
+ "lazada.vn.": {},
+ "lcmchealth-my.sharepoint.com.": {},
+ "lcmchealth.sharepoint.com.": {},
+ "ldap.google.com.": {},
+ "ldcorp-my.sharepoint.com.": {},
+ "ldcorp.sharepoint.com.": {},
+ "leaderboards.xboxlive.com.": {},
+ "leadmanagerfx.com.": {},
+ "lebo.cn.": {},
+ "leiniao.com.": {},
+ "lenovomm.com.": {},
+ "lenovosupport-app.quantummetric.com.": {},
+ "levect.com.": {},
+ "leveldata.poki.io.": {},
+ "leviton-my.sharepoint.com.": {},
+ "lexicon.33across.com.": {},
+ "lf16-geckocdn-offline.g-p-static.com.": {},
+ "lianmeng.360.cn.": {},
+ "license.gonative.io.": {},
+ "license.litespeedtech.com.": {},
+ "license.unity3d.com.": {},
+ "licensing.bitmovin.com.": {},
+ "licensing.microsoftcasualgames.com.": {},
+ "licensing.sbullet.com.": {},
+ "lightwidget.com.": {},
+ "like-video.com.": {},
+ "likr.tw.": {},
+ "lima.remotepc.com.": {},
+ "lincare-my.sharepoint.com.": {},
+ "lincare.sharepoint.com.": {},
+ "link-vision-picture-sgp.oss-ap-southeast-1.aliyuncs.com.": {},
+ "link.storjshare.io.": {},
+ "lisbon.remotepc.com.": {},
+ "lissabon.remotepc.com.": {},
+ "list.tronlink.org.": {},
+ "lists-e.tm-rt.sharepoint.com.": {},
+ "lit.connatix.com.cdn.cloudflare.net.": {},
+ "lit.elements.video.": {},
+ "litedev.sgp.hik-connect.com.": {},
+ "litedev.us.hik-connect.com.": {},
+ "litespeedtech.com.": {},
+ "littler-my.sharepoint.com.": {},
+ "live.126.net.": {},
+ "live.ngb.haplat.net.": {},
+ "live3.ngb.haplat.net.": {},
+ "live5.ngb.haplat.net.": {},
+ "livect.haplat.net.": {},
+ "livedmpsk12ia-my.sharepoint.com.": {},
+ "livedmpsk12ia.sharepoint.com.": {},
+ "livekilleenisd-my.sharepoint.com.": {},
+ "livekilleenisd.sharepoint.com.": {},
+ "liveutmb-my.sharepoint.com.": {},
+ "liveutmb.sharepoint.com.": {},
+ "ljubljana.remotepc.com.": {},
+ "llbean-sync.quantummetric.com.": {},
+ "load.fomo.com.": {},
+ "local.adguard.org.": {},
+ "local.info.g9hc4.cn.": {},
+ "log-api.newrelic.com.cdn.cloudflare.net.": {},
+ "log.getadblock.com.": {},
+ "log.lscreenc.com.": {},
+ "log.umsns.com.": {},
+ "log.voicecloud.cn.": {},
+ "log.zoom.us.": {},
+ "log1.cmpassport.com.": {},
+ "log2.cmpassport.com.": {},
+ "logger.moviead55.ru.": {},
+ "logging-service-prod.getepic.com.": {},
+ "logging.mp.lura.live.": {},
+ "login.epm.cyberark.com.": {},
+ "login.teamviewer.com.": {},
+ "login.ultipro.com.": {},
+ "loginradius.com.": {},
+ "logrocket.io.": {},
+ "logs-ingress.svc.vinted.com.": {},
+ "logus.xiaoyi.com.": {},
+ "logx.optimizely.com.": {},
+ "loklok.tv.": {},
+ "london.remotepc.com.": {},
+ "london2.remotepc.com.": {},
+ "london3.remotepc.com.": {},
+ "london4.remotepc.com.": {},
+ "london5.remotepc.com.": {},
+ "london6.remotepc.com.": {},
+ "london8.remotepc.com.": {},
+ "look.360.cn.": {},
+ "loopme.me.": {},
+ "lp-03.chat.online.citi.com.": {},
+ "lptag-cdn.liveperson.net.": {},
+ "lsagentrelay.lansweeper.com.": {},
+ "lscreenc.com.": {},
+ "ltfinc-my.sharepoint.com.": {},
+ "ltfinc.sharepoint.com.": {},
+ "ltfl.librarything.com.": {},
+ "ltpnetwork-my.sharepoint.com.": {},
+ "luckyorange.com.": {},
+ "ludashi.com.": {},
+ "lululemon-app.quantummetric.com.": {},
+ "lunamedia.live.": {},
+ "luxembourg.remotepc.com.": {},
+ "lycraservice-pa-cam-prod.googleapis.com.": {},
+ "lyric.alarmnet.com.": {},
+ "m.du4yh.com.": {},
+ "m104216-ucdn.mp.lura.live.": {},
+ "m107833-mcdn.mp.lura.live.": {},
+ "m5-zb.amap.com.": {},
+ "macclog-as.rj.link.": {},
+ "madisonschools-my.sharepoint.com.": {},
+ "madrid.remotepc.com.": {},
+ "maers.adrs.org.cn.": {},
+ "magichue.net.": {},
+ "maidenhead.remotepc.com.": {},
+ "mail.superhuman.com.": {},
+ "mailinblue.com.": {},
+ "majorel365-my.sharepoint.com.": {},
+ "malware-filter.gitlab.io.": {},
+ "mam.netease.com.": {},
+ "manage-dogfood.microsoft.com.": {},
+ "manage.wix.com.": {},
+ "manager.everbridge.net.": {},
+ "manager.intelligentlocations.io.": {},
+ "manassas.remotepc.com.": {},
+ "manchester.remotepc.com.": {},
+ "manifest.prod.boltdns.net.": {},
+ "marketing.beneplace.com.": {},
+ "marketingassets.staples.com.": {},
+ "marmot-cloud.com.": {},
+ "marseille.remotepc.com.": {},
+ "maserc-my.sharepoint.com.": {},
+ "masonitecloud-my.sharepoint.com.": {},
+ "masonitecloud.sharepoint.com.": {},
+ "master1.teamviewer.com.": {},
+ "master10.teamviewer.com.": {},
+ "master11.teamviewer.com.": {},
+ "master12.teamviewer.com.": {},
+ "master13.teamviewer.com.": {},
+ "master14.teamviewer.com.": {},
+ "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.": {},
+ "masterashley-my.sharepoint.com.": {},
+ "mathematica-my.sharepoint.com.": {},
+ "mattressfirm-my.sharepoint.com.": {},
+ "max-l.mediav.com.": {},
+ "mbboauth-1c.prd.cn.vwg-connect.cn.": {},
+ "mcallen.remotepc.com.": {},
+ "mcdermottinc-my.sharepoint.com.": {},
+ "mcdermottinc.sharepoint.com.": {},
+ "mcdermottwillemery-my.sharepoint.com.": {},
+ "mcdermottwillemery.sharepoint.com.": {},
+ "mclarenhealth.sharepoint.com.": {},
+ "mdap.tngdigital.com.my.": {},
+ "mdc-console.ifpserver.com.": {},
+ "mdp-upgrade-cn.heytapmobi.com.": {},
+ "meari-oss-us.oss-us-west-1.aliyuncs.com.": {},
+ "meari-us.oss-us-west-1.aliyuncs.com.": {},
+ "medellin.remotepc.com.": {},
+ "media-ams2-1.cdn.whatsapp.net.": {},
+ "media-ams4-1.cdn.whatsapp.net.": {},
+ "media-arn2-1.cdn.whatsapp.net.": {},
+ "media-atl3-1.cdn.whatsapp.net.": {},
+ "media-atl3-2.cdn.whatsapp.net.": {},
+ "media-ber1-1.cdn.whatsapp.net.": {},
+ "media-bog1-1.cdn.whatsapp.net.": {},
+ "media-bog2-1.cdn.whatsapp.net.": {},
+ "media-bom1-1.cdn.whatsapp.net.": {},
+ "media-bom1-2.cdn.whatsapp.net.": {},
+ "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-cgk1-3.cdn.whatsapp.net.": {},
+ "media-den4-1.cdn.whatsapp.net.": {},
+ "media-dfw5-1.cdn.whatsapp.net.": {},
+ "media-dfw5-2.cdn.whatsapp.net.": {},
+ "media-dus1-1.cdn.whatsapp.net.": {},
+ "media-fra3-1.cdn.whatsapp.net.": {},
+ "media-fra3-2.cdn.whatsapp.net.": {},
+ "media-fra5-1.cdn.whatsapp.net.": {},
+ "media-fra5-2.cdn.whatsapp.net.": {},
+ "media-gig4-1.cdn.whatsapp.net.": {},
+ "media-gru1-1.cdn.whatsapp.net.": {},
+ "media-gru1-2.cdn.whatsapp.net.": {},
+ "media-gru2-1.cdn.whatsapp.net.": {},
+ "media-gru2-2.cdn.whatsapp.net.": {},
+ "media-gua1-1.cdn.whatsapp.net.": {},
+ "media-ham3-1.cdn.whatsapp.net.": {},
+ "media-hel3-1.cdn.whatsapp.net.": {},
+ "media-hkg1-1.cdn.whatsapp.net.": {},
+ "media-hkg1-2.cdn.whatsapp.net.": {},
+ "media-hkg4-1.cdn.whatsapp.net.": {},
+ "media-hkg4-2.cdn.whatsapp.net.": {},
+ "media-hou1-1.cdn.whatsapp.net.": {},
+ "media-iad3-1.cdn.whatsapp.net.": {},
+ "media-iad3-2.cdn.whatsapp.net.": {},
+ "media-ist1-1.cdn.whatsapp.net.": {},
+ "media-kul2-1.cdn.whatsapp.net.": {},
+ "media-kul2-2.cdn.whatsapp.net.": {},
+ "media-kul3-1.cdn.whatsapp.net.": {},
+ "media-lax3-1.cdn.whatsapp.net.": {},
+ "media-lax3-2.cdn.whatsapp.net.": {},
+ "media-lga3-1.cdn.whatsapp.net.": {},
+ "media-lga3-2.cdn.whatsapp.net.": {},
+ "media-lhr6-1.cdn.whatsapp.net.": {},
+ "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-mad1-1.cdn.whatsapp.net.": {},
+ "media-mad2-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-mrs2-1.cdn.whatsapp.net.": {},
+ "media-mrs2-2.cdn.whatsapp.net.": {},
+ "media-msp1-1.cdn.whatsapp.net.": {},
+ "media-mty2-1.cdn.whatsapp.net.": {},
+ "media-muc2-1.cdn.whatsapp.net.": {},
+ "media-ord5-1.cdn.whatsapp.net.": {},
+ "media-ord5-2.cdn.whatsapp.net.": {},
+ "media-otp1-1.cdn.whatsapp.net.": {},
+ "media-prg1-1.cdn.whatsapp.net.": {},
+ "media-qro1-1.cdn.whatsapp.net.": {},
+ "media-qro1-2.cdn.whatsapp.net.": {},
+ "media-sea1-1.cdn.whatsapp.net.": {},
+ "media-sin6-1.cdn.whatsapp.net.": {},
+ "media-sin6-2.cdn.whatsapp.net.": {},
+ "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-sof1-2.cdn.whatsapp.net.": {},
+ "media-vie1-1.cdn.whatsapp.net.": {},
+ "media-waw1-1.cdn.whatsapp.net.": {},
+ "media-xsp1-1.cdn.whatsapp.net.": {},
+ "media-xsp1-2.cdn.whatsapp.net.": {},
+ "media-xsp1-3.cdn.whatsapp.net.": {},
+ "media-xsp2-1.cdn.whatsapp.net.": {},
+ "media-xxb1-1.cdn.whatsapp.net.": {},
+ "media-yyz1-1.cdn.whatsapp.net.": {},
+ "media.defense.gov.": {},
+ "media.graphassets.com.": {},
+ "media.ringcentral.com.": {},
+ "media.rockstargames.com.": {},
+ "media.springernature.com.": {},
+ "media.superhuman.com.": {},
+ "mediadata.xboxlive.com.": {},
+ "mediadelivery.net.": {},
+ "mediation-services.pubapp.network.": {},
+ "mediav.com.": {},
+ "mediavine-res.cloudinary.com.": {},
+ "medline0.sharepoint.com.": {},
+ "melbourne.remotepc.com.": {},
+ "memphis.remotepc.com.": {},
+ "mercadolivre.com.br.": {},
+ "metric-api.newrelic.com.cdn.cloudflare.net.": {},
+ "metric.picodi.global.": {},
+ "metrics-dre.data.dbankcloud.cn.": {},
+ "metrics-dre.dt.hihonorcloud.com.": {},
+ "metrics5.data.hicloud.com.": {},
+ "mexicocity.remotepc.com.": {},
+ "mf.b37mrtl.ru.": {},
+ "mgtv.com.": {},
+ "mia-itm-radar-testobject.citrix.com.": {},
+ "miami.remotepc.com.": {},
+ "miami2.remotepc.com.": {},
+ "miami3.remotepc.com.": {},
+ "miami4.remotepc.com.": {},
+ "miamidadecollegeprod-my.sharepoint.com.": {},
+ "mib2clu8.car-cloud-cn.net.": {},
+ "microad.jp.": {},
+ "microchiptechnology-my.sharepoint.com.": {},
+ "microchiptechnology.sharepoint.com.": {},
+ "microsoft-my.sharepoint.com.": {},
+ "microsoft.sharepoint.com.": {},
+ "microvirt.com.": {},
+ "mid4.linkedin.com.": {},
+ "mightytext.co.": {},
+ "milan.remotepc.com.": {},
+ "milestoneinternet.com.cdn.cloudflare.net.": {},
+ "milwaukeetool-my.sharepoint.com.": {},
+ "milwaukeetool.com.": {},
+ "mimir.vivaldi.com.": {},
+ "min-api.cryptocompare.com.": {},
+ "mini.browser.360.cn.": {},
+ "mirror.centos.iad1.serverforge.org.": {},
+ "mirror.fcix.net.": {},
+ "mirror.pit.teraswitch.com.": {},
+ "mirrors.rockylinux.org.": {},
+ "mitek-my.sharepoint.com.": {},
+ "mixi.media.": {},
+ "mm-mm.bing.net.trafficmanager.net.": {},
+ "mms.mckesson.com.": {},
+ "mn31.ultipro.com.": {},
+ "mn365.sharepoint.com.": {},
+ "moatpixel1.edgekey.net.": {},
+ "mobile-collector.newrelic.com.cdn.cloudflare.net.": {},
+ "mobile.bereal.com.": {},
+ "mobile.shuzilm.cn.": {},
+ "mobileapi.us.afterpay.com.": {},
+ "mobilecontent.costco.com.": {},
+ "mobiledataplan-pa.googleapis.com.": {},
+ "mobilelog.upqzfile.com.": {},
+ "mobilemaps-pa-gz.googleapis.com.": {},
+ "mobilemaps.googleapis.com.": {},
+ "modesto.remotepc.com.": {},
+ "moni-onrt-stsdk.vivo.com.cn.": {},
+ "monitor.fraudblocker.com.": {},
+ "monitoring.getelevar.com.": {},
+ "monitoring.worksighted.com.": {},
+ "monkeytype.com.": {},
+ "monsterenergycorp-my.sharepoint.com.": {},
+ "monsterenergycorp.sharepoint.com.": {},
+ "montage-updates.displaynote.com.": {},
+ "monticello.remotepc.com.": {},
+ "montreal.remotepc.com.": {},
+ "moodmedia.com.": {},
+ "motiondetection-us-1d.oss-us-west-1.aliyuncs.com.": {},
+ "motiondetection-us.oss-us-west-1.aliyuncs.com.": {},
+ "mouser.com.": {},
+ "moviead55.ru.": {},
+ "mp.360.cn.": {},
+ "mp.theepochtimes.com.": {},
+ "mpsaz-my.sharepoint.com.": {},
+ "mpsaz.sharepoint.com.": {},
+ "mpush-api.aliyun.com.": {},
+ "mq1n25.com.": {},
+ "mr.homedepot.com.": {},
+ "ms1app.pb.com.": {},
+ "msch.f.360.cn.": {},
+ "msdl.microsoft.com.": {},
+ "msf.3g.qq.com.": {},
+ "msg-img-hk.oss-cn-hongkong.aliyuncs.com.": {},
+ "msmat1.itsupport247.net.": {},
+ "mstate-my.sharepoint.com.": {},
+ "msync.tngdigital.com.my.": {},
+ "mtb-app.quantummetric.com.": {},
+ "mtncloud-my.sharepoint.com.": {},
+ "mtrace.qq.com.": {},
+ "mu.ariba.com.": {},
+ "mumbai.remotepc.com.": {},
+ "munich.remotepc.com.": {},
+ "musical.ly.": {},
+ "musicps.p2p.qq.com.": {},
+ "musicpunch.p2p.qq.com.": {},
+ "mvconf.f.360.cn.": {},
+ "mvconf.uk.cloud.360safe.com.": {},
+ "mvm.snapchat.com.": {},
+ "mx.amx.rcs.telephony.goog.": {},
+ "mxc-ios-app-logs-default.ap-southeast-1.log.aliyuncs.com.": {},
+ "mxc-logs-data-processing.ap-southeast-1.log.aliyuncs.com.": {},
+ "mxp-pusa01.app.blackbaud.net.": {},
+ "my.getadmiral.com.": {},
+ "my.jbi.global.": {},
+ "my.microsoftpersonalcontent.com.": {},
+ "my.nalpeiron.com.": {},
+ "myccmortgage-my.sharepoint.com.": {},
+ "mydigitalspace-my.sharepoint.com.": {},
+ "mydrive.connect.aig.": {},
+ "myisolved.com.": {},
+ "mylonestar-my.sharepoint.com.": {},
+ "myqcloud.com.": {},
+ "mysat.collegeboard.org.": {},
+ "myweblogon.com.": {},
+ "myworkdaycdn.com.cn.": {},
+ "n-1-la4x.ad-m.net.": {},
+ "n.gameads.io.": {},
+ "n.nativendo.de.": {},
+ "n13.ultipro.com.": {},
+ "n21.ultipro.com.": {},
+ "n21c.ultipro.com.": {},
+ "n33.ultipro.com.": {},
+ "na.lb.martianinc.co.": {},
+ "na137.epm.cyberark.com.": {},
+ "na2.chat.si.riotgames.com.": {},
+ "nagich.co.il.": {},
+ "nagich.com.": {},
+ "najva.com.": {},
+ "nam.veta.naver.com.": {},
+ "namequery.com.": {},
+ "namiml.com.": {},
+ "naperville.remotepc.com.": {},
+ "napps-2.com.": {},
+ "nashville.remotepc.com.": {},
+ "nation.foxnews.com.": {},
+ "nationalheritageacademies-my.sharepoint.com.": {},
+ "nationalheritageacademies.sharepoint.com.": {},
+ "nativecos.com.": {},
+ "nc-centos-mirror.iwebfusion.net.": {},
+ "nc-pod4-smp-device.apple.com.": {},
+ "nc.com.": {},
+ "nc.gifshow.com.": {},
+ "ncentral.centrexit.com.": {},
+ "ncjb-my.sharepoint.com.": {},
+ "nearme.com.cn.": {},
+ "nechicago.remotepc.com.": {},
+ "neonataltherapists.com.": {},
+ "netapp-my.sharepoint.com.": {},
+ "netapp.sharepoint.com.": {},
+ "netease.com.": {},
+ "netease.im.": {},
+ "netpop.app.": {},
+ "netsolssl.com.": {},
+ "new-sentry-relay.xiaohongshu.com.": {},
+ "new.adblockplus.org.": {},
+ "newcontinuum.net.": {},
+ "newhanovercountyschools-my.sharepoint.com.": {},
+ "neworleans.remotepc.com.": {},
+ "news-abroad.vivo.com.": {},
+ "news-af.feednews.com.": {},
+ "news-client.apple.com.": {},
+ "news-events.apple.com.": {},
+ "news-nar-aud.apple.com.": {},
+ "news-notification-events.apple.com.": {},
+ "news-sports-events.apple.com.": {},
+ "newsletter-edge.apple.com.": {},
+ "newsroom.bi.": {},
+ "newyork.remotepc.com.": {},
+ "newyork2.remotepc.com.": {},
+ "newyork3.remotepc.com.": {},
+ "nex.163.com.": {},
+ "nexstar.amp.permutive.com.": {},
+ "nexus2wlb.com.": {},
+ "nexx360.io.": {},
+ "nfm365-my.sharepoint.com.": {},
+ "nfm365.sharepoint.com.": {},
+ "ngb.haplat.net.": {},
+ "nhentai.net.": {},
+ "nhhospitals-my.sharepoint.com.": {},
+ "nhshumanservices423-my.sharepoint.com.": {},
+ "nhshumanservices423.sharepoint.com.": {},
+ "nice-team.net.": {},
+ "nie.netease.com.": {},
+ "nieuwsblad.be.": {},
+ "nike.com.multicdn.cloudinary.com.": {},
+ "nio365-my.sharepoint.com.": {},
+ "nio365.sharepoint.com.": {},
+ "nitropay.com.": {},
+ "nmhealth-my.sharepoint.com.": {},
+ "nmhealth.sharepoint.com.": {},
+ "noc.computerhelpnj.com.": {},
+ "node.setupad.com.": {},
+ "nordcurrent.com.": {},
+ "norma-external-collect.meizu.com.": {},
+ "notes-analytics-events.apple.com.": {},
+ "notes.services.box.com.": {},
+ "novel.itoon.org.": {},
+ "nps.gov.": {},
+ "nrc.nl.": {},
+ "ns-cloud-a1.googledomains.com.": {},
+ "ns-cloud-a2.googledomains.com.": {},
+ "ns-cloud-a3.googledomains.com.": {},
+ "ns-cloud-a4.googledomains.com.": {},
+ "ns-cloud-b1.googledomains.com.": {},
+ "ns-cloud-b2.googledomains.com.": {},
+ "ns-cloud-b3.googledomains.com.": {},
+ "ns-cloud-b4.googledomains.com.": {},
+ "ns-cloud-c1.googledomains.com.": {},
+ "ns-cloud-c2.googledomains.com.": {},
+ "ns-cloud-c3.googledomains.com.": {},
+ "ns-cloud-c4.googledomains.com.": {},
+ "ns-cloud-d1.googledomains.com.": {},
+ "ns-cloud-d2.googledomains.com.": {},
+ "ns-cloud-d3.googledomains.com.": {},
+ "ns-cloud-d4.googledomains.com.": {},
+ "ns-cloud-e1.googledomains.com.": {},
+ "ns-cloud-e2.googledomains.com.": {},
+ "ns-cloud-e3.googledomains.com.": {},
+ "ns-cloud-e4.googledomains.com.": {},
+ "ns.aliyuncs.com.": {},
+ "ns.identrust.com.": {},
+ "ns0105.secondary.cloudflare.com.": {},
+ "ns0160.secondary.cloudflare.com.": {},
+ "ns1.101domain.com.": {},
+ "ns1.bluehost.com.": {},
+ "ns1.cloudflare.net.": {},
+ "ns1.digitalocean.com.": {},
+ "ns1.g.aaplimg.com.": {},
+ "ns1.google.com.": {},
+ "ns1.identrust.com.": {},
+ "ns1dd441ab0.airspace-cdn.cbsivideo.com.": {},
+ "ns2.cloudflare.net.": {},
+ "ns2.g.aaplimg.com.": {},
+ "ns2.google.com.": {},
+ "ns3.cloudflare.net.": {},
+ "ns3.g.aaplimg.com.": {},
+ "ns3.google.com.": {},
+ "ns35.worldnic.com.": {},
+ "ns4.cloudflare.net.": {},
+ "ns4.g.aaplimg.com.": {},
+ "ns4.google.com.": {},
+ "ns5.cloudflare.net.": {},
+ "nsa.nalpeiron.com.": {},
+ "nsatc.net.": {},
+ "ntes53.netease.com.": {},
+ "ntp.aliyun.com.": {},
+ "ntp.arlo.com.": {},
+ "ntp.org.cn.": {},
+ "ntp1.aliyun.com.": {},
+ "ntp2.aliyun.com.": {},
+ "ntp3.aliyun.com.": {},
+ "ntp4.aliyun.com.": {},
+ "ntpool0.603.newcontinuum.net.": {},
+ "ntpool1.603.newcontinuum.net.": {},
+ "nttlimited-my.sharepoint.com.": {},
+ "nttlimited.sharepoint.com.": {},
+ "nuremberg.remotepc.com.": {},
+ "nvu-prd.mqtt.ivanticloud.com.": {},
+ "nw14.ultipro.com.": {},
+ "nwsalert.onelouder.com.": {},
+ "nzz.ch.": {},
+ "o15.officeredir.microsoft.com.": {},
+ "o300810.mp.lura.live.": {},
+ "oasisit-my.sharepoint.com.": {},
+ "oauth.ws.sonos.com.": {},
+ "obe0-my.sharepoint.com.": {},
+ "obihai.telephony.goog.": {},
+ "obs.ap-southeast-3.myhuaweicloud.com.": {},
+ "obsproject.com.": {},
+ "obus-dc2-cn.heytapmobi.com.": {},
+ "obus-dc20058-cn.heytapmobi.com.": {},
+ "obus-dc20123-cn.heytapmobi.com.": {},
+ "obus-dc20157-cn.heytapmobi.com.": {},
+ "obus-dctech-cn.heytapmobi.com.": {},
+ "oc1.chat.si.riotgames.com.": {},
+ "ocloud.oppomobile.com.": {},
+ "ocsa.office.microsoft.com.": {},
+ "ocsp.identrust.com.": {},
+ "ocsp.usertrust.com.": {},
+ "ocsredir.officeapps.live.com.": {},
+ "odsun.log.mgtv.com.": {},
+ "odw7bf.dood.video.": {},
+ "oec22-normal-alisg.tiktokv.com.": {},
+ "office.microsoft.com.": {},
+ "officepreviewredir.microsoft.com.": {},
+ "offline.toc.shopeemobile.com.": {},
+ "ohioix.mm.fcix.net.": {},
+ "ok7-crtrs.oktaedge.okta.com.": {},
+ "oklahomacity.remotepc.com.": {},
+ "olatheschoolsorg-my.sharepoint.com.": {},
+ "oldnavy-us.attn.tv.": {},
+ "om.ordergroove.com.": {},
+ "omats-my.sharepoint.com.": {},
+ "omats.sharepoint.com.": {},
+ "omiapp.me.": {},
+ "omitech.site.": {},
+ "omnihotels-my.sharepoint.com.": {},
+ "on-hwapps-o.api.leiniao.com.": {},
+ "onedrive.live.com.": {},
+ "oneharman-my.sharepoint.com.": {},
+ "oneharman.sharepoint.com.": {},
+ "onekey1.cmpassport.com.": {},
+ "oneplus.net.": {},
+ "onethingpcs.com.": {},
+ "onezapp.com.": {},
+ "online.citi.com.": {},
+ "onlinepress24-7.com.": {},
+ "onlinewebfonts.com.": {},
+ "onsite-api.listrak.com.cdn.cloudflare.net.": {},
+ "op.mykonf.com.": {},
+ "opamarketplace.com.": {},
+ "open.acgnxtracker.com.": {},
+ "open.acgtracker.com.": {},
+ "open.demonii.com.": {},
+ "open.oppomobile.com.": {},
+ "open.pixel.api.whale3.io.": {},
+ "opencmp.net.": {},
+ "opendsp.ru.": {},
+ "openrice.com.": {},
+ "openvpn.net.": {},
+ "opex-service-cn.allawntech.com.": {},
+ "oppo.com.": {},
+ "oppomobile.com.": {},
+ "optimize.ulinq.asia.": {},
+ "optimize.urekamedia.com.": {},
+ "optimizely.com.": {},
+ "optioncare.sharepoint.com.": {},
+ "or-mirror.iwebfusion.net.": {},
+ "oregon.remotepc.com.": {},
+ "orga.openrice.com.": {},
+ "orlando.remotepc.com.": {},
+ "osaka.remotepc.com.": {},
+ "oslab.shalltry.com.": {},
+ "oslab.transsion-os.com.": {},
+ "oss-ap-southeast-1.aliyuncs.com.": {},
+ "oss-ap-southeast-5.aliyuncs.com.": {},
+ "oss-cn-beijing.aliyuncs.com.": {},
+ "oss-cn-hongkong.aliyuncs.com.": {},
+ "oss-cn-shanghai.aliyuncs.com.": {},
+ "oss-cn-shenzhen.aliyuncs.com.": {},
+ "oss-enet.aliyuncs.com.": {},
+ "oss-eu-central-1.aliyuncs.com.": {},
+ "oss-us-east-1.aliyuncs.com.": {},
+ "oss-us-west-1.aliyuncs.com.": {},
+ "otc.t-systems.com.": {},
+ "otlp.nr-data.net.": {},
+ "otto.my.onetrust.eu.": {},
+ "ounion.api.mgtv.com.": {},
+ "oursummit-my.sharepoint.com.": {},
+ "oval.id.": {},
+ "overleaf.com.": {},
+ "overleafusercontent.com.": {},
+ "overseasccl-a.haplat.net.": {},
+ "overseasccl-b.haplat.net.": {},
+ "overseasccl-c.haplat.net.": {},
+ "overseasccl-major-a.haplat.net.": {},
+ "overseasccl-major-b.haplat.net.": {},
+ "overseasccl-major-c.haplat.net.": {},
+ "ovp.itv.com.": {},
+ "oxmudr-launches.appsflyersdk.com.": {},
+ "oxyinc-my.sharepoint.com.": {},
+ "p.adlooxtracking.com.": {},
+ "p.placed.com.": {},
+ "p.vivo.com.cn.": {},
+ "p107609.cedexis-test.com.": {},
+ "p107610.cedexis-test.com.": {},
+ "p107611.cedexis-test.com.": {},
+ "p109477.cedexis-test.com.": {},
+ "p17652.citrix-itm-test.com.": {},
+ "p17652.citrix-itm-test.com.c.footprint.net.": {},
+ "p20304.cedexis-test.com.": {},
+ "p20305.cedexis-test.com.": {},
+ "p20306.cedexis-test.com.": {},
+ "p20307b.cedexis-test.com.": {},
+ "p20308b.cedexis-test.com.": {},
+ "p20310.cedexis-test.com.": {},
+ "p20311.cedexis-test.com.": {},
+ "p20314.cedexis-test.com.": {},
+ "p20315.cedexis-test.com.": {},
+ "p2cdn.com.": {},
+ "p2p-cal-2.anker-in.com.": {},
+ "p2p-cal.anker-in.com.": {},
+ "p2p-ohi-2.anker-in.com.": {},
+ "p2p-par.anker-in.com.": {},
+ "p2p-sgp.anker-in.com.": {},
+ "p2p-vir.anker-in.com.": {},
+ "p2p.qq.com.": {},
+ "p2pm-ali.reolink.com.": {},
+ "p30605.cedexis-test.com.": {},
+ "p33-mailws.icloud.com.": {},
+ "p33231.cedexis-test.com.": {},
+ "p33234.cedexis-test.com.": {},
+ "p33236.cedexis-test.com.": {},
+ "p33242.cedexis-test.com.": {},
+ "p33245.cedexis-test.com.": {},
+ "p33249.cedexis-test.com.": {},
+ "p33251.cedexis-test.com.": {},
+ "p33256.cedexis-test.com.": {},
+ "p33259.cedexis-test.com.": {},
+ "p34854.cedexis-test.com.": {},
+ "p34856.cedexis-test.com.": {},
+ "p34858.cedexis-test.com.": {},
+ "p35399.cedexis-test.com.": {},
+ "p35883.cedexis-test.com.": {},
+ "p39264.cedexis-test.com.": {},
+ "p39604.cedexis-test.com.": {},
+ "p3a-creative.brave.com.": {},
+ "p3a-json.brave.com.": {},
+ "p40232.cedexis-test.com.": {},
+ "p40233.cedexis-test.com.": {},
+ "p40234.cedexis-test.com.": {},
+ "p40235.cedexis-test.com.": {},
+ "p40236.cedexis-test.com.": {},
+ "p40237.cedexis-test.com.": {},
+ "p40238.cedexis-test.com.": {},
+ "p40239.cedexis-test.com.": {},
+ "p40255.cedexis-test.com.": {},
+ "p40256.cedexis-test.com.": {},
+ "p40257.cedexis-test.com.": {},
+ "p40259.cedexis-test.com.": {},
+ "p40261.cedexis-test.com.": {},
+ "p40262.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.": {},
+ "p41255.cedexis-test.com.": {},
+ "p41256.cedexis-test.com.": {},
+ "p41259.cedexis-test.com.": {},
+ "p41261.cedexis-test.com.": {},
+ "p41266.cedexis-test.com.": {},
+ "p41267.cedexis-test.com.": {},
+ "p41268.cedexis-test.com.": {},
+ "p41272.cedexis-test.com.": {},
+ "p41273.cedexis-test.com.": {},
+ "p41274.cedexis-test.com.": {},
+ "p41281.cedexis-test.com.": {},
+ "p41905.cedexis-test.com.": {},
+ "p42051.cedexis-test.com.": {},
+ "p42052.cedexis-test.com.": {},
+ "p42053.cedexis-test.com.": {},
+ "p43671.cedexis-test.com.": {},
+ "p43707.cedexis-test.com.": {},
+ "p43773.cedexis-test.com.": {},
+ "p43774.cedexis-test.com.": {},
+ "p43775.cedexis-test.com.": {},
+ "p43776.cedexis-test.com.": {},
+ "p43813.cedexis-test.com.": {},
+ "p48434.cedexis-test.com.": {},
+ "p48435.cedexis-test.com.": {},
+ "p48436.cedexis-test.com.": {},
+ "p48437.cedexis-test.com.": {},
+ "p4p.arenabg.com.": {},
+ "p52066.cedexis-test.com.": {},
+ "p55-mailws.icloud.com.": {},
+ "p56745.cedexis-test.com.": {},
+ "p56746.cedexis-test.com.": {},
+ "p56747.cedexis-test.com.": {},
+ "p58-mailws.icloud.com.": {},
+ "p65-mailws.icloud.com.": {},
+ "p76593.cedexis-test.com.": {},
+ "p86069.cedexis-test.com.": {},
+ "p86070.cedexis-test.com.": {},
+ "p86071.cedexis-test.com.": {},
+ "p86072.cedexis-test.com.": {},
+ "p86073.cedexis-test.com.": {},
+ "p86074.cedexis-test.com.": {},
+ "p86075.cedexis-test.com.": {},
+ "p86076.cedexis-test.com.": {},
+ "p86077.cedexis-test.com.": {},
+ "p86078.cedexis-test.com.": {},
+ "p86079.cedexis-test.com.": {},
+ "p86080.cedexis-test.com.": {},
+ "p86081.cedexis-test.com.": {},
+ "p86082.cedexis-test.com.": {},
+ "p86083.cedexis-test.com.": {},
+ "p86084.cedexis-test.com.": {},
+ "p92860.cedexis-test.com.": {},
+ "p92861.cedexis-test.com.": {},
+ "p92862.cedexis-test.com.": {},
+ "p93644.cedexis-test.com.": {},
+ "p93645.cedexis-test.com.": {},
+ "p93646.cedexis-test.com.": {},
+ "p93647.cedexis-test.com.": {},
+ "p93648.cedexis-test.com.": {},
+ "p93649.cedexis-test.com.": {},
+ "p93650.cedexis-test.com.": {},
+ "p93651.cedexis-test.com.": {},
+ "p95707.cedexis-test.com.": {},
+ "p95708.cedexis-test.com.": {},
+ "p95711.cedexis-test.com.": {},
+ "p95717.cedexis-test.com.": {},
+ "p95721.cedexis-test.com.": {},
+ "p95722.cedexis-test.com.": {},
+ "p95723.cedexis-test.com.": {},
+ "p95724.cedexis-test.com.": {},
+ "p95725.cedexis-test.com.": {},
+ "p95726.cedexis-test.com.": {},
+ "p95727.cedexis-test.com.": {},
+ "p95728.cedexis-test.com.": {},
+ "paccarnet.sharepoint.com.": {},
+ "pai.googlezip.net.": {},
+ "palermo.remotepc.com.": {},
+ "palm.tech.": {},
+ "pandoraimavideo354813165505.s.moatpixel.com.": {},
+ "panorama.wixapps.net.": {},
+ "papi.walkme.com.": {},
+ "paris.remotepc.com.": {},
+ "parkhill1.sharepoint.com.": {},
+ "partiality.itunes.apple.com.": {},
+ "partners.thepennyhoarder.com.cdn.cloudflare.net.": {},
+ "pasadena.remotepc.com.": {},
+ "passportalmsp.com.": {},
+ "paulkitchendark.com.": {},
+ "pay.datatrans.com.": {},
+ "pay.wps.cn.": {},
+ "paycorinc-my.sharepoint.com.": {},
+ "paylocity1-my.sharepoint.com.": {},
+ "paylocity1.sharepoint.com.": {},
+ "payment.api.speechify.dev.": {},
+ "pbc.vliplatform.com.": {},
+ "pbe1.chat.si.riotgames.com.": {},
+ "pbsj.bricks-co.com.": {},
+ "pc-store.lenovomm.cn.": {},
+ "pcdn.brave.com.": {},
+ "pcs.baidu.com.": {},
+ "pd.cdnwidget.com.": {},
+ "pdengagementapi.trafficmanager.net.": {},
+ "pdfforge.org.": {},
+ "pdrnetwork-my.sharepoint.com.": {},
+ "penngaming-my.sharepoint.com.": {},
+ "penngaming.sharepoint.com.": {},
+ "penskeauto-my.sharepoint.com.": {},
+ "penskeauto.sharepoint.com.": {},
+ "peopleadmin.com.": {},
+ "pepsico-my.sharepoint.com.": {},
+ "pepsico.sharepoint.com.": {},
+ "pepsico.zoom.us.": {},
+ "perf-eu1.hsforms.com.": {},
+ "perimeter-ingress.attentivemobile.com.cdn.cloudflare.net.": {},
+ "permutive.arstechnica.com.": {},
+ "permutive.businessinsider.com.": {},
+ "permutive.com.": {},
+ "permutive.newyorker.com.": {},
+ "permutive.wired.com.": {},
+ "perrigo-my.sharepoint.com.": {},
+ "pf.intuit.com.": {},
+ "pfgsales-my.sharepoint.com.": {},
+ "pfgsales.sharepoint.com.": {},
+ "pgsth.kargo.com.": {},
+ "ph.globe.rcs.telephony.goog.": {},
+ "phoenix.remotepc.com.": {},
+ "phoenix2.remotepc.com.": {},
+ "phx02pap001.storage.live.com.": {},
+ "phx02pap002.storage.live.com.": {},
+ "phx02pap003.storage.live.com.": {},
+ "phx02pap004.storage.live.com.": {},
+ "phx02pap005.storage.live.com.": {},
+ "phx02pap006.storage.live.com.": {},
+ "phx02pap007.storage.live.com.": {},
+ "pi2850.ci.managedwhitelisting.com.": {},
+ "piano.io.": {},
+ "pikabu.ru.": {},
+ "ping-oci-med-us-1.mediaverse.ai.": {},
+ "ping.getadblock.com.": {},
+ "pingler.com.": {},
+ "pingmesh.bigo.sg.": {},
+ "piojm.tech.": {},
+ "pis.alicdn.com.": {},
+ "pittsburgh.remotepc.com.": {},
+ "pix.cdnwidget.com.": {},
+ "pixel-sync.trafficmanager.net.": {},
+ "pixel.gliacloud.com.": {},
+ "pjcr-my.sharepoint.com.": {},
+ "pk-live.cn.": {},
+ "pla-prod-scu-apim-01.azure-api.net.": {},
+ "planconfig.openspeech.cn.": {},
+ "platform.freedomadnetwork.com.": {},
+ "player03.com.": {},
+ "playmatic.video.": {},
+ "playstream.media.": {},
+ "plrm.zone.": {},
+ "pltw.org.": {},
+ "pm.geniusmonkey.com.": {},
+ "pngwing.com.": {},
+ "pns.alicdn.com.": {},
+ "pods.officeapps.live.com.": {},
+ "polarisind-my.sharepoint.com.": {},
+ "polarisind.sharepoint.com.": {},
+ "polling.zoom.us.": {},
+ "polyfill.archive.org.": {},
+ "popt.in.": {},
+ "portal.microsoftonline.com.": {},
+ "portal.myweblogon.com.": {},
+ "portals.mobi.": {},
+ "portland.remotepc.com.": {},
+ "portlandoregongov-my.sharepoint.com.": {},
+ "posthog.com.": {},
+ "postholdings-my.sharepoint.com.": {},
+ "pov.spectrum.net.": {},
+ "powerpoint-collab.officeapps.live.com.": {},
+ "ppgames.net.": {},
+ "pr-pod1-smp-device.apple.com.": {},
+ "pr-pod2-smp-device.apple.com.": {},
+ "pr-pod5-smp-device.apple.com.": {},
+ "pragmaticplay.net.": {},
+ "prebid-la.casalemedia.com.": {},
+ "prebid-ny.casalemedia.com.": {},
+ "prebid-sj.casalemedia.com.": {},
+ "prebid-va.casalemedia.com.": {},
+ "prebid.trustedstack.com.": {},
+ "prebidserver.pixfuture.com.": {},
+ "precisionmedicinegroup-my.sharepoint.com.": {},
+ "precisionmedicinegroup.sharepoint.com.": {},
+ "prediction-api-gateway-7igs62jc.uc.gateway.dev.": {},
+ "premierhealth-my.sharepoint.com.": {},
+ "premium.xvpn.io.": {},
+ "preyproject.com.": {},
+ "pringed.space.": {},
+ "printaudit.com.": {},
+ "printfriendly.com.": {},
+ "pro-blink-aks-tm.trafficmanager.net.": {},
+ "pro-swishapps-aks-tm.trafficmanager.net.": {},
+ "procore.com.": {},
+ "prod-client-api.v.aaplimg.com.": {},
+ "prod-default.lb.logrocket.network.": {},
+ "prod-event-relay-api.v.aaplimg.com.": {},
+ "prod-event-relay-books-api.v.aaplimg.com.": {},
+ "prod-event-relay-notes-api.v.aaplimg.com.": {},
+ "prod-event-relay-sports-api.v.aaplimg.com.": {},
+ "prod-event-relay-stocks-api.v.aaplimg.com.": {},
+ "prod-event-relay-weather-api.v.aaplimg.com.": {},
+ "prod-newsletter-edge.v.aaplimg.com.": {},
+ "prod-rso.lol.qq.com.": {},
+ "prod.api.letsencrypt.org.": {},
+ "prod.uno.demonware.net.": {},
+ "production-login-assets0.cdn.procore.com.": {},
+ "production-login-assets2.cdn.procore.com.": {},
+ "production.kabutoservices.com.": {},
+ "progressive-app.quantummetric.com.": {},
+ "promega-my.sharepoint.com.": {},
+ "prosperitybankusa.sharepoint.com.": {},
+ "protonvpn.com.": {},
+ "provaltech.com.": {},
+ "proxy-safebrowsing.googleapis.com.": {},
+ "proxy.mob.maps.yandex.net.": {},
+ "prwn.lenovomm.com.": {},
+ "ps.namequery.com.": {},
+ "psav-my.sharepoint.com.": {},
+ "psav.sharepoint.com.": {},
+ "pshud.365lpodds.com.": {},
+ "pths209-my.sharepoint.com.": {},
+ "ptingless.4d.silverbulletcloud.com.": {},
+ "pttor-my.sharepoint.com.": {},
+ "pttplc-my.sharepoint.com.": {},
+ "pub.affilimateapis.com.": {},
+ "pub.headerlift.com.": {},
+ "public.bn.files.1drv.com.": {},
+ "public.boxcloud.com.": {},
+ "public.dm.files.1drv.com.": {},
+ "publictracker.xyz.": {},
+ "publisher.liveperson.net.": {},
+ "pubsub.checkvideo.net.": {},
+ "pubtailer.com.": {},
+ "puffer.6.401402081.west-gcloud.codm.activision.com.": {},
+ "puhsd210.sharepoint.com.": {},
+ "pull-cmaf-f77-tt03.fcdn.eu.tiktokcdn.com.": {},
+ "pull-cmaf-f77-tt03.tiktokcdn.com.": {},
+ "punch.p2p.qq.com.": {},
+ "pusd11net-my.sharepoint.com.": {},
+ "pusd11net.sharepoint.com.": {},
+ "push-row.zui.com.": {},
+ "push.omiapp.me.": {},
+ "pushcrew.com.": {},
+ "pushmac.flexibits.com.": {},
+ "pushmart.net.": {},
+ "pushnetwork.com.": {},
+ "pushtrs7.push.hicloud.com.": {},
+ "puv.tt.browser.360.cn.": {},
+ "pwa.zoom.us.": {},
+ "pwcgov-my.sharepoint.com.": {},
+ "px4.ads.linkedin.com.": {},
+ "pypestream.com.": {},
+ "qa.sockets.stackexchange.com.": {},
+ "qagpublic.qg1.apps.qualys.ca.": {},
+ "qagpublic.qg1.apps.qualys.co.uk.": {},
+ "qagpublic.qg1.apps.qualys.com.": {},
+ "qagpublic.qg1.apps.qualys.eu.": {},
+ "qagpublic.qg2.apps.qualys.com.": {},
+ "qagpublic.qg2.apps.qualys.eu.": {},
+ "qagpublic.qg3.apps.qualys.com.": {},
+ "qagpublic.qg4.apps.qualys.com.": {},
+ "qcloud.com.": {},
+ "qfp.intuit.com.": {},
+ "qikify.com.": {},
+ "qookkagames.com.": {},
+ "qorvo-my.sharepoint.com.": {},
+ "qorvo.sharepoint.com.": {},
+ "qpic.cn.": {},
+ "qq.com.cn.": {},
+ "qsdn-web.qtlcname.com.": {},
+ "qualys.ca.": {},
+ "qualys.com.": {},
+ "qualys.eu.": {},
+ "quebeccity.remotepc.com.": {},
+ "questdiagnostics.sharepoint.com.": {},
+ "quickcep.com.": {},
+ "quotemedia.com.": {},
+ "quotientcrisp913871380293ftp.s.moatpixel.com.": {},
+ "qurl.f.360.cn.": {},
+ "qwant.com.": {},
+ "qxwz.com.": {},
+ "r.akulaku.net.": {},
+ "r.ingest-lr.com.": {},
+ "r.intake-lr.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.office.microsoft.com.": {},
+ "r.superhuman.com.": {},
+ "r1---sn-a5msenek.c.2mdn.net.": {},
+ "r1---sn-ab5l6nkd.c.2mdn.net.": {},
+ "r1---sn-ab5l6nr6.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrd.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrk.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrl.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrr.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrs.c.2mdn.net.": {},
+ "r1---sn-ab5l6nrz.c.2mdn.net.": {},
+ "r1---sn-ab5sznld.c.2mdn.net.": {},
+ "r1---sn-ab5sznly.c.2mdn.net.": {},
+ "r1---sn-ab5sznz6.c.2mdn.net.": {},
+ "r1---sn-ab5sznzd.c.2mdn.net.": {},
+ "r1---sn-ab5sznzk.c.2mdn.net.": {},
+ "r1---sn-ab5sznzr.c.2mdn.net.": {},
+ "r1---sn-ab5sznzs.c.2mdn.net.": {},
+ "r1---sn-ab5sznzy.c.2mdn.net.": {},
+ "r1---sn-ab5sznzz.c.2mdn.net.": {},
+ "r1---sn-p5qddn76.c.2mdn.net.": {},
+ "r1---sn-p5qs7nsk.c.2mdn.net.": {},
+ "r1---sn-q4fl6nd6.c.2mdn.net.": {},
+ "r1---sn-q4fl6nz6.c.2mdn.net.": {},
+ "r1---sn-q4fzen7s.c.2mdn.net.": {},
+ "r1---sn-vgqskn67.c.2mdn.net.": {},
+ "r1---sn-vgqskn6d.c.2mdn.net.": {},
+ "r1---sn-vgqsknls.c.2mdn.net.": {},
+ "r1---sn-vgqsknsk.c.2mdn.net.": {},
+ "r1---sn-vgqsknzd.c.2mdn.net.": {},
+ "r1---sn-vgqsknze.c.2mdn.net.": {},
+ "r1---sn-vgqsknzk.c.2mdn.net.": {},
+ "r1---sn-vgqsknzr.c.2mdn.net.": {},
+ "r1---sn-vgqsrn67.c.2mdn.net.": {},
+ "r1---sn-vgqsrn6e.c.2mdn.net.": {},
+ "r1---sn-vgqsrn6l.c.2mdn.net.": {},
+ "r1---sn-vgqsrnes.c.2mdn.net.": {},
+ "r1---sn-vgqsrnld.c.2mdn.net.": {},
+ "r1---sn-vgqsrnlz.c.2mdn.net.": {},
+ "r1---sn-vgqsrns6.c.2mdn.net.": {},
+ "r1---sn-vgqsrnsr.c.2mdn.net.": {},
+ "r1---sn-vgqsrnsy.c.2mdn.net.": {},
+ "r1---sn-vgqsrnz7.c.2mdn.net.": {},
+ "r1---sn-vgqsrnzy.c.2mdn.net.": {},
+ "r2---sn-a5mekndl.c.2mdn.net.": {},
+ "r2---sn-a5mlrnl6.c.2mdn.net.": {},
+ "r2---sn-ab5l6ndr.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-ab5l6nrl.c.2mdn.net.": {},
+ "r2---sn-ab5l6nrr.c.2mdn.net.": {},
+ "r2---sn-ab5l6nrs.c.2mdn.net.": {},
+ "r2---sn-ab5l6nrz.c.2mdn.net.": {},
+ "r2---sn-ab5sznld.c.2mdn.net.": {},
+ "r2---sn-ab5sznly.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-p5qddn7k.c.2mdn.net.": {},
+ "r2---sn-p5qlsn76.c.2mdn.net.": {},
+ "r2---sn-p5qlsnrr.c.2mdn.net.": {},
+ "r2---sn-p5qlsny6.c.2mdn.net.": {},
+ "r2---sn-p5qs7nzy.c.2mdn.net.": {},
+ "r2---sn-q4fl6nsd.c.2mdn.net.": {},
+ "r2---sn-q4flrnld.c.2mdn.net.": {},
+ "r2---sn-q4flrnsd.c.2mdn.net.": {},
+ "r2---sn-q4fzene7.c.2mdn.net.": {},
+ "r2---sn-q4fzenee.c.2mdn.net.": {},
+ "r2---sn-vgqskn66.c.2mdn.net.": {},
+ "r2---sn-vgqskn6d.c.2mdn.net.": {},
+ "r2---sn-vgqsknes.c.2mdn.net.": {},
+ "r2---sn-vgqsknlk.c.2mdn.net.": {},
+ "r2---sn-vgqsknz6.c.2mdn.net.": {},
+ "r2---sn-vgqsknzd.c.2mdn.net.": {},
+ "r2---sn-vgqsknzy.c.2mdn.net.": {},
+ "r2---sn-vgqsrn67.c.2mdn.net.": {},
+ "r2---sn-vgqsrns6.c.2mdn.net.": {},
+ "r2---sn-vgqsrnsy.c.2mdn.net.": {},
+ "r2---sn-vgqsrnzd.c.2mdn.net.": {},
+ "r2---sn-vgqsrnzk.c.2mdn.net.": {},
+ "r2---sn-vgqsrnzs.c.2mdn.net.": {},
+ "r3---sn-ab5l6ndr.c.2mdn.net.": {},
+ "r3---sn-ab5l6ndy.c.2mdn.net.": {},
+ "r3---sn-ab5l6nk6.c.2mdn.net.": {},
+ "r3---sn-ab5l6nr6.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-ab5sznzy.c.2mdn.net.": {},
+ "r3---sn-ab5sznzz.c.2mdn.net.": {},
+ "r3---sn-p5qddn7d.c.2mdn.net.": {},
+ "r3---sn-p5qlsn6l.c.2mdn.net.": {},
+ "r3---sn-p5qs7nzr.c.2mdn.net.": {},
+ "r3---sn-q4fl6ns6.c.2mdn.net.": {},
+ "r3---sn-q4fl6nzy.c.2mdn.net.": {},
+ "r3---sn-q4flrnes.c.2mdn.net.": {},
+ "r3---sn-q4flrnlz.c.2mdn.net.": {},
+ "r3---sn-q4fzen7l.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-vgqskn6z.c.2mdn.net.": {},
+ "r3---sn-vgqsknes.c.2mdn.net.": {},
+ "r3---sn-vgqsknsk.c.2mdn.net.": {},
+ "r3---sn-vgqsknz6.c.2mdn.net.": {},
+ "r3---sn-vgqsknzl.c.2mdn.net.": {},
+ "r3---sn-vgqsknzy.c.2mdn.net.": {},
+ "r3---sn-vgqsrne6.c.2mdn.net.": {},
+ "r3---sn-vgqsrnek.c.2mdn.net.": {},
+ "r3---sn-vgqsrnld.c.2mdn.net.": {},
+ "r3---sn-vgqsrnll.c.2mdn.net.": {},
+ "r3---sn-vgqsrns6.c.2mdn.net.": {},
+ "r3---sn-vgqsrnz7.c.2mdn.net.": {},
+ "r3---sn-vgqsrnzd.c.2mdn.net.": {},
+ "r3---sn-vgqsrnzr.c.2mdn.net.": {},
+ "r3---sn-vgqsrnzz.c.2mdn.net.": {},
+ "r4---sn-ab5l6nk6.c.2mdn.net.": {},
+ "r4---sn-ab5l6nkd.c.2mdn.net.": {},
+ "r4---sn-ab5l6nr6.c.2mdn.net.": {},
+ "r4---sn-ab5l6nrk.c.2mdn.net.": {},
+ "r4---sn-ab5l6nrl.c.2mdn.net.": {},
+ "r4---sn-ab5l6nrr.c.2mdn.net.": {},
+ "r4---sn-ab5l6nrs.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-ab5sznzr.c.2mdn.net.": {},
+ "r4---sn-ab5sznzs.c.2mdn.net.": {},
+ "r4---sn-ab5sznzy.c.2mdn.net.": {},
+ "r4---sn-ab5sznzz.c.2mdn.net.": {},
+ "r4---sn-p5qlsn7s.c.2mdn.net.": {},
+ "r4---sn-p5qlsnrr.c.2mdn.net.": {},
+ "r4---sn-p5qs7n6d.c.2mdn.net.": {},
+ "r4---sn-p5qs7nzr.c.2mdn.net.": {},
+ "r4---sn-q4fl6n6s.c.2mdn.net.": {},
+ "r4---sn-q4fl6nds.c.2mdn.net.": {},
+ "r4---sn-q4flrnld.c.2mdn.net.": {},
+ "r4---sn-q4flrnlz.c.2mdn.net.": {},
+ "r4---sn-q4fzen7l.c.2mdn.net.": {},
+ "r4---sn-q4fzen7y.c.2mdn.net.": {},
+ "r4---sn-vgqskn6d.c.2mdn.net.": {},
+ "r4---sn-vgqsknek.c.2mdn.net.": {},
+ "r4---sn-vgqsknld.c.2mdn.net.": {},
+ "r4---sn-vgqsknlk.c.2mdn.net.": {},
+ "r4---sn-vgqsknlr.c.2mdn.net.": {},
+ "r4---sn-vgqsknzd.c.2mdn.net.": {},
+ "r4---sn-vgqsrn66.c.2mdn.net.": {},
+ "r4---sn-vgqsrn6e.c.2mdn.net.": {},
+ "r4---sn-vgqsrn6l.c.2mdn.net.": {},
+ "r4---sn-vgqsrnsr.c.2mdn.net.": {},
+ "r4---sn-vgqsrnzk.c.2mdn.net.": {},
+ "r4---sn-vgqsrnzs.c.2mdn.net.": {},
+ "r4---sn-vgqsrnzz.c.2mdn.net.": {},
+ "r5---sn-ab5l6nk6.c.2mdn.net.": {},
+ "r5---sn-ab5l6nkd.c.2mdn.net.": {},
+ "r5---sn-ab5l6nr6.c.2mdn.net.": {},
+ "r5---sn-ab5l6nrd.c.2mdn.net.": {},
+ "r5---sn-ab5l6nrk.c.2mdn.net.": {},
+ "r5---sn-ab5l6nrl.c.2mdn.net.": {},
+ "r5---sn-ab5l6nrr.c.2mdn.net.": {},
+ "r5---sn-ab5l6nrs.c.2mdn.net.": {},
+ "r5---sn-ab5sznld.c.2mdn.net.": {},
+ "r5---sn-ab5sznzd.c.2mdn.net.": {},
+ "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-ab5sznzs.c.2mdn.net.": {},
+ "r5---sn-ab5sznzz.c.2mdn.net.": {},
+ "r5---sn-nx57ynse.c.2mdn.net.": {},
+ "r5---sn-nx5s7nel.c.2mdn.net.": {},
+ "r5---sn-p5qddn7z.c.2mdn.net.": {},
+ "r5---sn-p5qlsndz.c.2mdn.net.": {},
+ "r5---sn-p5qs7nsr.c.2mdn.net.": {},
+ "r5---sn-q4fl6nsr.c.2mdn.net.": {},
+ "r5---sn-q4fl6nz7.c.2mdn.net.": {},
+ "r5---sn-q4flrnld.c.2mdn.net.": {},
+ "r5---sn-q4flrnss.c.2mdn.net.": {},
+ "r5---sn-q4fzene7.c.2mdn.net.": {},
+ "r5---sn-vgqskn66.c.2mdn.net.": {},
+ "r5---sn-vgqskn67.c.2mdn.net.": {},
+ "r5---sn-vgqskn6d.c.2mdn.net.": {},
+ "r5---sn-vgqskn6z.c.2mdn.net.": {},
+ "r5---sn-vgqsknll.c.2mdn.net.": {},
+ "r5---sn-vgqsknly.c.2mdn.net.": {},
+ "r5---sn-vgqskns7.c.2mdn.net.": {},
+ "r5---sn-vgqsknse.c.2mdn.net.": {},
+ "r5---sn-vgqsknsk.c.2mdn.net.": {},
+ "r5---sn-vgqsknz6.c.2mdn.net.": {},
+ "r5---sn-vgqsknzd.c.2mdn.net.": {},
+ "r5---sn-vgqsknzl.c.2mdn.net.": {},
+ "r5---sn-vgqsknzs.c.2mdn.net.": {},
+ "r5---sn-vgqsrn66.c.2mdn.net.": {},
+ "r5---sn-vgqsrn67.c.2mdn.net.": {},
+ "r5---sn-vgqsrn6l.c.2mdn.net.": {},
+ "r5---sn-vgqsrnes.c.2mdn.net.": {},
+ "r5---sn-vgqsrnld.c.2mdn.net.": {},
+ "r5---sn-vgqsrnlz.c.2mdn.net.": {},
+ "raccorp-my.sharepoint.com.": {},
+ "radar.cedexis.com.": {},
+ "raleigh.remotepc.com.": {},
+ "randomhouse.app.box.com.": {},
+ "raspbian.raspberrypi.org.": {},
+ "rba-screen.healthsafe-id.com.": {},
+ "rba.onehealthcareid.com.": {},
+ "rbm-us.storage.googleapis.com.": {},
+ "rcs-acs-mcc510.jibe.google.com.": {},
+ "rcs-acs-tmobile-us.jibe.google.com.": {},
+ "rcs-copper-us.googleapis.com.": {},
+ "rcs-user-content-us.storage.googleapis.com.": {},
+ "rcs.telephony.goog.": {},
+ "rctiplus.id.": {},
+ "rd.com.": {},
+ "realtime.luckyorange.com.": {},
+ "realtime.services.box.net.": {},
+ "realtime2ws.nitrotype.com.": {},
+ "recharge-prod-apim.azure-api.net.": {},
+ "recombee.com.": {},
+ "recorder.sessionstack.com.": {},
+ "recruiting.ultipro.com.": {},
+ "recruiting2.ultipro.com.": {},
+ "reedelsevier-my.sharepoint.com.": {},
+ "reedelsevier.sharepoint.com.": {},
+ "referconfigexternal.americanexpress.com.": {},
+ "referee.xiaohongshu.com.": {},
+ "reflector.makerbot.com.": {},
+ "regional.azure-api.net.": {},
+ "registration.prap01.cmdagent.trafficmanager.net.": {},
+ "registration.prau01.cmdagent.trafficmanager.net.": {},
+ "registration.preu01.cmdagent.trafficmanager.net.": {},
+ "registration.prna01.cmdagent.trafficmanager.net.": {},
+ "regn-my.sharepoint.com.": {},
+ "regn.sharepoint.com.": {},
+ "reichelcormier.bid.": {},
+ "rejuvenation.com.": {},
+ "relay.shhnowisnottheti.me.": {},
+ "remote.control4.com.": {},
+ "remote.melonhead.me.": {},
+ "renklitoplar.com.": {},
+ "repo.zabbix.com.": {},
+ "republicservices-my.sharepoint.com.": {},
+ "republicservices.sharepoint.com.": {},
+ "request-global.czilladx.com.": {},
+ "resideo.com.": {},
+ "resource.digitalinsight.com.": {},
+ "restrict.youtube.com.": {},
+ "restrictmoderate.youtube.com.": {},
+ "retailrocket.net.": {},
+ "retailstarbucks1com-my.sharepoint.com.": {},
+ "retailstarbucks1com.sharepoint.com.": {},
+ "retcode-sg-lazada.arms.aliyuncs.com.": {},
+ "retcode-us-west-1.arms.aliyuncs.com.": {},
+ "rethinkad.com.": {},
+ "retinavue.net.": {},
+ "reverso.net.": {},
+ "revive-adserver.net.": {},
+ "rhmail-my.sharepoint.com.": {},
+ "rhmail.sharepoint.com.": {},
+ "ri9864.ci.managedwhitelisting.com.": {},
+ "rich-content.thescore.com.": {},
+ "richrelevance.com.": {},
+ "ridge.com.": {},
+ "rivhs-my.sharepoint.com.": {},
+ "rl.quantummetric.com.": {},
+ "rlm.haokan.mobi.": {},
+ "rmm.acctek.com.": {},
+ "rmm.aunalytics.com.": {},
+ "rms-dra.platform.dbankcloud.cn.": {},
+ "rms-dre.platform.dbankcloud.cn.": {},
+ "rn-resource-app.xiaohongshu.com.": {},
+ "roberthalf-my.sharepoint.com.": {},
+ "roblox-c5.cachefly.net.": {},
+ "roborock.com.": {},
+ "rocketsutoledo-my.sharepoint.com.": {},
+ "rockwellautomation-my.sharepoint.com.": {},
+ "rockwellautomation.sharepoint.com.": {},
+ "rockylinux.org.": {},
+ "rogueone.aristotleinsight.com.": {},
+ "rollbar.com.": {},
+ "romsp-unifyconfig.vivo.com.cn.": {},
+ "roninchain.com.": {},
+ "rottentomatoes-app.quantummetric.com.": {},
+ "router.teamviewer.com.": {},
+ "roxy.azurefd.net.": {},
+ "rpt.cedexis.com.": {},
+ "rq.wh.cmcm.com.": {},
+ "rr1---sn-02o-ao3e.googlevideo.com.": {},
+ "rr1---sn-0nnpbo5a-bggl.googlevideo.com.": {},
+ "rr1---sn-0op8v4h5pox-cbgl.googlevideo.com.": {},
+ "rr1---sn-2aqu-hoaly.googlevideo.com.": {},
+ "rr1---sn-2imern76.googlevideo.com.": {},
+ "rr1---sn-2imern76.gvt1.com.": {},
+ "rr1---sn-2imern7d.googlevideo.com.": {},
+ "rr1---sn-2imern7d.gvt1.com.": {},
+ "rr1---sn-2imeyn7k.googlevideo.com.": {},
+ "rr1---sn-2imeyn7k.gvt1.com.": {},
+ "rr1---sn-2napbiu-p5ie.googlevideo.com.": {},
+ "rr1---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr1---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr1---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr1---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr1---sn-2vgu0b5auxaxjvh-v2vz.googlevideo.com.": {},
+ "rr1---sn-30a7rne6.googlevideo.com.": {},
+ "rr1---sn-30a7rned.googlevideo.com.": {},
+ "rr1---sn-30a7rnek.googlevideo.com.": {},
+ "rr1---sn-30a7rner.googlevideo.com.": {},
+ "rr1---sn-30a7ynek.googlevideo.com.": {},
+ "rr1---sn-30a7yner.googlevideo.com.": {},
+ "rr1---sn-30a7yney.googlevideo.com.": {},
+ "rr1---sn-30a7ynl7.googlevideo.com.": {},
+ "rr1---sn-3n4pcxg-pjul.googlevideo.com.": {},
+ "rr1---sn-42u-nboze.googlevideo.com.": {},
+ "rr1---sn-42u-nbozl.googlevideo.com.": {},
+ "rr1---sn-42u-nbozz.googlevideo.com.": {},
+ "rr1---sn-4g5e6ns6.googlevideo.com.": {},
+ "rr1---sn-4g5e6ns7.googlevideo.com.": {},
+ "rr1---sn-4g5e6nsd.googlevideo.com.": {},
+ "rr1---sn-4g5e6nsk.googlevideo.com.": {},
+ "rr1---sn-4g5e6nsr.googlevideo.com.": {},
+ "rr1---sn-4g5e6nss.googlevideo.com.": {},
+ "rr1---sn-4g5e6nsy.googlevideo.com.": {},
+ "rr1---sn-4g5e6nsz.googlevideo.com.": {},
+ "rr1---sn-4g5e6nz7.googlevideo.com.": {},
+ "rr1---sn-4g5e6nze.googlevideo.com.": {},
+ "rr1---sn-4g5e6nzl.googlevideo.com.": {},
+ "rr1---sn-4g5e6nzs.googlevideo.com.": {},
+ "rr1---sn-4g5e6nzz.googlevideo.com.": {},
+ "rr1---sn-4g5edn6k.googlevideo.com.": {},
+ "rr1---sn-4g5edn6r.googlevideo.com.": {},
+ "rr1---sn-4g5edn6y.googlevideo.com.": {},
+ "rr1---sn-4g5ednd7.googlevideo.com.": {},
+ "rr1---sn-4g5edndd.googlevideo.com.": {},
+ "rr1---sn-4g5ednde.googlevideo.com.": {},
+ "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.": {},
+ "rr1---sn-4g5ednld.googlevideo.com.": {},
+ "rr1---sn-4g5ednly.googlevideo.com.": {},
+ "rr1---sn-4g5edns6.googlevideo.com.": {},
+ "rr1---sn-4g5edns7.googlevideo.com.": {},
+ "rr1---sn-4g5ednsd.googlevideo.com.": {},
+ "rr1---sn-4g5ednse.googlevideo.com.": {},
+ "rr1---sn-4g5ednsk.googlevideo.com.": {},
+ "rr1---sn-4g5ednsl.googlevideo.com.": {},
+ "rr1---sn-4g5ednss.googlevideo.com.": {},
+ "rr1---sn-4g5ednsy.googlevideo.com.": {},
+ "rr1---sn-4g5ednsz.googlevideo.com.": {},
+ "rr1---sn-4g5ednz7.googlevideo.com.": {},
+ "rr1---sn-4g5lzne6.googlevideo.com.": {},
+ "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-4g5lznle.googlevideo.com.": {},
+ "rr1---sn-4g5lznls.googlevideo.com.": {},
+ "rr1---sn-4g5lznlz.googlevideo.com.": {},
+ "rr1---sn-4jjo-apnl.googlevideo.com.": {},
+ "rr1---sn-4pgnuapbiu-5acs.googlevideo.com.": {},
+ "rr1---sn-4pgnuapbiu-hiue.googlevideo.com.": {},
+ "rr1---sn-5hne6n6e.googlevideo.com.": {},
+ "rr1---sn-5hne6n6l.googlevideo.com.": {},
+ "rr1---sn-5hne6ns6.googlevideo.com.": {},
+ "rr1---sn-5hne6nsd.googlevideo.com.": {},
+ "rr1---sn-5hne6nsk.googlevideo.com.": {},
+ "rr1---sn-5hne6nsr.googlevideo.com.": {},
+ "rr1---sn-5hne6nsy.googlevideo.com.": {},
+ "rr1---sn-5hne6nsz.googlevideo.com.": {},
+ "rr1---sn-5hne6nz6.googlevideo.com.": {},
+ "rr1---sn-5hne6nzd.googlevideo.com.": {},
+ "rr1---sn-5hne6nzk.googlevideo.com.": {},
+ "rr1---sn-5hne6nzs.googlevideo.com.": {},
+ "rr1---sn-5hne6nzy.googlevideo.com.": {},
+ "rr1---sn-5hnednss.googlevideo.com.": {},
+ "rr1---sn-5hnednsz.googlevideo.com.": {},
+ "rr1---sn-5hnekn76.googlevideo.com.": {},
+ "rr1---sn-5hnekn7d.googlevideo.com.": {},
+ "rr1---sn-5hnekn7k.googlevideo.com.": {},
+ "rr1---sn-5hnekn7l.googlevideo.com.": {},
+ "rr1---sn-5hnekn7s.googlevideo.com.": {},
+ "rr1---sn-5hnekn7z.googlevideo.com.": {},
+ "rr1---sn-5hneknee.googlevideo.com.": {},
+ "rr1---sn-5hneknek.googlevideo.com.": {},
+ "rr1---sn-5hneknes.googlevideo.com.": {},
+ "rr1---sn-5pgnugx5h-hn2s.googlevideo.com.": {},
+ "rr1---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr1---sn-5uaezndd.googlevideo.com.": {},
+ "rr1---sn-5uaezne6.googlevideo.com.": {},
+ "rr1---sn-5uaezned.googlevideo.com.": {},
+ "rr1---sn-5uaeznez.googlevideo.com.": {},
+ "rr1---sn-5uaeznl6.googlevideo.com.": {},
+ "rr1---sn-5uaeznld.googlevideo.com.": {},
+ "rr1---sn-5uaeznls.googlevideo.com.": {},
+ "rr1---sn-5uaeznly.googlevideo.com.": {},
+ "rr1---sn-5uaeznlz.googlevideo.com.": {},
+ "rr1---sn-5uaeznrz.googlevideo.com.": {},
+ "rr1---sn-5uaezns7.googlevideo.com.": {},
+ "rr1---sn-5uaeznse.googlevideo.com.": {},
+ "rr1---sn-5uaeznsl.googlevideo.com.": {},
+ "rr1---sn-5uaeznss.googlevideo.com.": {},
+ "rr1---sn-5uaezny6.googlevideo.com.": {},
+ "rr1---sn-5uaeznys.googlevideo.com.": {},
+ "rr1---sn-5uaeznyz.googlevideo.com.": {},
+ "rr1---sn-5uaeznze.googlevideo.com.": {},
+ "rr1---sn-5ualdnle.googlevideo.com.": {},
+ "rr1---sn-5ualdnlr.googlevideo.com.": {},
+ "rr1---sn-5ualdnls.googlevideo.com.": {},
+ "rr1---sn-5ualdns6.googlevideo.com.": {},
+ "rr1---sn-5ualdns7.googlevideo.com.": {},
+ "rr1---sn-5ualdnsd.googlevideo.com.": {},
+ "rr1---sn-5ualdnse.googlevideo.com.": {},
+ "rr1---sn-5ualdnsk.googlevideo.com.": {},
+ "rr1---sn-5ualdnsl.googlevideo.com.": {},
+ "rr1---sn-5ualdnsr.googlevideo.com.": {},
+ "rr1---sn-5ualdnss.googlevideo.com.": {},
+ "rr1---sn-5ualdnsy.googlevideo.com.": {},
+ "rr1---sn-5ualdnsz.googlevideo.com.": {},
+ "rr1---sn-5ualdnz7.googlevideo.com.": {},
+ "rr1---sn-5ualdnze.googlevideo.com.": {},
+ "rr1---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr1---sn-8qj-i5okl.googlevideo.com.": {},
+ "rr1---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr1---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr1---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr1---sn-8qj-nbo6y.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-a5me.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab56.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab5d.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab5e.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab5l.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab5s.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-nh4e.googlevideo.com.": {},
+ "rr1---sn-8xgp1vo-p5ie.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-9gv7ene6.googlevideo.com.": {},
+ "rr1---sn-9gv7ened.googlevideo.com.": {},
+ "rr1---sn-9gv7zn76.googlevideo.com.": {},
+ "rr1---sn-9gv7zn7e.googlevideo.com.": {},
+ "rr1---sn-9gv7zn7r.googlevideo.com.": {},
+ "rr1---sn-9gv7zn7y.googlevideo.com.": {},
+ "rr1---sn-a5m7lnl6.googlevideo.com.": {},
+ "rr1---sn-a5m7lnl6.gvt1.com.": {},
+ "rr1---sn-a5m7lnld.googlevideo.com.": {},
+ "rr1---sn-a5mekn6d.googlevideo.com.": {},
+ "rr1---sn-a5mekn6k.googlevideo.com.": {},
+ "rr1---sn-a5mekn6l.googlevideo.com.": {},
+ "rr1---sn-a5mekn6r.googlevideo.com.": {},
+ "rr1---sn-a5mekn6s.googlevideo.com.": {},
+ "rr1---sn-a5mekn6z.googlevideo.com.": {},
+ "rr1---sn-a5mekn6z.gvt1.com.": {},
+ "rr1---sn-a5meknde.googlevideo.com.": {},
+ "rr1---sn-a5meknde.gvt1.com.": {},
+ "rr1---sn-a5mekndl.googlevideo.com.": {},
+ "rr1---sn-a5mekndl.gvt1.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-a5meknzl.gvt1.com.": {},
+ "rr1---sn-a5meknzr.googlevideo.com.": {},
+ "rr1---sn-a5mlrnek.googlevideo.com.": {},
+ "rr1---sn-a5mlrnek.gvt1.com.": {},
+ "rr1---sn-a5mlrnl6.googlevideo.com.": {},
+ "rr1---sn-a5mlrnl6.gvt1.com.": {},
+ "rr1---sn-a5mlrnll.googlevideo.com.": {},
+ "rr1---sn-a5mlrnll.gvt1.com.": {},
+ "rr1---sn-a5mlrnls.googlevideo.com.": {},
+ "rr1---sn-a5mlrnls.gvt1.com.": {},
+ "rr1---sn-a5mlrnlz.googlevideo.com.": {},
+ "rr1---sn-a5mlrnlz.gvt1.com.": {},
+ "rr1---sn-a5msen76.googlevideo.com.": {},
+ "rr1---sn-a5msen76.gvt1.com.": {},
+ "rr1---sn-a5msen7l.googlevideo.com.": {},
+ "rr1---sn-a5msen7s.googlevideo.com.": {},
+ "rr1---sn-a5msen7z.googlevideo.com.": {},
+ "rr1---sn-a5msenek.googlevideo.com.": {},
+ "rr1---sn-a5msenek.gvt1.com.": {},
+ "rr1---sn-a5msener.googlevideo.com.": {},
+ "rr1---sn-a5msenes.googlevideo.com.": {},
+ "rr1---sn-a5msenes.gvt1.com.": {},
+ "rr1---sn-a5msenl7.googlevideo.com.": {},
+ "rr1---sn-a5msenl7.gvt1.com.": {},
+ "rr1---sn-a5msenle.googlevideo.com.": {},
+ "rr1---sn-a5msenle.gvt1.com.": {},
+ "rr1---sn-a5msenll.googlevideo.com.": {},
+ "rr1---sn-a5oj5nuxg-hque.googlevideo.com.": {},
+ "rr1---sn-a5oj5nuxg-hque.gvt1.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-ab5l6nr6.gvt1.com.": {},
+ "rr1---sn-ab5l6nrd.googlevideo.com.": {},
+ "rr1---sn-ab5l6nrk.googlevideo.com.": {},
+ "rr1---sn-ab5l6nrl.googlevideo.com.": {},
+ "rr1---sn-ab5l6nrl.gvt1.com.": {},
+ "rr1---sn-ab5l6nrr.googlevideo.com.": {},
+ "rr1---sn-ab5l6nrs.googlevideo.com.": {},
+ "rr1---sn-ab5l6nrz.googlevideo.com.": {},
+ "rr1---sn-ab5sznld.googlevideo.com.": {},
+ "rr1---sn-ab5sznlk.googlevideo.com.": {},
+ "rr1---sn-ab5sznly.googlevideo.com.": {},
+ "rr1---sn-ab5sznz6.googlevideo.com.": {},
+ "rr1---sn-ab5sznzd.googlevideo.com.": {},
+ "rr1---sn-ab5sznze.googlevideo.com.": {},
+ "rr1---sn-ab5sznzk.googlevideo.com.": {},
+ "rr1---sn-ab5sznzk.gvt1.com.": {},
+ "rr1---sn-ab5sznzl.googlevideo.com.": {},
+ "rr1---sn-ab5sznzr.googlevideo.com.": {},
+ "rr1---sn-ab5sznzr.gvt1.com.": {},
+ "rr1---sn-ab5sznzs.googlevideo.com.": {},
+ "rr1---sn-ab5sznzs.gvt1.com.": {},
+ "rr1---sn-ab5sznzy.googlevideo.com.": {},
+ "rr1---sn-ab5sznzz.googlevideo.com.": {},
+ "rr1---sn-aigl6n6s.googlevideo.com.": {},
+ "rr1---sn-aigl6nek.googlevideo.com.": {},
+ "rr1---sn-aigl6ner.googlevideo.com.": {},
+ "rr1---sn-aigl6ney.googlevideo.com.": {},
+ "rr1---sn-aigl6nl7.googlevideo.com.": {},
+ "rr1---sn-aigl6ns6.googlevideo.com.": {},
+ "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-aigl6nzr.googlevideo.com.": {},
+ "rr1---sn-aigl6nzs.googlevideo.com.": {},
+ "rr1---sn-aigzrn76.googlevideo.com.": {},
+ "rr1---sn-aigzrn7d.googlevideo.com.": {},
+ "rr1---sn-aigzrn7e.googlevideo.com.": {},
+ "rr1---sn-aigzrn7k.googlevideo.com.": {},
+ "rr1---sn-aigzrn7l.googlevideo.com.": {},
+ "rr1---sn-aigzrn7s.googlevideo.com.": {},
+ "rr1---sn-aigzrn7z.googlevideo.com.": {},
+ "rr1---sn-aigzrne7.googlevideo.com.": {},
+ "rr1---sn-aigzrnse.googlevideo.com.": {},
+ "rr1---sn-aigzrnsl.googlevideo.com.": {},
+ "rr1---sn-aigzrnsr.googlevideo.com.": {},
+ "rr1---sn-aigzrnss.googlevideo.com.": {},
+ "rr1---sn-aigzrnss.gvt1.com.": {},
+ "rr1---sn-aigzrnsz.googlevideo.com.": {},
+ "rr1---sn-aigzrnz7.googlevideo.com.": {},
+ "rr1---sn-aigzrnze.googlevideo.com.": {},
+ "rr1---sn-apn7en7l.googlevideo.com.": {},
+ "rr1---sn-apn7en7s.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-bvvbax-2iae.googlevideo.com.": {},
+ "rr1---sn-c0q7lnz7.googlevideo.com.": {},
+ "rr1---sn-cvb7lne7.googlevideo.com.": {},
+ "rr1---sn-cvb7lnee.googlevideo.com.": {},
+ "rr1---sn-cvb7lnls.googlevideo.com.": {},
+ "rr1---sn-cvb7lnlz.googlevideo.com.": {},
+ "rr1---sn-cvb7sn7k.googlevideo.com.": {},
+ "rr1---sn-cvb7sn7r.googlevideo.com.": {},
+ "rr1---sn-fpnjoxu-hnol.googlevideo.com.": {},
+ "rr1---sn-gpuuxg-hxhl.googlevideo.com.": {},
+ "rr1---sn-gpuuxg-hxhl.gvt1.com.": {},
+ "rr1---sn-gpuuxg-hxhs.googlevideo.com.": {},
+ "rr1---sn-gpuuxg-hxhs.gvt1.com.": {},
+ "rr1---sn-h0jeened.googlevideo.com.": {},
+ "rr1---sn-h0jeenek.googlevideo.com.": {},
+ "rr1---sn-h0jeener.googlevideo.com.": {},
+ "rr1---sn-h0jeenl6.googlevideo.com.": {},
+ "rr1---sn-h0jeenld.googlevideo.com.": {},
+ "rr1---sn-h0jeenle.googlevideo.com.": {},
+ "rr1---sn-h0jeln7l.googlevideo.com.": {},
+ "rr1---sn-h0jelne6.googlevideo.com.": {},
+ "rr1---sn-h0jelne7.googlevideo.com.": {},
+ "rr1---sn-h0jelnez.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-hp57kn6y.googlevideo.com.": {},
+ "rr1---sn-hp57knd6.googlevideo.com.": {},
+ "rr1---sn-hp57kndd.googlevideo.com.": {},
+ "rr1---sn-hp57kndk.googlevideo.com.": {},
+ "rr1---sn-hp57kndr.googlevideo.com.": {},
+ "rr1---sn-hp57kndr.gvt1.com.": {},
+ "rr1---sn-hp57knds.googlevideo.com.": {},
+ "rr1---sn-hp57kndy.googlevideo.com.": {},
+ "rr1---sn-hp57kndz.googlevideo.com.": {},
+ "rr1---sn-hp57kndz.gvt1.com.": {},
+ "rr1---sn-hp57yn7r.googlevideo.com.": {},
+ "rr1---sn-hp57yn7y.googlevideo.com.": {},
+ "rr1---sn-hp57yne7.googlevideo.com.": {},
+ "rr1---sn-hp57ynl6.googlevideo.com.": {},
+ "rr1---sn-hp57ynl6.gvt1.com.": {},
+ "rr1---sn-hp57ynlr.googlevideo.com.": {},
+ "rr1---sn-hp57ynly.googlevideo.com.": {},
+ "rr1---sn-hp57yns7.googlevideo.com.": {},
+ "rr1---sn-hp57ynse.googlevideo.com.": {},
+ "rr1---sn-hp57ynsl.googlevideo.com.": {},
+ "rr1---sn-hp57ynss.googlevideo.com.": {},
+ "rr1---sn-hp57ynss.gvt1.com.": {},
+ "rr1---sn-hxugvoxupoj-poqz.googlevideo.com.": {},
+ "rr1---sn-i3b7kn6s.googlevideo.com.": {},
+ "rr1---sn-i3b7knld.googlevideo.com.": {},
+ "rr1---sn-i3b7knlk.googlevideo.com.": {},
+ "rr1---sn-i3b7kns6.googlevideo.com.": {},
+ "rr1---sn-i3b7knsd.googlevideo.com.": {},
+ "rr1---sn-i3b7knse.googlevideo.com.": {},
+ "rr1---sn-i3b7knsl.googlevideo.com.": {},
+ "rr1---sn-i3b7knzl.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-i5goxu-i3bl.googlevideo.com.": {},
+ "rr1---sn-i5h7lner.googlevideo.com.": {},
+ "rr1---sn-i5h7lnl6.googlevideo.com.": {},
+ "rr1---sn-i5h7lnll.googlevideo.com.": {},
+ "rr1---sn-i5h7lnls.googlevideo.com.": {},
+ "rr1---sn-i5heen7d.googlevideo.com.": {},
+ "rr1---sn-i5heen7r.googlevideo.com.": {},
+ "rr1---sn-i5heen7s.googlevideo.com.": {},
+ "rr1---sn-i5heen7z.googlevideo.com.": {},
+ "rr1---sn-jn2pgx4pcxg-w5os.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-2iae.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-2ial.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-nh4e.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-nh4l.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-nh4s.googlevideo.com.": {},
+ "rr1---sn-jvhj5nu-nh4z.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-jxopj-n5oe.googlevideo.com.": {},
+ "rr1---sn-jxopj-n5oe.gvt1.com.": {},
+ "rr1---sn-n0g45pg-ncoe.googlevideo.com.": {},
+ "rr1---sn-n2uxaxjvh-j5xs.googlevideo.com.": {},
+ "rr1---sn-n2uxaxjvh-j5xs.gvt1.com.": {},
+ "rr1---sn-n4v7snee.googlevideo.com.": {},
+ "rr1---sn-n4v7sney.googlevideo.com.": {},
+ "rr1---sn-n4v7snl7.googlevideo.com.": {},
+ "rr1---sn-n4v7snll.googlevideo.com.": {},
+ "rr1---sn-n4v7snlr.googlevideo.com.": {},
+ "rr1---sn-n4v7snls.googlevideo.com.": {},
+ "rr1---sn-n4v7snly.googlevideo.com.": {},
+ "rr1---sn-n4v7sns7.googlevideo.com.": {},
+ "rr1---sn-n4v7snse.googlevideo.com.": {},
+ "rr1---sn-n4v7snse.gvt1.com.": {},
+ "rr1---sn-nh5gujvh-h4xe.googlevideo.com.": {},
+ "rr1---sn-nh5gujvh-h4xe.gvt1.com.": {},
+ "rr1---sn-npoe7ndl.googlevideo.com.": {},
+ "rr1---sn-npoe7nds.googlevideo.com.": {},
+ "rr1---sn-npoe7ne6.googlevideo.com.": {},
+ "rr1---sn-npoe7ne7.googlevideo.com.": {},
+ "rr1---sn-npoe7ned.googlevideo.com.": {},
+ "rr1---sn-npoe7nek.googlevideo.com.": {},
+ "rr1---sn-npoe7ner.googlevideo.com.": {},
+ "rr1---sn-npoe7nes.googlevideo.com.": {},
+ "rr1---sn-npoe7ney.googlevideo.com.": {},
+ "rr1---sn-npoe7nez.googlevideo.com.": {},
+ "rr1---sn-npoe7nl6.googlevideo.com.": {},
+ "rr1---sn-npoe7nlz.googlevideo.com.": {},
+ "rr1---sn-npoe7ns6.googlevideo.com.": {},
+ "rr1---sn-npoe7ns7.googlevideo.com.": {},
+ "rr1---sn-npoe7nsd.googlevideo.com.": {},
+ "rr1---sn-npoe7nsk.googlevideo.com.": {},
+ "rr1---sn-npoe7nsl.googlevideo.com.": {},
+ "rr1---sn-npoe7nsr.googlevideo.com.": {},
+ "rr1---sn-npoe7nss.googlevideo.com.": {},
+ "rr1---sn-npoe7nsy.googlevideo.com.": {},
+ "rr1---sn-npoe7nz7.googlevideo.com.": {},
+ "rr1---sn-npoeene6.googlevideo.com.": {},
+ "rr1---sn-npoeened.googlevideo.com.": {},
+ "rr1---sn-npoeenee.googlevideo.com.": {},
+ "rr1---sn-npoeenek.googlevideo.com.": {},
+ "rr1---sn-npoeener.googlevideo.com.": {},
+ "rr1---sn-npoeenez.googlevideo.com.": {},
+ "rr1---sn-npoeenl7.googlevideo.com.": {},
+ "rr1---sn-npoeenle.googlevideo.com.": {},
+ "rr1---sn-npoeenll.googlevideo.com.": {},
+ "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-npoldn7y.googlevideo.com.": {},
+ "rr1---sn-npoldn7z.googlevideo.com.": {},
+ "rr1---sn-npoldne7.googlevideo.com.": {},
+ "rr1---sn-nuagpm-nuae.googlevideo.com.": {},
+ "rr1---sn-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr1---sn-nv47ln6e.googlevideo.com.": {},
+ "rr1---sn-nv47lns7.googlevideo.com.": {},
+ "rr1---sn-nv47lnsd.googlevideo.com.": {},
+ "rr1---sn-nv47zn7r.googlevideo.com.": {},
+ "rr1---sn-nv47zn7y.googlevideo.com.": {},
+ "rr1---sn-nv47zne7.googlevideo.com.": {},
+ "rr1---sn-nv47znel.googlevideo.com.": {},
+ "rr1---sn-nx57ynsd.googlevideo.com.": {},
+ "rr1---sn-nx57ynsd.gvt1.com.": {},
+ "rr1---sn-nx57ynse.googlevideo.com.": {},
+ "rr1---sn-nx57ynse.gvt1.com.": {},
+ "rr1---sn-nx57ynsk.googlevideo.com.": {},
+ "rr1---sn-nx57ynsk.gvt1.com.": {},
+ "rr1---sn-nx57ynsl.googlevideo.com.": {},
+ "rr1---sn-nx57ynsl.gvt1.com.": {},
+ "rr1---sn-nx57ynss.googlevideo.com.": {},
+ "rr1---sn-nx57ynss.gvt1.com.": {},
+ "rr1---sn-nx57ynsz.googlevideo.com.": {},
+ "rr1---sn-nx57ynsz.gvt1.com.": {},
+ "rr1---sn-nx5s7n76.googlevideo.com.": {},
+ "rr1---sn-nx5s7n76.gvt1.com.": {},
+ "rr1---sn-nx5s7n7d.googlevideo.com.": {},
+ "rr1---sn-nx5s7n7d.gvt1.com.": {},
+ "rr1---sn-nx5s7n7s.googlevideo.com.": {},
+ "rr1---sn-nx5s7n7s.gvt1.com.": {},
+ "rr1---sn-nx5s7n7y.googlevideo.com.": {},
+ "rr1---sn-nx5s7n7y.gvt1.com.": {},
+ "rr1---sn-nx5s7n7z.googlevideo.com.": {},
+ "rr1---sn-nx5s7n7z.gvt1.com.": {},
+ "rr1---sn-nx5s7nee.googlevideo.com.": {},
+ "rr1---sn-nx5s7nee.gvt1.com.": {},
+ "rr1---sn-nx5s7nel.googlevideo.com.": {},
+ "rr1---sn-nx5s7nel.gvt1.com.": {},
+ "rr1---sn-o097znsd.googlevideo.com.": {},
+ "rr1---sn-o097znse.googlevideo.com.": {},
+ "rr1---sn-o097znsk.googlevideo.com.": {},
+ "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.": {},
+ "rr1---sn-o097znzk.googlevideo.com.": {},
+ "rr1---sn-o097znzr.googlevideo.com.": {},
+ "rr1---sn-opnq-n5ue.googlevideo.com.": {},
+ "rr1---sn-ounjvhh-acce.googlevideo.com.": {},
+ "rr1---sn-oxgpj-5ace.googlevideo.com.": {},
+ "rr1---sn-p5qddn76.googlevideo.com.": {},
+ "rr1---sn-p5qddn7d.googlevideo.com.": {},
+ "rr1---sn-p5qddn7k.googlevideo.com.": {},
+ "rr1---sn-p5qddn7z.googlevideo.com.": {},
+ "rr1---sn-p5qlsn6l.googlevideo.com.": {},
+ "rr1---sn-p5qlsn76.googlevideo.com.": {},
+ "rr1---sn-p5qlsn7d.googlevideo.com.": {},
+ "rr1---sn-p5qlsn7l.googlevideo.com.": {},
+ "rr1---sn-p5qlsn7s.googlevideo.com.": {},
+ "rr1---sn-p5qlsnd6.googlevideo.com.": {},
+ "rr1---sn-p5qlsndd.googlevideo.com.": {},
+ "rr1---sn-p5qlsndk.googlevideo.com.": {},
+ "rr1---sn-p5qlsndr.googlevideo.com.": {},
+ "rr1---sn-p5qlsndz.googlevideo.com.": {},
+ "rr1---sn-p5qlsnrr.googlevideo.com.": {},
+ "rr1---sn-p5qlsny6.googlevideo.com.": {},
+ "rr1---sn-p5qs7n6d.googlevideo.com.": {},
+ "rr1---sn-p5qs7nsk.googlevideo.com.": {},
+ "rr1---sn-p5qs7nsr.googlevideo.com.": {},
+ "rr1---sn-p5qs7nzk.googlevideo.com.": {},
+ "rr1---sn-p5qs7nzk.gvt1.com.": {},
+ "rr1---sn-p5qs7nzr.googlevideo.com.": {},
+ "rr1---sn-p5qs7nzy.googlevideo.com.": {},
+ "rr1---sn-paapovpnjxou0gt-nual.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-q4fl6n6r.googlevideo.com.": {},
+ "rr1---sn-q4fl6n6r.gvt1.com.": {},
+ "rr1---sn-q4fl6n6s.googlevideo.com.": {},
+ "rr1---sn-q4fl6n6y.googlevideo.com.": {},
+ "rr1---sn-q4fl6n6z.googlevideo.com.": {},
+ "rr1---sn-q4fl6nd6.googlevideo.com.": {},
+ "rr1---sn-q4fl6nd6.gvt1.com.": {},
+ "rr1---sn-q4fl6nd7.googlevideo.com.": {},
+ "rr1---sn-q4fl6nde.googlevideo.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-q4fl6ndz.gvt1.com.": {},
+ "rr1---sn-q4fl6nlz.googlevideo.com.": {},
+ "rr1---sn-q4fl6nlz.gvt1.com.": {},
+ "rr1---sn-q4fl6ns6.googlevideo.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-q4fl6nsr.googlevideo.com.": {},
+ "rr1---sn-q4fl6nsr.gvt1.com.": {},
+ "rr1---sn-q4fl6nss.googlevideo.com.": {},
+ "rr1---sn-q4fl6nsy.googlevideo.com.": {},
+ "rr1---sn-q4fl6nz6.googlevideo.com.": {},
+ "rr1---sn-q4fl6nz6.gvt1.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-q4flrne7.gvt1.com.": {},
+ "rr1---sn-q4flrnee.googlevideo.com.": {},
+ "rr1---sn-q4flrnek.googlevideo.com.": {},
+ "rr1---sn-q4flrnek.gvt1.com.": {},
+ "rr1---sn-q4flrnel.googlevideo.com.": {},
+ "rr1---sn-q4flrner.googlevideo.com.": {},
+ "rr1---sn-q4flrnes.googlevideo.com.": {},
+ "rr1---sn-q4flrney.googlevideo.com.": {},
+ "rr1---sn-q4flrnez.googlevideo.com.": {},
+ "rr1---sn-q4flrnl6.googlevideo.com.": {},
+ "rr1---sn-q4flrnl7.googlevideo.com.": {},
+ "rr1---sn-q4flrnld.googlevideo.com.": {},
+ "rr1---sn-q4flrnle.googlevideo.com.": {},
+ "rr1---sn-q4flrnle.gvt1.com.": {},
+ "rr1---sn-q4flrnlz.googlevideo.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-q4flrnss.googlevideo.com.": {},
+ "rr1---sn-q4flrnss.gvt1.com.": {},
+ "rr1---sn-q4fzen7e.googlevideo.com.": {},
+ "rr1---sn-q4fzen7e.gvt1.com.": {},
+ "rr1---sn-q4fzen7l.googlevideo.com.": {},
+ "rr1---sn-q4fzen7l.gvt1.com.": {},
+ "rr1---sn-q4fzen7r.googlevideo.com.": {},
+ "rr1---sn-q4fzen7r.gvt1.com.": {},
+ "rr1---sn-q4fzen7s.googlevideo.com.": {},
+ "rr1---sn-q4fzen7y.googlevideo.com.": {},
+ "rr1---sn-q4fzen7y.gvt1.com.": {},
+ "rr1---sn-q4fzene7.googlevideo.com.": {},
+ "rr1---sn-q4fzenee.googlevideo.com.": {},
+ "rr1---sn-qpbp-30ar.googlevideo.com.": {},
+ "rr1---sn-qpbp-30ay.googlevideo.com.": {},
+ "rr1---sn-qxo7rn7k.googlevideo.com.": {},
+ "rr1---sn-qxo7rn7r.googlevideo.com.": {},
+ "rr1---sn-qxoedn7k.googlevideo.com.": {},
+ "rr1---sn-qxoedne7.googlevideo.com.": {},
+ "rr1---sn-qxoednee.googlevideo.com.": {},
+ "rr1---sn-u1hp55-5c.googlevideo.com.": {},
+ "rr1---sn-uhvcpaxoa-5hne.googlevideo.com.": {},
+ "rr1---sn-uhvcpaxoa-guhe.googlevideo.com.": {},
+ "rr1---sn-v53a5oqnji-4oul.googlevideo.com.": {},
+ "rr1---sn-v53a5oqnji-4oul.gvt1.com.": {},
+ "rr1---sn-v5goxu-jhi6.googlevideo.com.": {},
+ "rr1---sn-v5goxu-jhi6.gvt1.com.": {},
+ "rr1---sn-v5goxu-jhil.googlevideo.com.": {},
+ "rr1---sn-v5goxu-jhil.gvt1.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-vgqskn67.gvt1.com.": {},
+ "rr1---sn-vgqskn6d.googlevideo.com.": {},
+ "rr1---sn-vgqskn6s.googlevideo.com.": {},
+ "rr1---sn-vgqskn6z.googlevideo.com.": {},
+ "rr1---sn-vgqskn6z.gvt1.com.": {},
+ "rr1---sn-vgqskne6.googlevideo.com.": {},
+ "rr1---sn-vgqskne6.gvt1.com.": {},
+ "rr1---sn-vgqskned.googlevideo.com.": {},
+ "rr1---sn-vgqsknek.googlevideo.com.": {},
+ "rr1---sn-vgqsknes.googlevideo.com.": {},
+ "rr1---sn-vgqsknez.googlevideo.com.": {},
+ "rr1---sn-vgqsknez.gvt1.com.": {},
+ "rr1---sn-vgqsknld.googlevideo.com.": {},
+ "rr1---sn-vgqsknlk.googlevideo.com.": {},
+ "rr1---sn-vgqsknlk.gvt1.com.": {},
+ "rr1---sn-vgqsknll.googlevideo.com.": {},
+ "rr1---sn-vgqsknls.googlevideo.com.": {},
+ "rr1---sn-vgqsknls.gvt1.com.": {},
+ "rr1---sn-vgqsknlz.googlevideo.com.": {},
+ "rr1---sn-vgqsknse.googlevideo.com.": {},
+ "rr1---sn-vgqsknsk.googlevideo.com.": {},
+ "rr1---sn-vgqsknsk.gvt1.com.": {},
+ "rr1---sn-vgqsknz6.googlevideo.com.": {},
+ "rr1---sn-vgqsknz6.gvt1.com.": {},
+ "rr1---sn-vgqsknz7.googlevideo.com.": {},
+ "rr1---sn-vgqsknz7.gvt1.com.": {},
+ "rr1---sn-vgqsknzd.googlevideo.com.": {},
+ "rr1---sn-vgqsknzd.gvt1.com.": {},
+ "rr1---sn-vgqsknze.googlevideo.com.": {},
+ "rr1---sn-vgqsknzk.googlevideo.com.": {},
+ "rr1---sn-vgqsknzl.googlevideo.com.": {},
+ "rr1---sn-vgqsknzl.gvt1.com.": {},
+ "rr1---sn-vgqsknzr.googlevideo.com.": {},
+ "rr1---sn-vgqsknzr.gvt1.com.": {},
+ "rr1---sn-vgqsknzs.googlevideo.com.": {},
+ "rr1---sn-vgqsknzs.gvt1.com.": {},
+ "rr1---sn-vgqsknzy.googlevideo.com.": {},
+ "rr1---sn-vgqsknzz.googlevideo.com.": {},
+ "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-vgqsrne6.gvt1.com.": {},
+ "rr1---sn-vgqsrned.googlevideo.com.": {},
+ "rr1---sn-vgqsrnek.googlevideo.com.": {},
+ "rr1---sn-vgqsrnes.googlevideo.com.": {},
+ "rr1---sn-vgqsrnes.gvt1.com.": {},
+ "rr1---sn-vgqsrnez.googlevideo.com.": {},
+ "rr1---sn-vgqsrnl6.googlevideo.com.": {},
+ "rr1---sn-vgqsrnld.googlevideo.com.": {},
+ "rr1---sn-vgqsrnll.googlevideo.com.": {},
+ "rr1---sn-vgqsrnls.googlevideo.com.": {},
+ "rr1---sn-vgqsrnls.gvt1.com.": {},
+ "rr1---sn-vgqsrnlz.googlevideo.com.": {},
+ "rr1---sn-vgqsrns6.googlevideo.com.": {},
+ "rr1---sn-vgqsrnsd.googlevideo.com.": {},
+ "rr1---sn-vgqsrnsr.googlevideo.com.": {},
+ "rr1---sn-vgqsrnsr.gvt1.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-vgqsrnzd.gvt1.com.": {},
+ "rr1---sn-vgqsrnzk.googlevideo.com.": {},
+ "rr1---sn-vgqsrnzk.gvt1.com.": {},
+ "rr1---sn-vgqsrnzr.googlevideo.com.": {},
+ "rr1---sn-vgqsrnzs.googlevideo.com.": {},
+ "rr1---sn-vgqsrnzy.googlevideo.com.": {},
+ "rr1---sn-vgqsrnzz.googlevideo.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-5hne6nsz.googlevideo.com.": {},
+ "rr1.sn-5hneknek.googlevideo.com.": {},
+ "rr1.sn-q4fl6ndl.googlevideo.com.": {},
+ "rr1.sn-q4fl6ns7.googlevideo.com.": {},
+ "rr1.sn-q4flrnsk.googlevideo.com.": {},
+ "rr10---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr10---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr11---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr12---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr12---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr2---sn-02o-ao3e.googlevideo.com.": {},
+ "rr2---sn-0nnpbo5a-bggl.googlevideo.com.": {},
+ "rr2---sn-0op8v4h5pox-cbgl.googlevideo.com.": {},
+ "rr2---sn-2aqu-hoaly.googlevideo.com.": {},
+ "rr2---sn-2aqu-hoas7.googlevideo.com.": {},
+ "rr2---sn-2imern76.googlevideo.com.": {},
+ "rr2---sn-2imern76.gvt1.com.": {},
+ "rr2---sn-2imern7d.googlevideo.com.": {},
+ "rr2---sn-2imern7d.gvt1.com.": {},
+ "rr2---sn-2imeyn7k.googlevideo.com.": {},
+ "rr2---sn-2imeyn7k.gvt1.com.": {},
+ "rr2---sn-2napbiu-p5ie.googlevideo.com.": {},
+ "rr2---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr2---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr2---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr2---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr2---sn-2vgu0b5auxaxjvh-v2vz.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.": {},
+ "rr2---sn-30a7yney.googlevideo.com.": {},
+ "rr2---sn-30a7ynl7.googlevideo.com.": {},
+ "rr2---sn-3n4pcxg-pjul.googlevideo.com.": {},
+ "rr2---sn-42u-nboze.googlevideo.com.": {},
+ "rr2---sn-42u-nbozl.googlevideo.com.": {},
+ "rr2---sn-42u-nbozs.googlevideo.com.": {},
+ "rr2---sn-4g5e6ns6.googlevideo.com.": {},
+ "rr2---sn-4g5e6ns7.googlevideo.com.": {},
+ "rr2---sn-4g5e6nsd.googlevideo.com.": {},
+ "rr2---sn-4g5e6nsk.googlevideo.com.": {},
+ "rr2---sn-4g5e6nsr.googlevideo.com.": {},
+ "rr2---sn-4g5e6nss.googlevideo.com.": {},
+ "rr2---sn-4g5e6nsy.googlevideo.com.": {},
+ "rr2---sn-4g5e6nsz.googlevideo.com.": {},
+ "rr2---sn-4g5e6nz7.googlevideo.com.": {},
+ "rr2---sn-4g5e6nze.googlevideo.com.": {},
+ "rr2---sn-4g5e6nzl.googlevideo.com.": {},
+ "rr2---sn-4g5e6nzs.googlevideo.com.": {},
+ "rr2---sn-4g5e6nzz.googlevideo.com.": {},
+ "rr2---sn-4g5edn6k.googlevideo.com.": {},
+ "rr2---sn-4g5edn6r.googlevideo.com.": {},
+ "rr2---sn-4g5edn6y.googlevideo.com.": {},
+ "rr2---sn-4g5ednd7.googlevideo.com.": {},
+ "rr2---sn-4g5edndd.googlevideo.com.": {},
+ "rr2---sn-4g5ednde.googlevideo.com.": {},
+ "rr2---sn-4g5edndk.googlevideo.com.": {},
+ "rr2---sn-4g5edndl.googlevideo.com.": {},
+ "rr2---sn-4g5edndr.googlevideo.com.": {},
+ "rr2---sn-4g5ednds.googlevideo.com.": {},
+ "rr2---sn-4g5edndy.googlevideo.com.": {},
+ "rr2---sn-4g5edndz.googlevideo.com.": {},
+ "rr2---sn-4g5ednkl.googlevideo.com.": {},
+ "rr2---sn-4g5ednld.googlevideo.com.": {},
+ "rr2---sn-4g5ednly.googlevideo.com.": {},
+ "rr2---sn-4g5edns6.googlevideo.com.": {},
+ "rr2---sn-4g5edns7.googlevideo.com.": {},
+ "rr2---sn-4g5ednsd.googlevideo.com.": {},
+ "rr2---sn-4g5ednse.googlevideo.com.": {},
+ "rr2---sn-4g5ednsk.googlevideo.com.": {},
+ "rr2---sn-4g5ednsl.googlevideo.com.": {},
+ "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.": {},
+ "rr2---sn-4g5lzner.googlevideo.com.": {},
+ "rr2---sn-4g5lznes.googlevideo.com.": {},
+ "rr2---sn-4g5lzney.googlevideo.com.": {},
+ "rr2---sn-4g5lznez.googlevideo.com.": {},
+ "rr2---sn-4g5lznl6.googlevideo.com.": {},
+ "rr2---sn-4g5lznl7.googlevideo.com.": {},
+ "rr2---sn-4g5lznle.googlevideo.com.": {},
+ "rr2---sn-4g5lznls.googlevideo.com.": {},
+ "rr2---sn-4g5lznlz.googlevideo.com.": {},
+ "rr2---sn-4jjo-apnl.googlevideo.com.": {},
+ "rr2---sn-4pgnuapbiu-5acs.googlevideo.com.": {},
+ "rr2---sn-4pgnuapbiu-hiue.googlevideo.com.": {},
+ "rr2---sn-5hne6n6e.googlevideo.com.": {},
+ "rr2---sn-5hne6n6l.googlevideo.com.": {},
+ "rr2---sn-5hne6ns6.googlevideo.com.": {},
+ "rr2---sn-5hne6nsd.googlevideo.com.": {},
+ "rr2---sn-5hne6nsk.googlevideo.com.": {},
+ "rr2---sn-5hne6nsr.googlevideo.com.": {},
+ "rr2---sn-5hne6nsy.googlevideo.com.": {},
+ "rr2---sn-5hne6nsz.googlevideo.com.": {},
+ "rr2---sn-5hne6nz6.googlevideo.com.": {},
+ "rr2---sn-5hne6nzd.googlevideo.com.": {},
+ "rr2---sn-5hne6nzk.googlevideo.com.": {},
+ "rr2---sn-5hne6nzs.googlevideo.com.": {},
+ "rr2---sn-5hne6nzy.googlevideo.com.": {},
+ "rr2---sn-5hnednss.googlevideo.com.": {},
+ "rr2---sn-5hnednsz.googlevideo.com.": {},
+ "rr2---sn-5hnekn76.googlevideo.com.": {},
+ "rr2---sn-5hnekn7d.googlevideo.com.": {},
+ "rr2---sn-5hnekn7k.googlevideo.com.": {},
+ "rr2---sn-5hnekn7l.googlevideo.com.": {},
+ "rr2---sn-5hnekn7s.googlevideo.com.": {},
+ "rr2---sn-5hnekn7z.googlevideo.com.": {},
+ "rr2---sn-5hneknee.googlevideo.com.": {},
+ "rr2---sn-5hneknee.gvt1.com.": {},
+ "rr2---sn-5hneknek.googlevideo.com.": {},
+ "rr2---sn-5hneknes.googlevideo.com.": {},
+ "rr2---sn-5pgnugx5h-hn2s.googlevideo.com.": {},
+ "rr2---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr2---sn-5uaezndd.googlevideo.com.": {},
+ "rr2---sn-5uaezne6.googlevideo.com.": {},
+ "rr2---sn-5uaezned.googlevideo.com.": {},
+ "rr2---sn-5uaeznez.googlevideo.com.": {},
+ "rr2---sn-5uaeznl6.googlevideo.com.": {},
+ "rr2---sn-5uaeznld.googlevideo.com.": {},
+ "rr2---sn-5uaeznls.googlevideo.com.": {},
+ "rr2---sn-5uaeznly.googlevideo.com.": {},
+ "rr2---sn-5uaeznlz.googlevideo.com.": {},
+ "rr2---sn-5uaeznrz.googlevideo.com.": {},
+ "rr2---sn-5uaezns7.googlevideo.com.": {},
+ "rr2---sn-5uaeznse.googlevideo.com.": {},
+ "rr2---sn-5uaeznsl.googlevideo.com.": {},
+ "rr2---sn-5uaeznss.googlevideo.com.": {},
+ "rr2---sn-5uaezny6.googlevideo.com.": {},
+ "rr2---sn-5uaeznys.googlevideo.com.": {},
+ "rr2---sn-5uaeznyz.googlevideo.com.": {},
+ "rr2---sn-5uaeznze.googlevideo.com.": {},
+ "rr2---sn-5ualdnle.googlevideo.com.": {},
+ "rr2---sn-5ualdnll.googlevideo.com.": {},
+ "rr2---sn-5ualdnlr.googlevideo.com.": {},
+ "rr2---sn-5ualdnls.googlevideo.com.": {},
+ "rr2---sn-5ualdns6.googlevideo.com.": {},
+ "rr2---sn-5ualdns7.googlevideo.com.": {},
+ "rr2---sn-5ualdnsd.googlevideo.com.": {},
+ "rr2---sn-5ualdnse.googlevideo.com.": {},
+ "rr2---sn-5ualdnsk.googlevideo.com.": {},
+ "rr2---sn-5ualdnsl.googlevideo.com.": {},
+ "rr2---sn-5ualdnsr.googlevideo.com.": {},
+ "rr2---sn-5ualdnss.googlevideo.com.": {},
+ "rr2---sn-5ualdnsy.googlevideo.com.": {},
+ "rr2---sn-5ualdnsz.googlevideo.com.": {},
+ "rr2---sn-5ualdnz7.googlevideo.com.": {},
+ "rr2---sn-5ualdnze.googlevideo.com.": {},
+ "rr2---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr2---sn-8qj-i5okl.googlevideo.com.": {},
+ "rr2---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr2---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr2---sn-8qj-nbo6y.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-2iay.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-a5me.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab56.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab5d.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab5e.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab5l.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab5s.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-nh4e.googlevideo.com.": {},
+ "rr2---sn-8xgp1vo-p5ie.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-9gv76n7z.googlevideo.com.": {},
+ "rr2---sn-9gv7ene6.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-a5m7lnld.googlevideo.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-a5mekn6l.gvt1.com.": {},
+ "rr2---sn-a5mekn6r.googlevideo.com.": {},
+ "rr2---sn-a5mekn6s.googlevideo.com.": {},
+ "rr2---sn-a5mekn6z.googlevideo.com.": {},
+ "rr2---sn-a5meknd6.googlevideo.com.": {},
+ "rr2---sn-a5meknd6.gvt1.com.": {},
+ "rr2---sn-a5meknde.googlevideo.com.": {},
+ "rr2---sn-a5meknde.gvt1.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-a5meknzr.googlevideo.com.": {},
+ "rr2---sn-a5meknzs.googlevideo.com.": {},
+ "rr2---sn-a5meknzs.gvt1.com.": {},
+ "rr2---sn-a5mlrnl6.googlevideo.com.": {},
+ "rr2---sn-a5mlrnl6.gvt1.com.": {},
+ "rr2---sn-a5mlrnll.googlevideo.com.": {},
+ "rr2---sn-a5mlrnls.googlevideo.com.": {},
+ "rr2---sn-a5mlrnlz.googlevideo.com.": {},
+ "rr2---sn-a5mlrnlz.gvt1.com.": {},
+ "rr2---sn-a5msen76.googlevideo.com.": {},
+ "rr2---sn-a5msen76.gvt1.com.": {},
+ "rr2---sn-a5msen7l.googlevideo.com.": {},
+ "rr2---sn-a5msen7s.googlevideo.com.": {},
+ "rr2---sn-a5msen7z.googlevideo.com.": {},
+ "rr2---sn-a5msenek.googlevideo.com.": {},
+ "rr2---sn-a5msenek.gvt1.com.": {},
+ "rr2---sn-a5msener.googlevideo.com.": {},
+ "rr2---sn-a5msenes.googlevideo.com.": {},
+ "rr2---sn-a5msenl7.googlevideo.com.": {},
+ "rr2---sn-a5msenle.googlevideo.com.": {},
+ "rr2---sn-a5msenll.googlevideo.com.": {},
+ "rr2---sn-a5oj5nuxg-hque.gvt1.com.": {},
+ "rr2---sn-ab5l6ndr.googlevideo.com.": {},
+ "rr2---sn-ab5l6ndr.gvt1.com.": {},
+ "rr2---sn-ab5l6ndy.googlevideo.com.": {},
+ "rr2---sn-ab5l6nk6.googlevideo.com.": {},
+ "rr2---sn-ab5l6nkd.googlevideo.com.": {},
+ "rr2---sn-ab5l6nr6.googlevideo.com.": {},
+ "rr2---sn-ab5l6nr6.gvt1.com.": {},
+ "rr2---sn-ab5l6nrd.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrd.gvt1.com.": {},
+ "rr2---sn-ab5l6nrk.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrl.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrr.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrr.gvt1.com.": {},
+ "rr2---sn-ab5l6nrs.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrz.googlevideo.com.": {},
+ "rr2---sn-ab5l6nrz.gvt1.com.": {},
+ "rr2---sn-ab5sznld.googlevideo.com.": {},
+ "rr2---sn-ab5sznlk.googlevideo.com.": {},
+ "rr2---sn-ab5sznly.googlevideo.com.": {},
+ "rr2---sn-ab5sznz6.googlevideo.com.": {},
+ "rr2---sn-ab5sznzd.googlevideo.com.": {},
+ "rr2---sn-ab5sznzd.gvt1.com.": {},
+ "rr2---sn-ab5sznze.googlevideo.com.": {},
+ "rr2---sn-ab5sznzk.googlevideo.com.": {},
+ "rr2---sn-ab5sznzl.googlevideo.com.": {},
+ "rr2---sn-ab5sznzr.googlevideo.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-aigl6nsd.googlevideo.com.": {},
+ "rr2---sn-aigl6nsk.googlevideo.com.": {},
+ "rr2---sn-aigl6nsk.gvt1.com.": {},
+ "rr2---sn-aigl6nsr.googlevideo.com.": {},
+ "rr2---sn-aigl6nz7.googlevideo.com.": {},
+ "rr2---sn-aigl6nze.googlevideo.com.": {},
+ "rr2---sn-aigl6nzk.googlevideo.com.": {},
+ "rr2---sn-aigl6nzl.googlevideo.com.": {},
+ "rr2---sn-aigl6nzr.googlevideo.com.": {},
+ "rr2---sn-aigl6nzs.googlevideo.com.": {},
+ "rr2---sn-aigzrn76.googlevideo.com.": {},
+ "rr2---sn-aigzrn7d.googlevideo.com.": {},
+ "rr2---sn-aigzrn7e.googlevideo.com.": {},
+ "rr2---sn-aigzrn7k.googlevideo.com.": {},
+ "rr2---sn-aigzrn7l.googlevideo.com.": {},
+ "rr2---sn-aigzrn7s.googlevideo.com.": {},
+ "rr2---sn-aigzrn7z.googlevideo.com.": {},
+ "rr2---sn-aigzrne7.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.": {},
+ "rr2---sn-aigzrnz7.googlevideo.com.": {},
+ "rr2---sn-aigzrnze.googlevideo.com.": {},
+ "rr2---sn-apn7en7e.googlevideo.com.": {},
+ "rr2---sn-apn7en7l.googlevideo.com.": {},
+ "rr2---sn-apn7en7s.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-c0q7lnz7.googlevideo.com.": {},
+ "rr2---sn-cvb7lne7.googlevideo.com.": {},
+ "rr2---sn-cvb7lnee.googlevideo.com.": {},
+ "rr2---sn-cvb7lnl7.googlevideo.com.": {},
+ "rr2---sn-cvb7lnls.googlevideo.com.": {},
+ "rr2---sn-cvb7lnlz.googlevideo.com.": {},
+ "rr2---sn-cvb7sn7k.googlevideo.com.": {},
+ "rr2---sn-cvb7sn7r.googlevideo.com.": {},
+ "rr2---sn-fpnjoxu-hnol.googlevideo.com.": {},
+ "rr2---sn-gpuuxg-hxhl.googlevideo.com.": {},
+ "rr2---sn-gpuuxg-hxhl.gvt1.com.": {},
+ "rr2---sn-gpuuxg-hxhs.googlevideo.com.": {},
+ "rr2---sn-gpuuxg-hxhs.gvt1.com.": {},
+ "rr2---sn-h0jeenek.googlevideo.com.": {},
+ "rr2---sn-h0jeenl6.googlevideo.com.": {},
+ "rr2---sn-h0jeenld.googlevideo.com.": {},
+ "rr2---sn-h0jeln7e.googlevideo.com.": {},
+ "rr2---sn-h0jeln7l.googlevideo.com.": {},
+ "rr2---sn-h0jelne6.googlevideo.com.": {},
+ "rr2---sn-h0jelne7.googlevideo.com.": {},
+ "rr2---sn-h0jelnes.googlevideo.com.": {},
+ "rr2---sn-h0jelnez.googlevideo.com.": {},
+ "rr2---sn-hjoj-gq0l.googlevideo.com.": {},
+ "rr2---sn-hjoj-gq0l.gvt1.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-hp57kn6y.googlevideo.com.": {},
+ "rr2---sn-hp57knd6.googlevideo.com.": {},
+ "rr2---sn-hp57kndd.googlevideo.com.": {},
+ "rr2---sn-hp57kndk.googlevideo.com.": {},
+ "rr2---sn-hp57kndr.googlevideo.com.": {},
+ "rr2---sn-hp57knds.googlevideo.com.": {},
+ "rr2---sn-hp57kndy.googlevideo.com.": {},
+ "rr2---sn-hp57kndy.gvt1.com.": {},
+ "rr2---sn-hp57kndz.googlevideo.com.": {},
+ "rr2---sn-hp57yn7r.googlevideo.com.": {},
+ "rr2---sn-hp57yn7y.googlevideo.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-hp57ynss.googlevideo.com.": {},
+ "rr2---sn-hp57ynss.gvt1.com.": {},
+ "rr2---sn-hxugvoxupoj-poqz.googlevideo.com.": {},
+ "rr2---sn-i3b7kn6s.googlevideo.com.": {},
+ "rr2---sn-i3b7knld.googlevideo.com.": {},
+ "rr2---sn-i3b7knlk.googlevideo.com.": {},
+ "rr2---sn-i3b7knsd.googlevideo.com.": {},
+ "rr2---sn-i3b7knse.googlevideo.com.": {},
+ "rr2---sn-i3b7knsl.googlevideo.com.": {},
+ "rr2---sn-i3b7knzl.googlevideo.com.": {},
+ "rr2---sn-i3b7knzs.googlevideo.com.": {},
+ "rr2---sn-i3belne6.googlevideo.com.": {},
+ "rr2---sn-i3belney.googlevideo.com.": {},
+ "rr2---sn-i3belnl6.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-ioal.googlevideo.com.": {},
+ "rr2---sn-i5h7lner.googlevideo.com.": {},
+ "rr2---sn-i5h7lnl6.googlevideo.com.": {},
+ "rr2---sn-i5h7lnll.googlevideo.com.": {},
+ "rr2---sn-i5h7lnls.googlevideo.com.": {},
+ "rr2---sn-i5heen7d.googlevideo.com.": {},
+ "rr2---sn-i5heen7r.googlevideo.com.": {},
+ "rr2---sn-i5heen7s.googlevideo.com.": {},
+ "rr2---sn-jn2pgx4pcxg-w5os.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-2iae.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-2ial.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-nh4e.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-nh4l.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-nh4s.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-nh4z.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-qufe.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-qufl.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-qufs.googlevideo.com.": {},
+ "rr2---sn-jvhj5nu-qufz.googlevideo.com.": {},
+ "rr2---sn-jxopj-n5oe.googlevideo.com.": {},
+ "rr2---sn-jxopj-n5oe.gvt1.com.": {},
+ "rr2---sn-jxopj-nh4e.googlevideo.com.": {},
+ "rr2---sn-jxopj-nh4e.gvt1.com.": {},
+ "rr2---sn-n0g45pg-ncoe.googlevideo.com.": {},
+ "rr2---sn-n2uxaxjvh-j5xl.googlevideo.com.": {},
+ "rr2---sn-n2uxaxjvh-j5xl.gvt1.com.": {},
+ "rr2---sn-n2uxaxjvh-j5xs.googlevideo.com.": {},
+ "rr2---sn-n2uxaxjvh-j5xs.gvt1.com.": {},
+ "rr2---sn-n4v7snee.googlevideo.com.": {},
+ "rr2---sn-n4v7sney.googlevideo.com.": {},
+ "rr2---sn-n4v7snl7.googlevideo.com.": {},
+ "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-n4v7sns7.gvt1.com.": {},
+ "rr2---sn-n4v7snse.googlevideo.com.": {},
+ "rr2---sn-nh5gujvh-h4xe.googlevideo.com.": {},
+ "rr2---sn-nh5gujvh-h4xe.gvt1.com.": {},
+ "rr2---sn-npoe7ndl.googlevideo.com.": {},
+ "rr2---sn-npoe7nds.googlevideo.com.": {},
+ "rr2---sn-npoe7ne6.googlevideo.com.": {},
+ "rr2---sn-npoe7ne7.googlevideo.com.": {},
+ "rr2---sn-npoe7ned.googlevideo.com.": {},
+ "rr2---sn-npoe7nek.googlevideo.com.": {},
+ "rr2---sn-npoe7ner.googlevideo.com.": {},
+ "rr2---sn-npoe7nes.googlevideo.com.": {},
+ "rr2---sn-npoe7ney.googlevideo.com.": {},
+ "rr2---sn-npoe7nez.googlevideo.com.": {},
+ "rr2---sn-npoe7nl6.googlevideo.com.": {},
+ "rr2---sn-npoe7nlz.googlevideo.com.": {},
+ "rr2---sn-npoe7ns6.googlevideo.com.": {},
+ "rr2---sn-npoe7ns7.googlevideo.com.": {},
+ "rr2---sn-npoe7nsd.googlevideo.com.": {},
+ "rr2---sn-npoe7nsk.googlevideo.com.": {},
+ "rr2---sn-npoe7nsk.gvt1.com.": {},
+ "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.": {},
+ "rr2---sn-npoeenee.googlevideo.com.": {},
+ "rr2---sn-npoeenek.googlevideo.com.": {},
+ "rr2---sn-npoeener.googlevideo.com.": {},
+ "rr2---sn-npoeeney.googlevideo.com.": {},
+ "rr2---sn-npoeenez.googlevideo.com.": {},
+ "rr2---sn-npoeenl7.googlevideo.com.": {},
+ "rr2---sn-npoeenle.googlevideo.com.": {},
+ "rr2---sn-npoeenlk.googlevideo.com.": {},
+ "rr2---sn-npoeenll.googlevideo.com.": {},
+ "rr2---sn-npoeenly.googlevideo.com.": {},
+ "rr2---sn-npoeens7.googlevideo.com.": {},
+ "rr2---sn-npoldn76.googlevideo.com.": {},
+ "rr2---sn-npoldn7d.googlevideo.com.": {},
+ "rr2---sn-npoldn7e.googlevideo.com.": {},
+ "rr2---sn-npoldn7l.googlevideo.com.": {},
+ "rr2---sn-npoldn7s.googlevideo.com.": {},
+ "rr2---sn-npoldn7y.googlevideo.com.": {},
+ "rr2---sn-npoldn7z.googlevideo.com.": {},
+ "rr2---sn-npoldne7.googlevideo.com.": {},
+ "rr2---sn-nuagpm-nuae.googlevideo.com.": {},
+ "rr2---sn-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr2---sn-nv47ln6e.googlevideo.com.": {},
+ "rr2---sn-nv47lns6.googlevideo.com.": {},
+ "rr2---sn-nv47lns7.googlevideo.com.": {},
+ "rr2---sn-nv47lnsr.googlevideo.com.": {},
+ "rr2---sn-nv47zn7r.googlevideo.com.": {},
+ "rr2---sn-nv47zn7y.googlevideo.com.": {},
+ "rr2---sn-nv47zne7.googlevideo.com.": {},
+ "rr2---sn-nv47znee.googlevideo.com.": {},
+ "rr2---sn-nv47znel.googlevideo.com.": {},
+ "rr2---sn-nx57ynsd.googlevideo.com.": {},
+ "rr2---sn-nx57ynsd.gvt1.com.": {},
+ "rr2---sn-nx57ynsk.googlevideo.com.": {},
+ "rr2---sn-nx57ynsk.gvt1.com.": {},
+ "rr2---sn-nx57ynsl.googlevideo.com.": {},
+ "rr2---sn-nx57ynsl.gvt1.com.": {},
+ "rr2---sn-nx57ynss.googlevideo.com.": {},
+ "rr2---sn-nx57ynss.gvt1.com.": {},
+ "rr2---sn-nx57ynsz.googlevideo.com.": {},
+ "rr2---sn-nx5s7n76.googlevideo.com.": {},
+ "rr2---sn-nx5s7n7y.googlevideo.com.": {},
+ "rr2---sn-nx5s7n7y.gvt1.com.": {},
+ "rr2---sn-nx5s7n7z.googlevideo.com.": {},
+ "rr2---sn-nx5s7n7z.gvt1.com.": {},
+ "rr2---sn-nx5s7nee.googlevideo.com.": {},
+ "rr2---sn-nx5s7nel.googlevideo.com.": {},
+ "rr2---sn-nx5s7nel.gvt1.com.": {},
+ "rr2---sn-o097znsd.googlevideo.com.": {},
+ "rr2---sn-o097znsd.gvt1.com.": {},
+ "rr2---sn-o097znse.googlevideo.com.": {},
+ "rr2---sn-o097znsk.googlevideo.com.": {},
+ "rr2---sn-o097znsl.googlevideo.com.": {},
+ "rr2---sn-o097znsr.googlevideo.com.": {},
+ "rr2---sn-o097znsr.gvt1.com.": {},
+ "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-o097znze.gvt1.com.": {},
+ "rr2---sn-o097znzk.googlevideo.com.": {},
+ "rr2---sn-o097znzr.googlevideo.com.": {},
+ "rr2---sn-opnq-n5ue.googlevideo.com.": {},
+ "rr2---sn-ounjvhh-acce.googlevideo.com.": {},
+ "rr2---sn-oxgpj-5ace.googlevideo.com.": {},
+ "rr2---sn-p5qddn76.googlevideo.com.": {},
+ "rr2---sn-p5qddn7d.googlevideo.com.": {},
+ "rr2---sn-p5qddn7k.googlevideo.com.": {},
+ "rr2---sn-p5qddn7z.googlevideo.com.": {},
+ "rr2---sn-p5qlsn6l.googlevideo.com.": {},
+ "rr2---sn-p5qlsn76.googlevideo.com.": {},
+ "rr2---sn-p5qlsn7d.googlevideo.com.": {},
+ "rr2---sn-p5qlsn7l.googlevideo.com.": {},
+ "rr2---sn-p5qlsn7s.googlevideo.com.": {},
+ "rr2---sn-p5qlsnd6.googlevideo.com.": {},
+ "rr2---sn-p5qlsndd.googlevideo.com.": {},
+ "rr2---sn-p5qlsndr.googlevideo.com.": {},
+ "rr2---sn-p5qlsndz.googlevideo.com.": {},
+ "rr2---sn-p5qlsnrl.googlevideo.com.": {},
+ "rr2---sn-p5qlsnrr.googlevideo.com.": {},
+ "rr2---sn-p5qlsny6.googlevideo.com.": {},
+ "rr2---sn-p5qs7n6d.googlevideo.com.": {},
+ "rr2---sn-p5qs7nsk.googlevideo.com.": {},
+ "rr2---sn-p5qs7nzk.googlevideo.com.": {},
+ "rr2---sn-p5qs7nzr.googlevideo.com.": {},
+ "rr2---sn-p5qs7nzy.googlevideo.com.": {},
+ "rr2---sn-paapovpnjxou0gt-nual.googlevideo.com.": {},
+ "rr2---sn-pjnpu-5hfe.googlevideo.com.": {},
+ "rr2---sn-pobpb-poql.googlevideo.com.": {},
+ "rr2---sn-q4fl6n66.googlevideo.com.": {},
+ "rr2---sn-q4fl6n6d.googlevideo.com.": {},
+ "rr2---sn-q4fl6n6d.gvt1.com.": {},
+ "rr2---sn-q4fl6n6r.googlevideo.com.": {},
+ "rr2---sn-q4fl6n6s.googlevideo.com.": {},
+ "rr2---sn-q4fl6n6y.googlevideo.com.": {},
+ "rr2---sn-q4fl6n6z.googlevideo.com.": {},
+ "rr2---sn-q4fl6nd6.googlevideo.com.": {},
+ "rr2---sn-q4fl6nd7.googlevideo.com.": {},
+ "rr2---sn-q4fl6nd7.gvt1.com.": {},
+ "rr2---sn-q4fl6nde.googlevideo.com.": {},
+ "rr2---sn-q4fl6ndl.googlevideo.com.": {},
+ "rr2---sn-q4fl6ndl.gvt1.com.": {},
+ "rr2---sn-q4fl6nds.googlevideo.com.": {},
+ "rr2---sn-q4fl6nds.gvt1.com.": {},
+ "rr2---sn-q4fl6ndz.googlevideo.com.": {},
+ "rr2---sn-q4fl6ndz.gvt1.com.": {},
+ "rr2---sn-q4fl6nlz.googlevideo.com.": {},
+ "rr2---sn-q4fl6ns6.googlevideo.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-q4fl6nsl.gvt1.com.": {},
+ "rr2---sn-q4fl6nsr.googlevideo.com.": {},
+ "rr2---sn-q4fl6nss.googlevideo.com.": {},
+ "rr2---sn-q4fl6nsy.googlevideo.com.": {},
+ "rr2---sn-q4fl6nsy.gvt1.com.": {},
+ "rr2---sn-q4fl6nz6.googlevideo.com.": {},
+ "rr2---sn-q4fl6nz7.googlevideo.com.": {},
+ "rr2---sn-q4fl6nzy.googlevideo.com.": {},
+ "rr2---sn-q4flrn7k.googlevideo.com.": {},
+ "rr2---sn-q4flrn7r.googlevideo.com.": {},
+ "rr2---sn-q4flrn7y.googlevideo.com.": {},
+ "rr2---sn-q4flrn7y.gvt1.com.": {},
+ "rr2---sn-q4flrne6.googlevideo.com.": {},
+ "rr2---sn-q4flrne7.googlevideo.com.": {},
+ "rr2---sn-q4flrne7.gvt1.com.": {},
+ "rr2---sn-q4flrnee.googlevideo.com.": {},
+ "rr2---sn-q4flrnek.googlevideo.com.": {},
+ "rr2---sn-q4flrnel.googlevideo.com.": {},
+ "rr2---sn-q4flrner.googlevideo.com.": {},
+ "rr2---sn-q4flrner.gvt1.com.": {},
+ "rr2---sn-q4flrnes.googlevideo.com.": {},
+ "rr2---sn-q4flrney.googlevideo.com.": {},
+ "rr2---sn-q4flrnez.googlevideo.com.": {},
+ "rr2---sn-q4flrnl6.googlevideo.com.": {},
+ "rr2---sn-q4flrnl7.googlevideo.com.": {},
+ "rr2---sn-q4flrnld.googlevideo.com.": {},
+ "rr2---sn-q4flrnld.gvt1.com.": {},
+ "rr2---sn-q4flrnle.googlevideo.com.": {},
+ "rr2---sn-q4flrnle.gvt1.com.": {},
+ "rr2---sn-q4flrnlz.googlevideo.com.": {},
+ "rr2---sn-q4flrnsd.googlevideo.com.": {},
+ "rr2---sn-q4flrnsk.googlevideo.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-q4fzen7l.googlevideo.com.": {},
+ "rr2---sn-q4fzen7r.googlevideo.com.": {},
+ "rr2---sn-q4fzen7r.gvt1.com.": {},
+ "rr2---sn-q4fzen7s.googlevideo.com.": {},
+ "rr2---sn-q4fzen7s.gvt1.com.": {},
+ "rr2---sn-q4fzen7y.googlevideo.com.": {},
+ "rr2---sn-q4fzene7.googlevideo.com.": {},
+ "rr2---sn-q4fzene7.gvt1.com.": {},
+ "rr2---sn-q4fzenee.googlevideo.com.": {},
+ "rr2---sn-q4fzenee.gvt1.com.": {},
+ "rr2---sn-qpbp-30ay.googlevideo.com.": {},
+ "rr2---sn-qxo7rn7k.googlevideo.com.": {},
+ "rr2---sn-qxo7rn7r.googlevideo.com.": {},
+ "rr2---sn-qxo7rn7y.googlevideo.com.": {},
+ "rr2---sn-qxoedne7.googlevideo.com.": {},
+ "rr2---sn-qxoednee.googlevideo.com.": {},
+ "rr2---sn-u1hp55-5c.googlevideo.com.": {},
+ "rr2---sn-uhvcpaxoa-5hne.googlevideo.com.": {},
+ "rr2---sn-uhvcpaxoa-guhe.googlevideo.com.": {},
+ "rr2---sn-v53a5oqnji-4oul.googlevideo.com.": {},
+ "rr2---sn-v53a5oqnji-4oul.gvt1.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-vgqskn67.googlevideo.com.": {},
+ "rr2---sn-vgqskn67.gvt1.com.": {},
+ "rr2---sn-vgqskn6d.googlevideo.com.": {},
+ "rr2---sn-vgqskn6s.googlevideo.com.": {},
+ "rr2---sn-vgqskn6s.gvt1.com.": {},
+ "rr2---sn-vgqskn6z.googlevideo.com.": {},
+ "rr2---sn-vgqskne6.googlevideo.com.": {},
+ "rr2---sn-vgqskne6.gvt1.com.": {},
+ "rr2---sn-vgqskned.googlevideo.com.": {},
+ "rr2---sn-vgqsknek.googlevideo.com.": {},
+ "rr2---sn-vgqsknes.googlevideo.com.": {},
+ "rr2---sn-vgqsknez.googlevideo.com.": {},
+ "rr2---sn-vgqsknld.googlevideo.com.": {},
+ "rr2---sn-vgqsknld.gvt1.com.": {},
+ "rr2---sn-vgqsknlk.googlevideo.com.": {},
+ "rr2---sn-vgqsknll.googlevideo.com.": {},
+ "rr2---sn-vgqsknlr.googlevideo.com.": {},
+ "rr2---sn-vgqsknlr.gvt1.com.": {},
+ "rr2---sn-vgqsknls.googlevideo.com.": {},
+ "rr2---sn-vgqsknls.gvt1.com.": {},
+ "rr2---sn-vgqsknlz.googlevideo.com.": {},
+ "rr2---sn-vgqsknlz.gvt1.com.": {},
+ "rr2---sn-vgqsknsk.googlevideo.com.": {},
+ "rr2---sn-vgqsknsk.gvt1.com.": {},
+ "rr2---sn-vgqsknz6.googlevideo.com.": {},
+ "rr2---sn-vgqsknz6.gvt1.com.": {},
+ "rr2---sn-vgqsknz7.googlevideo.com.": {},
+ "rr2---sn-vgqsknzd.googlevideo.com.": {},
+ "rr2---sn-vgqsknzd.gvt1.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-vgqsknzr.gvt1.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-vgqsrn6l.googlevideo.com.": {},
+ "rr2---sn-vgqsrn6z.googlevideo.com.": {},
+ "rr2---sn-vgqsrn6z.gvt1.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-vgqsrnl6.gvt1.com.": {},
+ "rr2---sn-vgqsrnlk.googlevideo.com.": {},
+ "rr2---sn-vgqsrnll.googlevideo.com.": {},
+ "rr2---sn-vgqsrnls.googlevideo.com.": {},
+ "rr2---sn-vgqsrnlz.googlevideo.com.": {},
+ "rr2---sn-vgqsrns6.googlevideo.com.": {},
+ "rr2---sn-vgqsrnsd.googlevideo.com.": {},
+ "rr2---sn-vgqsrnsd.gvt1.com.": {},
+ "rr2---sn-vgqsrnsr.googlevideo.com.": {},
+ "rr2---sn-vgqsrnsr.gvt1.com.": {},
+ "rr2---sn-vgqsrnsy.googlevideo.com.": {},
+ "rr2---sn-vgqsrnsy.gvt1.com.": {},
+ "rr2---sn-vgqsrnz6.googlevideo.com.": {},
+ "rr2---sn-vgqsrnz7.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzd.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzk.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzr.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzr.gvt1.com.": {},
+ "rr2---sn-vgqsrnzs.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzy.googlevideo.com.": {},
+ "rr2---sn-vgqsrnzy.gvt1.com.": {},
+ "rr2---sn-vgqsrnzz.googlevideo.com.": {},
+ "rr2---sn-vnix5o-28ql.googlevideo.com.": {},
+ "rr2---sn-voxoxu-v3jl.googlevideo.com.": {},
+ "rr2---sn-voxoxu-v3js.googlevideo.com.": {},
+ "rr2---sn-xo5-co5l.googlevideo.com.": {},
+ "rr2.sn-5hne6nz6.googlevideo.com.": {},
+ "rr2.sn-5hnekn7d.googlevideo.com.": {},
+ "rr2.sn-aigzrne7.googlevideo.com.": {},
+ "rr2.sn-q4fl6n6d.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-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr3---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr3---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr3---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr3---sn-2vgu0b5auxaxjvh-v2vz.googlevideo.com.": {},
+ "rr3---sn-30a7rne6.googlevideo.com.": {},
+ "rr3---sn-30a7rned.googlevideo.com.": {},
+ "rr3---sn-30a7rner.googlevideo.com.": {},
+ "rr3---sn-30a7ynek.googlevideo.com.": {},
+ "rr3---sn-30a7yner.googlevideo.com.": {},
+ "rr3---sn-30a7yney.googlevideo.com.": {},
+ "rr3---sn-30a7ynl7.googlevideo.com.": {},
+ "rr3---sn-42u-nboze.googlevideo.com.": {},
+ "rr3---sn-42u-nbozs.googlevideo.com.": {},
+ "rr3---sn-42u-nbozz.googlevideo.com.": {},
+ "rr3---sn-4g5e6ns6.googlevideo.com.": {},
+ "rr3---sn-4g5e6ns7.googlevideo.com.": {},
+ "rr3---sn-4g5e6nsd.googlevideo.com.": {},
+ "rr3---sn-4g5e6nsk.googlevideo.com.": {},
+ "rr3---sn-4g5e6nsr.googlevideo.com.": {},
+ "rr3---sn-4g5e6nss.googlevideo.com.": {},
+ "rr3---sn-4g5e6nsz.googlevideo.com.": {},
+ "rr3---sn-4g5e6nz7.googlevideo.com.": {},
+ "rr3---sn-4g5e6nze.googlevideo.com.": {},
+ "rr3---sn-4g5e6nzl.googlevideo.com.": {},
+ "rr3---sn-4g5e6nzs.googlevideo.com.": {},
+ "rr3---sn-4g5e6nzz.googlevideo.com.": {},
+ "rr3---sn-4g5edn6k.googlevideo.com.": {},
+ "rr3---sn-4g5edn6r.googlevideo.com.": {},
+ "rr3---sn-4g5edn6y.googlevideo.com.": {},
+ "rr3---sn-4g5ednd7.googlevideo.com.": {},
+ "rr3---sn-4g5edndd.googlevideo.com.": {},
+ "rr3---sn-4g5ednde.googlevideo.com.": {},
+ "rr3---sn-4g5edndk.googlevideo.com.": {},
+ "rr3---sn-4g5edndl.googlevideo.com.": {},
+ "rr3---sn-4g5edndr.googlevideo.com.": {},
+ "rr3---sn-4g5ednds.googlevideo.com.": {},
+ "rr3---sn-4g5edndy.googlevideo.com.": {},
+ "rr3---sn-4g5edndz.googlevideo.com.": {},
+ "rr3---sn-4g5ednkl.googlevideo.com.": {},
+ "rr3---sn-4g5ednld.googlevideo.com.": {},
+ "rr3---sn-4g5ednly.googlevideo.com.": {},
+ "rr3---sn-4g5edns6.googlevideo.com.": {},
+ "rr3---sn-4g5edns7.googlevideo.com.": {},
+ "rr3---sn-4g5ednsd.googlevideo.com.": {},
+ "rr3---sn-4g5ednse.googlevideo.com.": {},
+ "rr3---sn-4g5ednsk.googlevideo.com.": {},
+ "rr3---sn-4g5ednsl.googlevideo.com.": {},
+ "rr3---sn-4g5ednsr.googlevideo.com.": {},
+ "rr3---sn-4g5ednss.googlevideo.com.": {},
+ "rr3---sn-4g5ednsy.googlevideo.com.": {},
+ "rr3---sn-4g5ednsz.googlevideo.com.": {},
+ "rr3---sn-4g5ednz7.googlevideo.com.": {},
+ "rr3---sn-4g5lzne6.googlevideo.com.": {},
+ "rr3---sn-4g5lzned.googlevideo.com.": {},
+ "rr3---sn-4g5lznek.googlevideo.com.": {},
+ "rr3---sn-4g5lzner.googlevideo.com.": {},
+ "rr3---sn-4g5lznes.googlevideo.com.": {},
+ "rr3---sn-4g5lzney.googlevideo.com.": {},
+ "rr3---sn-4g5lznez.googlevideo.com.": {},
+ "rr3---sn-4g5lznl6.googlevideo.com.": {},
+ "rr3---sn-4g5lznl7.googlevideo.com.": {},
+ "rr3---sn-4g5lznle.googlevideo.com.": {},
+ "rr3---sn-4g5lznls.googlevideo.com.": {},
+ "rr3---sn-4g5lznlz.googlevideo.com.": {},
+ "rr3---sn-5hne6n6e.googlevideo.com.": {},
+ "rr3---sn-5hne6n6l.googlevideo.com.": {},
+ "rr3---sn-5hne6ns6.googlevideo.com.": {},
+ "rr3---sn-5hne6nsd.googlevideo.com.": {},
+ "rr3---sn-5hne6nsk.googlevideo.com.": {},
+ "rr3---sn-5hne6nsr.googlevideo.com.": {},
+ "rr3---sn-5hne6nsy.googlevideo.com.": {},
+ "rr3---sn-5hne6nsz.googlevideo.com.": {},
+ "rr3---sn-5hne6nz6.googlevideo.com.": {},
+ "rr3---sn-5hne6nzd.googlevideo.com.": {},
+ "rr3---sn-5hne6nzd.gvt1.com.": {},
+ "rr3---sn-5hne6nzk.googlevideo.com.": {},
+ "rr3---sn-5hne6nzs.googlevideo.com.": {},
+ "rr3---sn-5hne6nzy.googlevideo.com.": {},
+ "rr3---sn-5hnednss.googlevideo.com.": {},
+ "rr3---sn-5hnednsz.googlevideo.com.": {},
+ "rr3---sn-5hnekn76.googlevideo.com.": {},
+ "rr3---sn-5hnekn7d.googlevideo.com.": {},
+ "rr3---sn-5hnekn7k.googlevideo.com.": {},
+ "rr3---sn-5hnekn7l.googlevideo.com.": {},
+ "rr3---sn-5hnekn7s.googlevideo.com.": {},
+ "rr3---sn-5hnekn7z.googlevideo.com.": {},
+ "rr3---sn-5hneknee.googlevideo.com.": {},
+ "rr3---sn-5hneknek.googlevideo.com.": {},
+ "rr3---sn-5hneknes.googlevideo.com.": {},
+ "rr3---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr3---sn-5uaezndd.googlevideo.com.": {},
+ "rr3---sn-5uaezne6.googlevideo.com.": {},
+ "rr3---sn-5uaezned.googlevideo.com.": {},
+ "rr3---sn-5uaeznez.googlevideo.com.": {},
+ "rr3---sn-5uaeznl6.googlevideo.com.": {},
+ "rr3---sn-5uaeznld.googlevideo.com.": {},
+ "rr3---sn-5uaeznls.googlevideo.com.": {},
+ "rr3---sn-5uaeznly.googlevideo.com.": {},
+ "rr3---sn-5uaeznlz.googlevideo.com.": {},
+ "rr3---sn-5uaeznrz.googlevideo.com.": {},
+ "rr3---sn-5uaezns7.googlevideo.com.": {},
+ "rr3---sn-5uaeznse.googlevideo.com.": {},
+ "rr3---sn-5uaeznsl.googlevideo.com.": {},
+ "rr3---sn-5uaeznss.googlevideo.com.": {},
+ "rr3---sn-5uaezny6.googlevideo.com.": {},
+ "rr3---sn-5uaeznys.googlevideo.com.": {},
+ "rr3---sn-5uaeznyz.googlevideo.com.": {},
+ "rr3---sn-5uaeznze.googlevideo.com.": {},
+ "rr3---sn-5ualdnle.googlevideo.com.": {},
+ "rr3---sn-5ualdnll.googlevideo.com.": {},
+ "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.": {},
+ "rr3---sn-5ualdnsl.googlevideo.com.": {},
+ "rr3---sn-5ualdnsr.googlevideo.com.": {},
+ "rr3---sn-5ualdnss.googlevideo.com.": {},
+ "rr3---sn-5ualdnsy.googlevideo.com.": {},
+ "rr3---sn-5ualdnsz.googlevideo.com.": {},
+ "rr3---sn-5ualdnz7.googlevideo.com.": {},
+ "rr3---sn-5ualdnze.googlevideo.com.": {},
+ "rr3---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr3---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr3---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-2iay.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-a5me.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab56.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab5d.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab5e.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab5l.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab5s.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-nh4e.googlevideo.com.": {},
+ "rr3---sn-8xgp1vo-p5ie.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-9gv76n7l.googlevideo.com.": {},
+ "rr3---sn-9gv76n7s.googlevideo.com.": {},
+ "rr3---sn-9gv76n7z.googlevideo.com.": {},
+ "rr3---sn-9gv7ene6.googlevideo.com.": {},
+ "rr3---sn-9gv7ened.googlevideo.com.": {},
+ "rr3---sn-9gv7zn76.googlevideo.com.": {},
+ "rr3---sn-9gv7zn7e.googlevideo.com.": {},
+ "rr3---sn-9gv7zn7r.googlevideo.com.": {},
+ "rr3---sn-a5m7lnld.googlevideo.com.": {},
+ "rr3---sn-a5m7lnld.gvt1.com.": {},
+ "rr3---sn-a5mekn6d.googlevideo.com.": {},
+ "rr3---sn-a5mekn6d.gvt1.com.": {},
+ "rr3---sn-a5mekn6k.googlevideo.com.": {},
+ "rr3---sn-a5mekn6l.googlevideo.com.": {},
+ "rr3---sn-a5mekn6l.gvt1.com.": {},
+ "rr3---sn-a5mekn6r.googlevideo.com.": {},
+ "rr3---sn-a5mekn6s.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-a5meknsd.googlevideo.com.": {},
+ "rr3---sn-a5meknsy.googlevideo.com.": {},
+ "rr3---sn-a5meknsy.gvt1.com.": {},
+ "rr3---sn-a5meknzk.googlevideo.com.": {},
+ "rr3---sn-a5meknzl.googlevideo.com.": {},
+ "rr3---sn-a5meknzl.gvt1.com.": {},
+ "rr3---sn-a5meknzr.googlevideo.com.": {},
+ "rr3---sn-a5meknzs.googlevideo.com.": {},
+ "rr3---sn-a5mlrnek.googlevideo.com.": {},
+ "rr3---sn-a5mlrnl6.googlevideo.com.": {},
+ "rr3---sn-a5mlrnll.googlevideo.com.": {},
+ "rr3---sn-a5mlrnll.gvt1.com.": {},
+ "rr3---sn-a5mlrnls.googlevideo.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-a5msenek.googlevideo.com.": {},
+ "rr3---sn-a5msenek.gvt1.com.": {},
+ "rr3---sn-a5msener.googlevideo.com.": {},
+ "rr3---sn-a5msenes.googlevideo.com.": {},
+ "rr3---sn-a5msenl7.googlevideo.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-ab5l6nk6.gvt1.com.": {},
+ "rr3---sn-ab5l6nkd.googlevideo.com.": {},
+ "rr3---sn-ab5l6nr6.googlevideo.com.": {},
+ "rr3---sn-ab5l6nr6.gvt1.com.": {},
+ "rr3---sn-ab5l6nrd.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrd.gvt1.com.": {},
+ "rr3---sn-ab5l6nrk.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrl.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrl.gvt1.com.": {},
+ "rr3---sn-ab5l6nrr.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrs.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrz.googlevideo.com.": {},
+ "rr3---sn-ab5l6nrz.gvt1.com.": {},
+ "rr3---sn-ab5sznld.googlevideo.com.": {},
+ "rr3---sn-ab5sznlk.googlevideo.com.": {},
+ "rr3---sn-ab5sznlk.gvt1.com.": {},
+ "rr3---sn-ab5sznly.googlevideo.com.": {},
+ "rr3---sn-ab5sznz6.googlevideo.com.": {},
+ "rr3---sn-ab5sznz6.gvt1.com.": {},
+ "rr3---sn-ab5sznzd.googlevideo.com.": {},
+ "rr3---sn-ab5sznzd.gvt1.com.": {},
+ "rr3---sn-ab5sznze.googlevideo.com.": {},
+ "rr3---sn-ab5sznze.gvt1.com.": {},
+ "rr3---sn-ab5sznzk.googlevideo.com.": {},
+ "rr3---sn-ab5sznzl.googlevideo.com.": {},
+ "rr3---sn-ab5sznzr.googlevideo.com.": {},
+ "rr3---sn-ab5sznzs.googlevideo.com.": {},
+ "rr3---sn-ab5sznzs.gvt1.com.": {},
+ "rr3---sn-ab5sznzy.googlevideo.com.": {},
+ "rr3---sn-ab5sznzz.googlevideo.com.": {},
+ "rr3---sn-aigl6n6s.googlevideo.com.": {},
+ "rr3---sn-aigl6n6s.gvt1.com.": {},
+ "rr3---sn-aigl6ned.googlevideo.com.": {},
+ "rr3---sn-aigl6ner.googlevideo.com.": {},
+ "rr3---sn-aigl6ney.googlevideo.com.": {},
+ "rr3---sn-aigl6nl7.googlevideo.com.": {},
+ "rr3---sn-aigl6nl7.gvt1.com.": {},
+ "rr3---sn-aigl6ns6.googlevideo.com.": {},
+ "rr3---sn-aigl6nsd.googlevideo.com.": {},
+ "rr3---sn-aigl6nsk.googlevideo.com.": {},
+ "rr3---sn-aigl6nsr.googlevideo.com.": {},
+ "rr3---sn-aigl6nz7.googlevideo.com.": {},
+ "rr3---sn-aigl6nze.googlevideo.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.": {},
+ "rr3---sn-aigzrn7d.gvt1.com.": {},
+ "rr3---sn-aigzrn7e.googlevideo.com.": {},
+ "rr3---sn-aigzrn7k.googlevideo.com.": {},
+ "rr3---sn-aigzrn7l.googlevideo.com.": {},
+ "rr3---sn-aigzrn7s.googlevideo.com.": {},
+ "rr3---sn-aigzrn7z.googlevideo.com.": {},
+ "rr3---sn-aigzrne7.googlevideo.com.": {},
+ "rr3---sn-aigzrnld.googlevideo.com.": {},
+ "rr3---sn-aigzrnse.googlevideo.com.": {},
+ "rr3---sn-aigzrnsl.googlevideo.com.": {},
+ "rr3---sn-aigzrnsr.googlevideo.com.": {},
+ "rr3---sn-aigzrnss.googlevideo.com.": {},
+ "rr3---sn-aigzrnsz.googlevideo.com.": {},
+ "rr3---sn-aigzrnz7.googlevideo.com.": {},
+ "rr3---sn-aigzrnze.googlevideo.com.": {},
+ "rr3---sn-apn7en7e.googlevideo.com.": {},
+ "rr3---sn-apn7en7s.googlevideo.com.": {},
+ "rr3---sn-bg5oqxjvh-50nz.googlevideo.com.": {},
+ "rr3---sn-c0q7lnz7.googlevideo.com.": {},
+ "rr3---sn-cvb7lne7.googlevideo.com.": {},
+ "rr3---sn-cvb7lnee.googlevideo.com.": {},
+ "rr3---sn-cvb7lnl7.googlevideo.com.": {},
+ "rr3---sn-cvb7lnls.googlevideo.com.": {},
+ "rr3---sn-cvb7lnlz.googlevideo.com.": {},
+ "rr3---sn-cvb7sn7k.googlevideo.com.": {},
+ "rr3---sn-cvb7sn7r.googlevideo.com.": {},
+ "rr3---sn-gpuuxg-hxhl.googlevideo.com.": {},
+ "rr3---sn-gpuuxg-hxhl.gvt1.com.": {},
+ "rr3---sn-h0jeened.googlevideo.com.": {},
+ "rr3---sn-h0jeenek.googlevideo.com.": {},
+ "rr3---sn-h0jeener.googlevideo.com.": {},
+ "rr3---sn-h0jeenl6.googlevideo.com.": {},
+ "rr3---sn-h0jeenld.googlevideo.com.": {},
+ "rr3---sn-h0jeenle.googlevideo.com.": {},
+ "rr3---sn-h0jeln7l.googlevideo.com.": {},
+ "rr3---sn-h0jelne6.googlevideo.com.": {},
+ "rr3---sn-h0jelne7.googlevideo.com.": {},
+ "rr3---sn-h0jelnes.googlevideo.com.": {},
+ "rr3---sn-h0jelnez.googlevideo.com.": {},
+ "rr3---sn-hjoj-gq0l.googlevideo.com.": {},
+ "rr3---sn-hjoj-gq0l.gvt1.com.": {},
+ "rr3---sn-hjoj-poul.googlevideo.com.": {},
+ "rr3---sn-hoa7kn76.googlevideo.com.": {},
+ "rr3---sn-hoa7kn7z.googlevideo.com.": {},
+ "rr3---sn-hoa7rn76.googlevideo.com.": {},
+ "rr3---sn-hoa7rn7z.googlevideo.com.": {},
+ "rr3---sn-hp57kn6y.googlevideo.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-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-hp57yn7y.googlevideo.com.": {},
+ "rr3---sn-hp57yn7y.gvt1.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-hp57ynly.gvt1.com.": {},
+ "rr3---sn-hp57yns7.googlevideo.com.": {},
+ "rr3---sn-hp57ynse.googlevideo.com.": {},
+ "rr3---sn-hp57ynsl.googlevideo.com.": {},
+ "rr3---sn-hp57ynss.googlevideo.com.": {},
+ "rr3---sn-hp57ynss.gvt1.com.": {},
+ "rr3---sn-i3b7kn6s.googlevideo.com.": {},
+ "rr3---sn-i3b7knld.googlevideo.com.": {},
+ "rr3---sn-i3b7knlk.googlevideo.com.": {},
+ "rr3---sn-i3b7kns6.googlevideo.com.": {},
+ "rr3---sn-i3b7knsd.googlevideo.com.": {},
+ "rr3---sn-i3b7knse.googlevideo.com.": {},
+ "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.": {},
+ "rr3---sn-i3belnls.googlevideo.com.": {},
+ "rr3---sn-i3belnlz.googlevideo.com.": {},
+ "rr3---sn-i3bssn7e.googlevideo.com.": {},
+ "rr3---sn-i5f5ppuxa-ioal.googlevideo.com.": {},
+ "rr3---sn-i5h7lner.googlevideo.com.": {},
+ "rr3---sn-i5h7lnl6.googlevideo.com.": {},
+ "rr3---sn-i5h7lnll.googlevideo.com.": {},
+ "rr3---sn-i5h7lnls.googlevideo.com.": {},
+ "rr3---sn-i5heen7d.googlevideo.com.": {},
+ "rr3---sn-i5heen7r.googlevideo.com.": {},
+ "rr3---sn-i5heen7s.googlevideo.com.": {},
+ "rr3---sn-jn2pgx4pcxg-w5os.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-2iae.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-2ial.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-nh4e.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-nh4l.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-nh4s.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-nh4z.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-qufe.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-qufl.googlevideo.com.": {},
+ "rr3---sn-jvhj5nu-qufz.googlevideo.com.": {},
+ "rr3---sn-jxopj-n5oe.googlevideo.com.": {},
+ "rr3---sn-jxopj-n5oe.gvt1.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-n4v7sney.gvt1.com.": {},
+ "rr3---sn-n4v7snl7.googlevideo.com.": {},
+ "rr3---sn-n4v7snl7.gvt1.com.": {},
+ "rr3---sn-n4v7snll.googlevideo.com.": {},
+ "rr3---sn-n4v7snlr.googlevideo.com.": {},
+ "rr3---sn-n4v7snls.googlevideo.com.": {},
+ "rr3---sn-n4v7snly.googlevideo.com.": {},
+ "rr3---sn-n4v7sns7.googlevideo.com.": {},
+ "rr3---sn-n4v7snse.googlevideo.com.": {},
+ "rr3---sn-npoe7ndl.googlevideo.com.": {},
+ "rr3---sn-npoe7nds.googlevideo.com.": {},
+ "rr3---sn-npoe7ne6.googlevideo.com.": {},
+ "rr3---sn-npoe7ne7.googlevideo.com.": {},
+ "rr3---sn-npoe7ned.googlevideo.com.": {},
+ "rr3---sn-npoe7nek.googlevideo.com.": {},
+ "rr3---sn-npoe7ner.googlevideo.com.": {},
+ "rr3---sn-npoe7nes.googlevideo.com.": {},
+ "rr3---sn-npoe7ney.googlevideo.com.": {},
+ "rr3---sn-npoe7nez.googlevideo.com.": {},
+ "rr3---sn-npoe7nl6.googlevideo.com.": {},
+ "rr3---sn-npoe7nlz.googlevideo.com.": {},
+ "rr3---sn-npoe7ns6.googlevideo.com.": {},
+ "rr3---sn-npoe7ns7.googlevideo.com.": {},
+ "rr3---sn-npoe7nsd.googlevideo.com.": {},
+ "rr3---sn-npoe7nsk.googlevideo.com.": {},
+ "rr3---sn-npoe7nsl.googlevideo.com.": {},
+ "rr3---sn-npoe7nsr.googlevideo.com.": {},
+ "rr3---sn-npoe7nss.googlevideo.com.": {},
+ "rr3---sn-npoe7nsy.googlevideo.com.": {},
+ "rr3---sn-npoe7nz7.googlevideo.com.": {},
+ "rr3---sn-npoeene6.googlevideo.com.": {},
+ "rr3---sn-npoeened.googlevideo.com.": {},
+ "rr3---sn-npoeenee.googlevideo.com.": {},
+ "rr3---sn-npoeenek.googlevideo.com.": {},
+ "rr3---sn-npoeener.googlevideo.com.": {},
+ "rr3---sn-npoeeney.googlevideo.com.": {},
+ "rr3---sn-npoeenez.googlevideo.com.": {},
+ "rr3---sn-npoeenl7.googlevideo.com.": {},
+ "rr3---sn-npoeenle.googlevideo.com.": {},
+ "rr3---sn-npoeens7.googlevideo.com.": {},
+ "rr3---sn-npoldn76.googlevideo.com.": {},
+ "rr3---sn-npoldn7d.googlevideo.com.": {},
+ "rr3---sn-npoldn7e.googlevideo.com.": {},
+ "rr3---sn-npoldn7l.googlevideo.com.": {},
+ "rr3---sn-npoldn7s.googlevideo.com.": {},
+ "rr3---sn-npoldn7y.googlevideo.com.": {},
+ "rr3---sn-npoldn7z.googlevideo.com.": {},
+ "rr3---sn-npoldne7.googlevideo.com.": {},
+ "rr3---sn-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr3---sn-nv47ln6e.googlevideo.com.": {},
+ "rr3---sn-nv47lnly.googlevideo.com.": {},
+ "rr3---sn-nv47lns6.googlevideo.com.": {},
+ "rr3---sn-nv47lns7.googlevideo.com.": {},
+ "rr3---sn-nv47lnsr.googlevideo.com.": {},
+ "rr3---sn-nv47zn7r.googlevideo.com.": {},
+ "rr3---sn-nv47zn7y.googlevideo.com.": {},
+ "rr3---sn-nv47zne7.googlevideo.com.": {},
+ "rr3---sn-nv47znee.googlevideo.com.": {},
+ "rr3---sn-nv47znel.googlevideo.com.": {},
+ "rr3---sn-nx57ynsd.googlevideo.com.": {},
+ "rr3---sn-nx57ynsd.gvt1.com.": {},
+ "rr3---sn-nx57ynse.googlevideo.com.": {},
+ "rr3---sn-nx57ynse.gvt1.com.": {},
+ "rr3---sn-nx57ynsk.googlevideo.com.": {},
+ "rr3---sn-nx57ynsk.gvt1.com.": {},
+ "rr3---sn-nx57ynsl.googlevideo.com.": {},
+ "rr3---sn-nx57ynsl.gvt1.com.": {},
+ "rr3---sn-nx57ynss.googlevideo.com.": {},
+ "rr3---sn-nx57ynss.gvt1.com.": {},
+ "rr3---sn-nx57ynsz.googlevideo.com.": {},
+ "rr3---sn-nx5s7n76.googlevideo.com.": {},
+ "rr3---sn-nx5s7n7d.googlevideo.com.": {},
+ "rr3---sn-nx5s7n7s.googlevideo.com.": {},
+ "rr3---sn-nx5s7n7y.googlevideo.com.": {},
+ "rr3---sn-nx5s7n7y.gvt1.com.": {},
+ "rr3---sn-nx5s7n7z.googlevideo.com.": {},
+ "rr3---sn-nx5s7nee.googlevideo.com.": {},
+ "rr3---sn-nx5s7nee.gvt1.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.": {},
+ "rr3---sn-o097znsr.googlevideo.com.": {},
+ "rr3---sn-o097znsr.gvt1.com.": {},
+ "rr3---sn-o097znss.googlevideo.com.": {},
+ "rr3---sn-o097znsz.googlevideo.com.": {},
+ "rr3---sn-o097znz7.googlevideo.com.": {},
+ "rr3---sn-o097znzd.googlevideo.com.": {},
+ "rr3---sn-o097znze.googlevideo.com.": {},
+ "rr3---sn-o097znzk.googlevideo.com.": {},
+ "rr3---sn-o097znzr.googlevideo.com.": {},
+ "rr3---sn-ounjvhh-acce.googlevideo.com.": {},
+ "rr3---sn-p5qddn76.googlevideo.com.": {},
+ "rr3---sn-p5qddn7d.googlevideo.com.": {},
+ "rr3---sn-p5qddn7k.googlevideo.com.": {},
+ "rr3---sn-p5qddn7z.googlevideo.com.": {},
+ "rr3---sn-p5qlsn6l.googlevideo.com.": {},
+ "rr3---sn-p5qlsn76.googlevideo.com.": {},
+ "rr3---sn-p5qlsn76.gvt1.com.": {},
+ "rr3---sn-p5qlsn7d.googlevideo.com.": {},
+ "rr3---sn-p5qlsn7l.googlevideo.com.": {},
+ "rr3---sn-p5qlsn7s.googlevideo.com.": {},
+ "rr3---sn-p5qlsn7s.gvt1.com.": {},
+ "rr3---sn-p5qlsnd6.googlevideo.com.": {},
+ "rr3---sn-p5qlsndd.googlevideo.com.": {},
+ "rr3---sn-p5qlsndk.googlevideo.com.": {},
+ "rr3---sn-p5qlsndz.googlevideo.com.": {},
+ "rr3---sn-p5qlsnrl.googlevideo.com.": {},
+ "rr3---sn-p5qlsnrr.googlevideo.com.": {},
+ "rr3---sn-p5qlsny6.googlevideo.com.": {},
+ "rr3---sn-p5qs7n6d.googlevideo.com.": {},
+ "rr3---sn-p5qs7nzk.googlevideo.com.": {},
+ "rr3---sn-p5qs7nzr.googlevideo.com.": {},
+ "rr3---sn-p5qs7nzy.googlevideo.com.": {},
+ "rr3---sn-pobpb-poql.googlevideo.com.": {},
+ "rr3---sn-q4fl6n66.googlevideo.com.": {},
+ "rr3---sn-q4fl6n6d.googlevideo.com.": {},
+ "rr3---sn-q4fl6n6r.googlevideo.com.": {},
+ "rr3---sn-q4fl6n6s.googlevideo.com.": {},
+ "rr3---sn-q4fl6n6y.googlevideo.com.": {},
+ "rr3---sn-q4fl6n6y.gvt1.com.": {},
+ "rr3---sn-q4fl6n6z.googlevideo.com.": {},
+ "rr3---sn-q4fl6nd6.googlevideo.com.": {},
+ "rr3---sn-q4fl6nd6.gvt1.com.": {},
+ "rr3---sn-q4fl6nd7.googlevideo.com.": {},
+ "rr3---sn-q4fl6nde.googlevideo.com.": {},
+ "rr3---sn-q4fl6ndl.googlevideo.com.": {},
+ "rr3---sn-q4fl6ndl.gvt1.com.": {},
+ "rr3---sn-q4fl6nds.googlevideo.com.": {},
+ "rr3---sn-q4fl6ndz.googlevideo.com.": {},
+ "rr3---sn-q4fl6nlz.googlevideo.com.": {},
+ "rr3---sn-q4fl6ns6.googlevideo.com.": {},
+ "rr3---sn-q4fl6ns6.gvt1.com.": {},
+ "rr3---sn-q4fl6ns7.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsd.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsk.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsk.gvt1.com.": {},
+ "rr3---sn-q4fl6nsl.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsl.gvt1.com.": {},
+ "rr3---sn-q4fl6nsr.googlevideo.com.": {},
+ "rr3---sn-q4fl6nss.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsy.googlevideo.com.": {},
+ "rr3---sn-q4fl6nsy.gvt1.com.": {},
+ "rr3---sn-q4fl6nz6.googlevideo.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-q4flrn7y.googlevideo.com.": {},
+ "rr3---sn-q4flrn7y.gvt1.com.": {},
+ "rr3---sn-q4flrne6.googlevideo.com.": {},
+ "rr3---sn-q4flrne6.gvt1.com.": {},
+ "rr3---sn-q4flrne7.googlevideo.com.": {},
+ "rr3---sn-q4flrnee.googlevideo.com.": {},
+ "rr3---sn-q4flrnee.gvt1.com.": {},
+ "rr3---sn-q4flrnek.googlevideo.com.": {},
+ "rr3---sn-q4flrnel.googlevideo.com.": {},
+ "rr3---sn-q4flrner.googlevideo.com.": {},
+ "rr3---sn-q4flrnes.googlevideo.com.": {},
+ "rr3---sn-q4flrney.googlevideo.com.": {},
+ "rr3---sn-q4flrney.gvt1.com.": {},
+ "rr3---sn-q4flrnez.googlevideo.com.": {},
+ "rr3---sn-q4flrnl6.googlevideo.com.": {},
+ "rr3---sn-q4flrnl6.gvt1.com.": {},
+ "rr3---sn-q4flrnl7.googlevideo.com.": {},
+ "rr3---sn-q4flrnl7.gvt1.com.": {},
+ "rr3---sn-q4flrnld.googlevideo.com.": {},
+ "rr3---sn-q4flrnld.gvt1.com.": {},
+ "rr3---sn-q4flrnle.googlevideo.com.": {},
+ "rr3---sn-q4flrnle.gvt1.com.": {},
+ "rr3---sn-q4flrnlz.googlevideo.com.": {},
+ "rr3---sn-q4flrnlz.gvt1.com.": {},
+ "rr3---sn-q4flrnsd.googlevideo.com.": {},
+ "rr3---sn-q4flrnsk.googlevideo.com.": {},
+ "rr3---sn-q4flrnsk.gvt1.com.": {},
+ "rr3---sn-q4flrnsl.googlevideo.com.": {},
+ "rr3---sn-q4flrnss.googlevideo.com.": {},
+ "rr3---sn-q4fzen7e.googlevideo.com.": {},
+ "rr3---sn-q4fzen7l.googlevideo.com.": {},
+ "rr3---sn-q4fzen7l.gvt1.com.": {},
+ "rr3---sn-q4fzen7r.googlevideo.com.": {},
+ "rr3---sn-q4fzen7r.gvt1.com.": {},
+ "rr3---sn-q4fzen7s.googlevideo.com.": {},
+ "rr3---sn-q4fzen7y.googlevideo.com.": {},
+ "rr3---sn-q4fzene7.googlevideo.com.": {},
+ "rr3---sn-q4fzene7.gvt1.com.": {},
+ "rr3---sn-q4fzenee.googlevideo.com.": {},
+ "rr3---sn-q4fzenee.gvt1.com.": {},
+ "rr3---sn-qpbp-30ar.googlevideo.com.": {},
+ "rr3---sn-qxo7rn7k.googlevideo.com.": {},
+ "rr3---sn-qxo7rn7r.googlevideo.com.": {},
+ "rr3---sn-qxo7rn7y.googlevideo.com.": {},
+ "rr3---sn-qxoedn7k.googlevideo.com.": {},
+ "rr3---sn-qxoednee.googlevideo.com.": {},
+ "rr3---sn-t0a7sn7d.googlevideo.com.": {},
+ "rr3---sn-u1hp55-5c.googlevideo.com.": {},
+ "rr3---sn-u1hp55-5c.gvt1.com.": {},
+ "rr3---sn-uhvcpaxoa-5hne.googlevideo.com.": {},
+ "rr3---sn-uhvcpaxoa-guhe.googlevideo.com.": {},
+ "rr3---sn-v5goxu-jhil.googlevideo.com.": {},
+ "rr3---sn-v5goxu-jhil.gvt1.com.": {},
+ "rr3---sn-vgqskn66.googlevideo.com.": {},
+ "rr3---sn-vgqskn67.googlevideo.com.": {},
+ "rr3---sn-vgqskn67.gvt1.com.": {},
+ "rr3---sn-vgqskn6d.googlevideo.com.": {},
+ "rr3---sn-vgqskn6s.googlevideo.com.": {},
+ "rr3---sn-vgqskn6z.googlevideo.com.": {},
+ "rr3---sn-vgqskne6.googlevideo.com.": {},
+ "rr3---sn-vgqskne6.gvt1.com.": {},
+ "rr3---sn-vgqskned.googlevideo.com.": {},
+ "rr3---sn-vgqsknek.googlevideo.com.": {},
+ "rr3---sn-vgqsknes.googlevideo.com.": {},
+ "rr3---sn-vgqsknez.googlevideo.com.": {},
+ "rr3---sn-vgqsknlk.googlevideo.com.": {},
+ "rr3---sn-vgqsknll.googlevideo.com.": {},
+ "rr3---sn-vgqsknls.googlevideo.com.": {},
+ "rr3---sn-vgqsknls.gvt1.com.": {},
+ "rr3---sn-vgqsknly.googlevideo.com.": {},
+ "rr3---sn-vgqsknlz.googlevideo.com.": {},
+ "rr3---sn-vgqskns7.googlevideo.com.": {},
+ "rr3---sn-vgqsknse.googlevideo.com.": {},
+ "rr3---sn-vgqsknse.gvt1.com.": {},
+ "rr3---sn-vgqsknsk.googlevideo.com.": {},
+ "rr3---sn-vgqsknsk.gvt1.com.": {},
+ "rr3---sn-vgqsknz6.googlevideo.com.": {},
+ "rr3---sn-vgqsknz6.gvt1.com.": {},
+ "rr3---sn-vgqsknz7.googlevideo.com.": {},
+ "rr3---sn-vgqsknzd.googlevideo.com.": {},
+ "rr3---sn-vgqsknzd.gvt1.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-vgqsknzs.gvt1.com.": {},
+ "rr3---sn-vgqsknzy.googlevideo.com.": {},
+ "rr3---sn-vgqsknzz.googlevideo.com.": {},
+ "rr3---sn-vgqsrn66.googlevideo.com.": {},
+ "rr3---sn-vgqsrn66.gvt1.com.": {},
+ "rr3---sn-vgqsrn67.googlevideo.com.": {},
+ "rr3---sn-vgqsrn6e.googlevideo.com.": {},
+ "rr3---sn-vgqsrn6e.gvt1.com.": {},
+ "rr3---sn-vgqsrn6l.googlevideo.com.": {},
+ "rr3---sn-vgqsrn6z.googlevideo.com.": {},
+ "rr3---sn-vgqsrn6z.gvt1.com.": {},
+ "rr3---sn-vgqsrne6.googlevideo.com.": {},
+ "rr3---sn-vgqsrned.googlevideo.com.": {},
+ "rr3---sn-vgqsrned.gvt1.com.": {},
+ "rr3---sn-vgqsrnek.googlevideo.com.": {},
+ "rr3---sn-vgqsrnek.gvt1.com.": {},
+ "rr3---sn-vgqsrnes.googlevideo.com.": {},
+ "rr3---sn-vgqsrnez.googlevideo.com.": {},
+ "rr3---sn-vgqsrnl6.googlevideo.com.": {},
+ "rr3---sn-vgqsrnld.googlevideo.com.": {},
+ "rr3---sn-vgqsrnlk.googlevideo.com.": {},
+ "rr3---sn-vgqsrnlk.gvt1.com.": {},
+ "rr3---sn-vgqsrnll.googlevideo.com.": {},
+ "rr3---sn-vgqsrnlz.googlevideo.com.": {},
+ "rr3---sn-vgqsrns6.googlevideo.com.": {},
+ "rr3---sn-vgqsrns6.gvt1.com.": {},
+ "rr3---sn-vgqsrnsd.googlevideo.com.": {},
+ "rr3---sn-vgqsrnsr.googlevideo.com.": {},
+ "rr3---sn-vgqsrnsr.gvt1.com.": {},
+ "rr3---sn-vgqsrnsy.googlevideo.com.": {},
+ "rr3---sn-vgqsrnsy.gvt1.com.": {},
+ "rr3---sn-vgqsrnz6.googlevideo.com.": {},
+ "rr3---sn-vgqsrnz6.gvt1.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-vgqsrnzz.gvt1.com.": {},
+ "rr3.sn-q4fl6nd6.googlevideo.com.": {},
+ "rr3.sn-q4fl6ndz.googlevideo.com.": {},
+ "rr3.sn-q4fl6nsd.googlevideo.com.": {},
+ "rr3.sn-q4flrne6.googlevideo.com.": {},
+ "rr3.sn-q4flrne7.googlevideo.com.": {},
+ "rr3.sn-t0a7sn7d.googlevideo.com.": {},
+ "rr4---sn-0nnpbo5a-bggl.googlevideo.com.": {},
+ "rr4---sn-2aqu-hoas7.googlevideo.com.": {},
+ "rr4---sn-2imern76.googlevideo.com.": {},
+ "rr4---sn-2imern76.gvt1.com.": {},
+ "rr4---sn-2imern7d.googlevideo.com.": {},
+ "rr4---sn-2imeyn7k.googlevideo.com.": {},
+ "rr4---sn-2imeyn7k.gvt1.com.": {},
+ "rr4---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr4---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr4---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr4---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr4---sn-2vgu0b5auxaxjvh-v2vz.googlevideo.com.": {},
+ "rr4---sn-30a7rne6.googlevideo.com.": {},
+ "rr4---sn-30a7rned.googlevideo.com.": {},
+ "rr4---sn-30a7rner.googlevideo.com.": {},
+ "rr4---sn-30a7ynek.googlevideo.com.": {},
+ "rr4---sn-30a7yner.googlevideo.com.": {},
+ "rr4---sn-30a7yney.googlevideo.com.": {},
+ "rr4---sn-30a7ynl7.googlevideo.com.": {},
+ "rr4---sn-42u-nboze.googlevideo.com.": {},
+ "rr4---sn-42u-nbozl.googlevideo.com.": {},
+ "rr4---sn-42u-nbozz.googlevideo.com.": {},
+ "rr4---sn-4g5e6ns6.googlevideo.com.": {},
+ "rr4---sn-4g5e6nsd.googlevideo.com.": {},
+ "rr4---sn-4g5e6nsk.googlevideo.com.": {},
+ "rr4---sn-4g5e6nsr.googlevideo.com.": {},
+ "rr4---sn-4g5e6nss.googlevideo.com.": {},
+ "rr4---sn-4g5e6nsy.googlevideo.com.": {},
+ "rr4---sn-4g5e6nsz.googlevideo.com.": {},
+ "rr4---sn-4g5e6nz7.googlevideo.com.": {},
+ "rr4---sn-4g5e6nze.googlevideo.com.": {},
+ "rr4---sn-4g5e6nzl.googlevideo.com.": {},
+ "rr4---sn-4g5e6nzs.googlevideo.com.": {},
+ "rr4---sn-4g5e6nzz.googlevideo.com.": {},
+ "rr4---sn-4g5edn6k.googlevideo.com.": {},
+ "rr4---sn-4g5edn6r.googlevideo.com.": {},
+ "rr4---sn-4g5edn6y.googlevideo.com.": {},
+ "rr4---sn-4g5ednd7.googlevideo.com.": {},
+ "rr4---sn-4g5edndd.googlevideo.com.": {},
+ "rr4---sn-4g5ednde.googlevideo.com.": {},
+ "rr4---sn-4g5edndk.googlevideo.com.": {},
+ "rr4---sn-4g5edndl.googlevideo.com.": {},
+ "rr4---sn-4g5edndr.googlevideo.com.": {},
+ "rr4---sn-4g5ednds.googlevideo.com.": {},
+ "rr4---sn-4g5edndy.googlevideo.com.": {},
+ "rr4---sn-4g5edndz.googlevideo.com.": {},
+ "rr4---sn-4g5ednkl.googlevideo.com.": {},
+ "rr4---sn-4g5ednld.googlevideo.com.": {},
+ "rr4---sn-4g5ednly.googlevideo.com.": {},
+ "rr4---sn-4g5edns6.googlevideo.com.": {},
+ "rr4---sn-4g5edns7.googlevideo.com.": {},
+ "rr4---sn-4g5ednsd.googlevideo.com.": {},
+ "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.": {},
+ "rr4---sn-4g5lzne6.googlevideo.com.": {},
+ "rr4---sn-4g5lzned.googlevideo.com.": {},
+ "rr4---sn-4g5lzner.googlevideo.com.": {},
+ "rr4---sn-4g5lznes.googlevideo.com.": {},
+ "rr4---sn-4g5lzney.googlevideo.com.": {},
+ "rr4---sn-4g5lznez.googlevideo.com.": {},
+ "rr4---sn-4g5lznl6.googlevideo.com.": {},
+ "rr4---sn-4g5lznl7.googlevideo.com.": {},
+ "rr4---sn-4g5lznle.googlevideo.com.": {},
+ "rr4---sn-4g5lznls.googlevideo.com.": {},
+ "rr4---sn-4g5lznlz.googlevideo.com.": {},
+ "rr4---sn-5hne6n6e.googlevideo.com.": {},
+ "rr4---sn-5hne6n6l.googlevideo.com.": {},
+ "rr4---sn-5hne6ns6.googlevideo.com.": {},
+ "rr4---sn-5hne6nsd.googlevideo.com.": {},
+ "rr4---sn-5hne6nsk.googlevideo.com.": {},
+ "rr4---sn-5hne6nsr.googlevideo.com.": {},
+ "rr4---sn-5hne6nsy.googlevideo.com.": {},
+ "rr4---sn-5hne6nsz.googlevideo.com.": {},
+ "rr4---sn-5hne6nz6.googlevideo.com.": {},
+ "rr4---sn-5hne6nzd.googlevideo.com.": {},
+ "rr4---sn-5hne6nzk.googlevideo.com.": {},
+ "rr4---sn-5hne6nzk.gvt1.com.": {},
+ "rr4---sn-5hne6nzy.googlevideo.com.": {},
+ "rr4---sn-5hne6nzy.gvt1.com.": {},
+ "rr4---sn-5hnednss.googlevideo.com.": {},
+ "rr4---sn-5hnednsz.googlevideo.com.": {},
+ "rr4---sn-5hnekn76.googlevideo.com.": {},
+ "rr4---sn-5hnekn7d.googlevideo.com.": {},
+ "rr4---sn-5hnekn7k.googlevideo.com.": {},
+ "rr4---sn-5hnekn7l.googlevideo.com.": {},
+ "rr4---sn-5hnekn7s.googlevideo.com.": {},
+ "rr4---sn-5hnekn7z.googlevideo.com.": {},
+ "rr4---sn-5hneknee.googlevideo.com.": {},
+ "rr4---sn-5hneknek.googlevideo.com.": {},
+ "rr4---sn-5hneknes.googlevideo.com.": {},
+ "rr4---sn-5hneknes.gvt1.com.": {},
+ "rr4---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr4---sn-5uaezndd.googlevideo.com.": {},
+ "rr4---sn-5uaezne6.googlevideo.com.": {},
+ "rr4---sn-5uaezned.googlevideo.com.": {},
+ "rr4---sn-5uaeznez.googlevideo.com.": {},
+ "rr4---sn-5uaeznl6.googlevideo.com.": {},
+ "rr4---sn-5uaeznld.googlevideo.com.": {},
+ "rr4---sn-5uaeznls.googlevideo.com.": {},
+ "rr4---sn-5uaeznly.googlevideo.com.": {},
+ "rr4---sn-5uaeznlz.googlevideo.com.": {},
+ "rr4---sn-5uaeznrz.googlevideo.com.": {},
+ "rr4---sn-5uaezns7.googlevideo.com.": {},
+ "rr4---sn-5uaeznse.googlevideo.com.": {},
+ "rr4---sn-5uaeznsl.googlevideo.com.": {},
+ "rr4---sn-5uaeznss.googlevideo.com.": {},
+ "rr4---sn-5uaezny6.googlevideo.com.": {},
+ "rr4---sn-5uaeznys.googlevideo.com.": {},
+ "rr4---sn-5uaeznyz.googlevideo.com.": {},
+ "rr4---sn-5uaeznze.googlevideo.com.": {},
+ "rr4---sn-5ualdnlr.googlevideo.com.": {},
+ "rr4---sn-5ualdnls.googlevideo.com.": {},
+ "rr4---sn-5ualdns6.googlevideo.com.": {},
+ "rr4---sn-5ualdns7.googlevideo.com.": {},
+ "rr4---sn-5ualdnsd.googlevideo.com.": {},
+ "rr4---sn-5ualdnse.googlevideo.com.": {},
+ "rr4---sn-5ualdnsk.googlevideo.com.": {},
+ "rr4---sn-5ualdnsl.googlevideo.com.": {},
+ "rr4---sn-5ualdnsr.googlevideo.com.": {},
+ "rr4---sn-5ualdnss.googlevideo.com.": {},
+ "rr4---sn-5ualdnsy.googlevideo.com.": {},
+ "rr4---sn-5ualdnsz.googlevideo.com.": {},
+ "rr4---sn-5ualdnz7.googlevideo.com.": {},
+ "rr4---sn-5ualdnze.googlevideo.com.": {},
+ "rr4---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr4---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr4---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-2iay.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-a5me.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab56.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab5d.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab5e.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab5l.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab5s.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-nh4e.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-p5ie.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-vgqe.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-xfge.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-xfgl.googlevideo.com.": {},
+ "rr4---sn-8xgp1vo-xfgs.googlevideo.com.": {},
+ "rr4---sn-9gv76n7e.googlevideo.com.": {},
+ "rr4---sn-9gv76n7l.googlevideo.com.": {},
+ "rr4---sn-9gv76n7s.googlevideo.com.": {},
+ "rr4---sn-9gv7ene6.googlevideo.com.": {},
+ "rr4---sn-9gv7ened.googlevideo.com.": {},
+ "rr4---sn-9gv7zn76.googlevideo.com.": {},
+ "rr4---sn-9gv7zn7e.googlevideo.com.": {},
+ "rr4---sn-9gv7zn7r.googlevideo.com.": {},
+ "rr4---sn-a5m7lnl6.googlevideo.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-a5mekn6k.gvt1.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-a5mekn6z.googlevideo.com.": {},
+ "rr4---sn-a5meknd6.googlevideo.com.": {},
+ "rr4---sn-a5meknd6.gvt1.com.": {},
+ "rr4---sn-a5meknde.googlevideo.com.": {},
+ "rr4---sn-a5mekndl.googlevideo.com.": {},
+ "rr4---sn-a5meknds.googlevideo.com.": {},
+ "rr4---sn-a5mekndz.googlevideo.com.": {},
+ "rr4---sn-a5mekndz.gvt1.com.": {},
+ "rr4---sn-a5meknsd.googlevideo.com.": {},
+ "rr4---sn-a5meknsy.googlevideo.com.": {},
+ "rr4---sn-a5meknzk.googlevideo.com.": {},
+ "rr4---sn-a5meknzk.gvt1.com.": {},
+ "rr4---sn-a5meknzl.googlevideo.com.": {},
+ "rr4---sn-a5meknzl.gvt1.com.": {},
+ "rr4---sn-a5meknzr.googlevideo.com.": {},
+ "rr4---sn-a5meknzs.googlevideo.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-a5mlrnlz.googlevideo.com.": {},
+ "rr4---sn-a5mlrnlz.gvt1.com.": {},
+ "rr4---sn-a5msen7l.googlevideo.com.": {},
+ "rr4---sn-a5msen7s.googlevideo.com.": {},
+ "rr4---sn-a5msen7z.googlevideo.com.": {},
+ "rr4---sn-a5msenek.googlevideo.com.": {},
+ "rr4---sn-a5msenek.gvt1.com.": {},
+ "rr4---sn-a5msenes.googlevideo.com.": {},
+ "rr4---sn-a5msenes.gvt1.com.": {},
+ "rr4---sn-a5msenl7.googlevideo.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-ab5l6nkd.googlevideo.com.": {},
+ "rr4---sn-ab5l6nkd.gvt1.com.": {},
+ "rr4---sn-ab5l6nr6.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrd.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrk.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrk.gvt1.com.": {},
+ "rr4---sn-ab5l6nrl.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrr.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrr.gvt1.com.": {},
+ "rr4---sn-ab5l6nrs.googlevideo.com.": {},
+ "rr4---sn-ab5l6nrs.gvt1.com.": {},
+ "rr4---sn-ab5l6nrz.googlevideo.com.": {},
+ "rr4---sn-ab5sznld.googlevideo.com.": {},
+ "rr4---sn-ab5sznlk.googlevideo.com.": {},
+ "rr4---sn-ab5sznly.googlevideo.com.": {},
+ "rr4---sn-ab5sznz6.googlevideo.com.": {},
+ "rr4---sn-ab5sznz6.gvt1.com.": {},
+ "rr4---sn-ab5sznzd.googlevideo.com.": {},
+ "rr4---sn-ab5sznzd.gvt1.com.": {},
+ "rr4---sn-ab5sznze.googlevideo.com.": {},
+ "rr4---sn-ab5sznzk.googlevideo.com.": {},
+ "rr4---sn-ab5sznzk.gvt1.com.": {},
+ "rr4---sn-ab5sznzl.googlevideo.com.": {},
+ "rr4---sn-ab5sznzl.gvt1.com.": {},
+ "rr4---sn-ab5sznzr.googlevideo.com.": {},
+ "rr4---sn-ab5sznzs.googlevideo.com.": {},
+ "rr4---sn-ab5sznzs.gvt1.com.": {},
+ "rr4---sn-ab5sznzy.googlevideo.com.": {},
+ "rr4---sn-ab5sznzy.gvt1.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.": {},
+ "rr4---sn-aigl6nl7.googlevideo.com.": {},
+ "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-aigl6nzl.googlevideo.com.": {},
+ "rr4---sn-aigl6nzr.googlevideo.com.": {},
+ "rr4---sn-aigl6nzs.googlevideo.com.": {},
+ "rr4---sn-aigzrn76.googlevideo.com.": {},
+ "rr4---sn-aigzrn7d.googlevideo.com.": {},
+ "rr4---sn-aigzrn7e.googlevideo.com.": {},
+ "rr4---sn-aigzrn7k.googlevideo.com.": {},
+ "rr4---sn-aigzrn7l.googlevideo.com.": {},
+ "rr4---sn-aigzrn7s.googlevideo.com.": {},
+ "rr4---sn-aigzrn7z.googlevideo.com.": {},
+ "rr4---sn-aigzrnld.googlevideo.com.": {},
+ "rr4---sn-aigzrnse.googlevideo.com.": {},
+ "rr4---sn-aigzrnsl.googlevideo.com.": {},
+ "rr4---sn-aigzrnsr.googlevideo.com.": {},
+ "rr4---sn-aigzrnss.googlevideo.com.": {},
+ "rr4---sn-aigzrnsz.googlevideo.com.": {},
+ "rr4---sn-aigzrnsz.gvt1.com.": {},
+ "rr4---sn-aigzrnz7.googlevideo.com.": {},
+ "rr4---sn-aigzrnze.googlevideo.com.": {},
+ "rr4---sn-apn7en7e.googlevideo.com.": {},
+ "rr4---sn-apn7en7l.googlevideo.com.": {},
+ "rr4---sn-apn7en7s.googlevideo.com.": {},
+ "rr4---sn-bg5oqxjvh-50nz.googlevideo.com.": {},
+ "rr4---sn-c0q7lnz7.googlevideo.com.": {},
+ "rr4---sn-cvb7lne7.googlevideo.com.": {},
+ "rr4---sn-cvb7lnee.googlevideo.com.": {},
+ "rr4---sn-cvb7lnl7.googlevideo.com.": {},
+ "rr4---sn-cvb7lnls.googlevideo.com.": {},
+ "rr4---sn-cvb7lnlz.googlevideo.com.": {},
+ "rr4---sn-cvb7sn7k.googlevideo.com.": {},
+ "rr4---sn-cvb7sn7r.googlevideo.com.": {},
+ "rr4---sn-h0jeened.googlevideo.com.": {},
+ "rr4---sn-h0jeenl6.googlevideo.com.": {},
+ "rr4---sn-h0jeenld.googlevideo.com.": {},
+ "rr4---sn-h0jeenle.googlevideo.com.": {},
+ "rr4---sn-h0jeln7e.googlevideo.com.": {},
+ "rr4---sn-h0jeln7l.googlevideo.com.": {},
+ "rr4---sn-h0jelne6.googlevideo.com.": {},
+ "rr4---sn-h0jelne7.googlevideo.com.": {},
+ "rr4---sn-h0jelnez.googlevideo.com.": {},
+ "rr4---sn-hgn7rn7y.googlevideo.com.": {},
+ "rr4---sn-hjoj-gq0l.googlevideo.com.": {},
+ "rr4---sn-hjoj-gq0l.gvt1.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-hp57kn6y.googlevideo.com.": {},
+ "rr4---sn-hp57knd6.googlevideo.com.": {},
+ "rr4---sn-hp57kndd.googlevideo.com.": {},
+ "rr4---sn-hp57kndd.gvt1.com.": {},
+ "rr4---sn-hp57kndk.googlevideo.com.": {},
+ "rr4---sn-hp57kndr.googlevideo.com.": {},
+ "rr4---sn-hp57knds.googlevideo.com.": {},
+ "rr4---sn-hp57kndy.googlevideo.com.": {},
+ "rr4---sn-hp57kndz.googlevideo.com.": {},
+ "rr4---sn-hp57kndz.gvt1.com.": {},
+ "rr4---sn-hp57yn7r.googlevideo.com.": {},
+ "rr4---sn-hp57yn7r.gvt1.com.": {},
+ "rr4---sn-hp57yn7y.googlevideo.com.": {},
+ "rr4---sn-hp57yne7.googlevideo.com.": {},
+ "rr4---sn-hp57ynee.googlevideo.com.": {},
+ "rr4---sn-hp57ynl6.googlevideo.com.": {},
+ "rr4---sn-hp57ynl6.gvt1.com.": {},
+ "rr4---sn-hp57ynlr.googlevideo.com.": {},
+ "rr4---sn-hp57ynly.googlevideo.com.": {},
+ "rr4---sn-hp57yns7.googlevideo.com.": {},
+ "rr4---sn-hp57ynse.googlevideo.com.": {},
+ "rr4---sn-hp57ynse.gvt1.com.": {},
+ "rr4---sn-hp57ynsl.googlevideo.com.": {},
+ "rr4---sn-hp57ynss.googlevideo.com.": {},
+ "rr4---sn-hp57ynss.gvt1.com.": {},
+ "rr4---sn-i3b7kn6s.googlevideo.com.": {},
+ "rr4---sn-i3b7knld.googlevideo.com.": {},
+ "rr4---sn-i3b7kns6.googlevideo.com.": {},
+ "rr4---sn-i3b7knsd.googlevideo.com.": {},
+ "rr4---sn-i3b7knse.googlevideo.com.": {},
+ "rr4---sn-i3b7knsl.googlevideo.com.": {},
+ "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-i3belnls.googlevideo.com.": {},
+ "rr4---sn-i3belnlz.googlevideo.com.": {},
+ "rr4---sn-i3bssn7e.googlevideo.com.": {},
+ "rr4---sn-i5f5ppuxa-ioal.googlevideo.com.": {},
+ "rr4---sn-i5h7lnl6.googlevideo.com.": {},
+ "rr4---sn-i5h7lnll.googlevideo.com.": {},
+ "rr4---sn-i5h7lnls.googlevideo.com.": {},
+ "rr4---sn-i5heen7d.googlevideo.com.": {},
+ "rr4---sn-i5heen7r.googlevideo.com.": {},
+ "rr4---sn-i5heen7s.googlevideo.com.": {},
+ "rr4---sn-jn2pgx4pcxg-w5os.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-2iae.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-2ial.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-nh4e.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-nh4l.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-nh4s.googlevideo.com.": {},
+ "rr4---sn-jvhj5nu-nh4z.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-n5oe.gvt1.com.": {},
+ "rr4---sn-jxopj-nh4e.googlevideo.com.": {},
+ "rr4---sn-jxopj-nh4e.gvt1.com.": {},
+ "rr4---sn-n4v7snee.googlevideo.com.": {},
+ "rr4---sn-n4v7sney.googlevideo.com.": {},
+ "rr4---sn-n4v7snl7.googlevideo.com.": {},
+ "rr4---sn-n4v7snl7.gvt1.com.": {},
+ "rr4---sn-n4v7snll.googlevideo.com.": {},
+ "rr4---sn-n4v7snlr.googlevideo.com.": {},
+ "rr4---sn-n4v7snls.googlevideo.com.": {},
+ "rr4---sn-n4v7snly.googlevideo.com.": {},
+ "rr4---sn-n4v7sns7.googlevideo.com.": {},
+ "rr4---sn-n4v7snse.googlevideo.com.": {},
+ "rr4---sn-npoe7ndl.googlevideo.com.": {},
+ "rr4---sn-npoe7nds.googlevideo.com.": {},
+ "rr4---sn-npoe7ne6.googlevideo.com.": {},
+ "rr4---sn-npoe7ne7.googlevideo.com.": {},
+ "rr4---sn-npoe7nek.googlevideo.com.": {},
+ "rr4---sn-npoe7ner.googlevideo.com.": {},
+ "rr4---sn-npoe7nes.googlevideo.com.": {},
+ "rr4---sn-npoe7ney.googlevideo.com.": {},
+ "rr4---sn-npoe7nez.googlevideo.com.": {},
+ "rr4---sn-npoe7nl6.googlevideo.com.": {},
+ "rr4---sn-npoe7nlz.googlevideo.com.": {},
+ "rr4---sn-npoe7ns6.googlevideo.com.": {},
+ "rr4---sn-npoe7ns7.googlevideo.com.": {},
+ "rr4---sn-npoe7nsd.googlevideo.com.": {},
+ "rr4---sn-npoe7nsk.googlevideo.com.": {},
+ "rr4---sn-npoe7nsl.googlevideo.com.": {},
+ "rr4---sn-npoe7nsr.googlevideo.com.": {},
+ "rr4---sn-npoe7nss.googlevideo.com.": {},
+ "rr4---sn-npoe7nsy.googlevideo.com.": {},
+ "rr4---sn-npoe7nz7.googlevideo.com.": {},
+ "rr4---sn-npoeene6.googlevideo.com.": {},
+ "rr4---sn-npoeened.googlevideo.com.": {},
+ "rr4---sn-npoeenee.googlevideo.com.": {},
+ "rr4---sn-npoeenek.googlevideo.com.": {},
+ "rr4---sn-npoeener.googlevideo.com.": {},
+ "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.": {},
+ "rr4---sn-npoeens7.googlevideo.com.": {},
+ "rr4---sn-npoldn76.googlevideo.com.": {},
+ "rr4---sn-npoldn7d.googlevideo.com.": {},
+ "rr4---sn-npoldn7e.googlevideo.com.": {},
+ "rr4---sn-npoldn7l.googlevideo.com.": {},
+ "rr4---sn-npoldn7s.googlevideo.com.": {},
+ "rr4---sn-npoldn7y.googlevideo.com.": {},
+ "rr4---sn-npoldn7z.googlevideo.com.": {},
+ "rr4---sn-npoldne7.googlevideo.com.": {},
+ "rr4---sn-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr4---sn-nv47ln6e.googlevideo.com.": {},
+ "rr4---sn-nv47lns6.googlevideo.com.": {},
+ "rr4---sn-nv47lns7.googlevideo.com.": {},
+ "rr4---sn-nv47lnsy.googlevideo.com.": {},
+ "rr4---sn-nv47zn7r.googlevideo.com.": {},
+ "rr4---sn-nv47zne7.googlevideo.com.": {},
+ "rr4---sn-nv47znee.googlevideo.com.": {},
+ "rr4---sn-nv47znel.googlevideo.com.": {},
+ "rr4---sn-nx57ynse.googlevideo.com.": {},
+ "rr4---sn-nx57ynsk.googlevideo.com.": {},
+ "rr4---sn-nx57ynsl.googlevideo.com.": {},
+ "rr4---sn-nx57ynss.googlevideo.com.": {},
+ "rr4---sn-nx57ynss.gvt1.com.": {},
+ "rr4---sn-nx57ynsz.googlevideo.com.": {},
+ "rr4---sn-nx5s7n76.googlevideo.com.": {},
+ "rr4---sn-nx5s7n76.gvt1.com.": {},
+ "rr4---sn-nx5s7n7d.googlevideo.com.": {},
+ "rr4---sn-nx5s7n7d.gvt1.com.": {},
+ "rr4---sn-nx5s7n7s.googlevideo.com.": {},
+ "rr4---sn-nx5s7n7s.gvt1.com.": {},
+ "rr4---sn-nx5s7n7y.googlevideo.com.": {},
+ "rr4---sn-nx5s7n7z.googlevideo.com.": {},
+ "rr4---sn-nx5s7n7z.gvt1.com.": {},
+ "rr4---sn-nx5s7nee.googlevideo.com.": {},
+ "rr4---sn-nx5s7nee.gvt1.com.": {},
+ "rr4---sn-nx5s7nel.googlevideo.com.": {},
+ "rr4---sn-nx5s7nel.gvt1.com.": {},
+ "rr4---sn-o097znsd.googlevideo.com.": {},
+ "rr4---sn-o097znse.googlevideo.com.": {},
+ "rr4---sn-o097znsk.googlevideo.com.": {},
+ "rr4---sn-o097znsl.googlevideo.com.": {},
+ "rr4---sn-o097znsr.googlevideo.com.": {},
+ "rr4---sn-o097znsr.gvt1.com.": {},
+ "rr4---sn-o097znss.googlevideo.com.": {},
+ "rr4---sn-o097znsz.googlevideo.com.": {},
+ "rr4---sn-o097znz7.googlevideo.com.": {},
+ "rr4---sn-o097znzd.googlevideo.com.": {},
+ "rr4---sn-o097znzd.gvt1.com.": {},
+ "rr4---sn-o097znze.googlevideo.com.": {},
+ "rr4---sn-o097znzk.googlevideo.com.": {},
+ "rr4---sn-o097znzr.googlevideo.com.": {},
+ "rr4---sn-p5qddn76.googlevideo.com.": {},
+ "rr4---sn-p5qddn7d.googlevideo.com.": {},
+ "rr4---sn-p5qddn7d.gvt1.com.": {},
+ "rr4---sn-p5qddn7k.googlevideo.com.": {},
+ "rr4---sn-p5qddn7z.googlevideo.com.": {},
+ "rr4---sn-p5qlsn6l.googlevideo.com.": {},
+ "rr4---sn-p5qlsn76.googlevideo.com.": {},
+ "rr4---sn-p5qlsn7d.googlevideo.com.": {},
+ "rr4---sn-p5qlsn7l.googlevideo.com.": {},
+ "rr4---sn-p5qlsn7s.googlevideo.com.": {},
+ "rr4---sn-p5qlsnd6.googlevideo.com.": {},
+ "rr4---sn-p5qlsndr.googlevideo.com.": {},
+ "rr4---sn-p5qlsndz.googlevideo.com.": {},
+ "rr4---sn-p5qlsnrr.googlevideo.com.": {},
+ "rr4---sn-p5qlsny6.googlevideo.com.": {},
+ "rr4---sn-p5qs7n6d.googlevideo.com.": {},
+ "rr4---sn-p5qs7nsk.googlevideo.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-q4fl6n66.gvt1.com.": {},
+ "rr4---sn-q4fl6n6d.googlevideo.com.": {},
+ "rr4---sn-q4fl6n6d.gvt1.com.": {},
+ "rr4---sn-q4fl6n6r.googlevideo.com.": {},
+ "rr4---sn-q4fl6n6s.googlevideo.com.": {},
+ "rr4---sn-q4fl6n6y.googlevideo.com.": {},
+ "rr4---sn-q4fl6n6y.gvt1.com.": {},
+ "rr4---sn-q4fl6n6z.googlevideo.com.": {},
+ "rr4---sn-q4fl6nd6.googlevideo.com.": {},
+ "rr4---sn-q4fl6nd7.googlevideo.com.": {},
+ "rr4---sn-q4fl6nd7.gvt1.com.": {},
+ "rr4---sn-q4fl6nde.googlevideo.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.": {},
+ "rr4---sn-q4fl6nlz.googlevideo.com.": {},
+ "rr4---sn-q4fl6nlz.gvt1.com.": {},
+ "rr4---sn-q4fl6ns6.googlevideo.com.": {},
+ "rr4---sn-q4fl6ns7.googlevideo.com.": {},
+ "rr4---sn-q4fl6nsd.googlevideo.com.": {},
+ "rr4---sn-q4fl6nsd.gvt1.com.": {},
+ "rr4---sn-q4fl6nsk.googlevideo.com.": {},
+ "rr4---sn-q4fl6nsk.gvt1.com.": {},
+ "rr4---sn-q4fl6nsl.googlevideo.com.": {},
+ "rr4---sn-q4fl6nsr.googlevideo.com.": {},
+ "rr4---sn-q4fl6nss.googlevideo.com.": {},
+ "rr4---sn-q4fl6nsy.googlevideo.com.": {},
+ "rr4---sn-q4fl6nz6.googlevideo.com.": {},
+ "rr4---sn-q4fl6nz7.googlevideo.com.": {},
+ "rr4---sn-q4fl6nzy.googlevideo.com.": {},
+ "rr4---sn-q4flrn7k.googlevideo.com.": {},
+ "rr4---sn-q4flrn7r.googlevideo.com.": {},
+ "rr4---sn-q4flrn7y.googlevideo.com.": {},
+ "rr4---sn-q4flrn7y.gvt1.com.": {},
+ "rr4---sn-q4flrne6.googlevideo.com.": {},
+ "rr4---sn-q4flrne7.googlevideo.com.": {},
+ "rr4---sn-q4flrnee.googlevideo.com.": {},
+ "rr4---sn-q4flrnek.googlevideo.com.": {},
+ "rr4---sn-q4flrnek.gvt1.com.": {},
+ "rr4---sn-q4flrnel.googlevideo.com.": {},
+ "rr4---sn-q4flrnel.gvt1.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-q4flrnl6.googlevideo.com.": {},
+ "rr4---sn-q4flrnl6.gvt1.com.": {},
+ "rr4---sn-q4flrnl7.googlevideo.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-q4flrnsl.gvt1.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-q4fzen7s.googlevideo.com.": {},
+ "rr4---sn-q4fzen7y.googlevideo.com.": {},
+ "rr4---sn-q4fzene7.googlevideo.com.": {},
+ "rr4---sn-q4fzene7.gvt1.com.": {},
+ "rr4---sn-q4fzenee.googlevideo.com.": {},
+ "rr4---sn-qxo7rn7k.googlevideo.com.": {},
+ "rr4---sn-qxo7rn7r.googlevideo.com.": {},
+ "rr4---sn-qxo7rn7y.googlevideo.com.": {},
+ "rr4---sn-qxoedn7k.googlevideo.com.": {},
+ "rr4---sn-qxoedne7.googlevideo.com.": {},
+ "rr4---sn-qxoednee.googlevideo.com.": {},
+ "rr4---sn-u1hp55-5c.googlevideo.com.": {},
+ "rr4---sn-uhvcpaxoa-5hne.googlevideo.com.": {},
+ "rr4---sn-uhvcpaxoa-guhe.googlevideo.com.": {},
+ "rr4---sn-v5goxu-jhil.googlevideo.com.": {},
+ "rr4---sn-v5goxu-jhil.gvt1.com.": {},
+ "rr4---sn-vgqskn66.googlevideo.com.": {},
+ "rr4---sn-vgqskn67.googlevideo.com.": {},
+ "rr4---sn-vgqskn67.gvt1.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.": {},
+ "rr4---sn-vgqskne6.gvt1.com.": {},
+ "rr4---sn-vgqskned.googlevideo.com.": {},
+ "rr4---sn-vgqsknek.googlevideo.com.": {},
+ "rr4---sn-vgqsknes.googlevideo.com.": {},
+ "rr4---sn-vgqsknez.googlevideo.com.": {},
+ "rr4---sn-vgqsknld.googlevideo.com.": {},
+ "rr4---sn-vgqsknlk.googlevideo.com.": {},
+ "rr4---sn-vgqsknlk.gvt1.com.": {},
+ "rr4---sn-vgqsknll.googlevideo.com.": {},
+ "rr4---sn-vgqsknlr.googlevideo.com.": {},
+ "rr4---sn-vgqsknlr.gvt1.com.": {},
+ "rr4---sn-vgqsknls.googlevideo.com.": {},
+ "rr4---sn-vgqsknls.gvt1.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-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-vgqsrn66.googlevideo.com.": {},
+ "rr4---sn-vgqsrn66.gvt1.com.": {},
+ "rr4---sn-vgqsrn67.googlevideo.com.": {},
+ "rr4---sn-vgqsrn67.gvt1.com.": {},
+ "rr4---sn-vgqsrn6e.googlevideo.com.": {},
+ "rr4---sn-vgqsrn6l.googlevideo.com.": {},
+ "rr4---sn-vgqsrn6z.googlevideo.com.": {},
+ "rr4---sn-vgqsrn6z.gvt1.com.": {},
+ "rr4---sn-vgqsrne6.googlevideo.com.": {},
+ "rr4---sn-vgqsrne6.gvt1.com.": {},
+ "rr4---sn-vgqsrned.googlevideo.com.": {},
+ "rr4---sn-vgqsrnek.googlevideo.com.": {},
+ "rr4---sn-vgqsrnek.gvt1.com.": {},
+ "rr4---sn-vgqsrnes.googlevideo.com.": {},
+ "rr4---sn-vgqsrnez.googlevideo.com.": {},
+ "rr4---sn-vgqsrnl6.googlevideo.com.": {},
+ "rr4---sn-vgqsrnld.googlevideo.com.": {},
+ "rr4---sn-vgqsrnld.gvt1.com.": {},
+ "rr4---sn-vgqsrnlk.googlevideo.com.": {},
+ "rr4---sn-vgqsrnll.googlevideo.com.": {},
+ "rr4---sn-vgqsrnls.googlevideo.com.": {},
+ "rr4---sn-vgqsrns6.googlevideo.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-hgn7rn7y.googlevideo.com.": {},
+ "rr4.sn-q4fl6nd6.googlevideo.com.": {},
+ "rr4.sn-q4fl6nz7.googlevideo.com.": {},
+ "rr4.sn-q4flrnl7.googlevideo.com.": {},
+ "rr5---sn-0nnpbo5a-bggl.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-2imeyn7k.gvt1.com.": {},
+ "rr5---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr5---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr5---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr5---sn-2vgu0b5auxaxjvh-v2vl.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-42u-nbozs.googlevideo.com.": {},
+ "rr5---sn-4g5e6ns6.googlevideo.com.": {},
+ "rr5---sn-4g5e6ns7.googlevideo.com.": {},
+ "rr5---sn-4g5e6nsd.googlevideo.com.": {},
+ "rr5---sn-4g5e6nsk.googlevideo.com.": {},
+ "rr5---sn-4g5e6nsr.googlevideo.com.": {},
+ "rr5---sn-4g5e6nss.googlevideo.com.": {},
+ "rr5---sn-4g5e6nsy.googlevideo.com.": {},
+ "rr5---sn-4g5e6nsz.googlevideo.com.": {},
+ "rr5---sn-4g5e6nz7.googlevideo.com.": {},
+ "rr5---sn-4g5e6nze.googlevideo.com.": {},
+ "rr5---sn-4g5e6nzl.googlevideo.com.": {},
+ "rr5---sn-4g5e6nzs.googlevideo.com.": {},
+ "rr5---sn-4g5e6nzz.googlevideo.com.": {},
+ "rr5---sn-4g5edn6k.googlevideo.com.": {},
+ "rr5---sn-4g5edn6r.googlevideo.com.": {},
+ "rr5---sn-4g5edn6y.googlevideo.com.": {},
+ "rr5---sn-4g5ednd7.googlevideo.com.": {},
+ "rr5---sn-4g5edndd.googlevideo.com.": {},
+ "rr5---sn-4g5ednde.googlevideo.com.": {},
+ "rr5---sn-4g5edndk.googlevideo.com.": {},
+ "rr5---sn-4g5edndl.googlevideo.com.": {},
+ "rr5---sn-4g5edndr.googlevideo.com.": {},
+ "rr5---sn-4g5ednds.googlevideo.com.": {},
+ "rr5---sn-4g5edndy.googlevideo.com.": {},
+ "rr5---sn-4g5edndz.googlevideo.com.": {},
+ "rr5---sn-4g5ednkl.googlevideo.com.": {},
+ "rr5---sn-4g5ednld.googlevideo.com.": {},
+ "rr5---sn-4g5ednly.googlevideo.com.": {},
+ "rr5---sn-4g5edns6.googlevideo.com.": {},
+ "rr5---sn-4g5edns7.googlevideo.com.": {},
+ "rr5---sn-4g5ednsd.googlevideo.com.": {},
+ "rr5---sn-4g5ednse.googlevideo.com.": {},
+ "rr5---sn-4g5ednsk.googlevideo.com.": {},
+ "rr5---sn-4g5ednsl.googlevideo.com.": {},
+ "rr5---sn-4g5ednsr.googlevideo.com.": {},
+ "rr5---sn-4g5ednsy.googlevideo.com.": {},
+ "rr5---sn-4g5ednsz.googlevideo.com.": {},
+ "rr5---sn-4g5ednz7.googlevideo.com.": {},
+ "rr5---sn-4g5lzne6.googlevideo.com.": {},
+ "rr5---sn-4g5lzned.googlevideo.com.": {},
+ "rr5---sn-4g5lznek.googlevideo.com.": {},
+ "rr5---sn-4g5lzner.googlevideo.com.": {},
+ "rr5---sn-4g5lznes.googlevideo.com.": {},
+ "rr5---sn-4g5lzney.googlevideo.com.": {},
+ "rr5---sn-4g5lznez.googlevideo.com.": {},
+ "rr5---sn-4g5lznl6.googlevideo.com.": {},
+ "rr5---sn-4g5lznl7.googlevideo.com.": {},
+ "rr5---sn-4g5lznle.googlevideo.com.": {},
+ "rr5---sn-4g5lznls.googlevideo.com.": {},
+ "rr5---sn-4g5lznlz.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-5hne6nsy.gvt1.com.": {},
+ "rr5---sn-5hne6nsz.googlevideo.com.": {},
+ "rr5---sn-5hne6nz6.googlevideo.com.": {},
+ "rr5---sn-5hne6nzd.googlevideo.com.": {},
+ "rr5---sn-5hne6nzk.googlevideo.com.": {},
+ "rr5---sn-5hne6nzs.googlevideo.com.": {},
+ "rr5---sn-5hne6nzy.googlevideo.com.": {},
+ "rr5---sn-5hnednss.googlevideo.com.": {},
+ "rr5---sn-5hnednsz.googlevideo.com.": {},
+ "rr5---sn-5hnekn76.googlevideo.com.": {},
+ "rr5---sn-5hnekn7d.googlevideo.com.": {},
+ "rr5---sn-5hnekn7k.googlevideo.com.": {},
+ "rr5---sn-5hnekn7l.googlevideo.com.": {},
+ "rr5---sn-5hnekn7s.googlevideo.com.": {},
+ "rr5---sn-5hnekn7z.googlevideo.com.": {},
+ "rr5---sn-5hneknee.googlevideo.com.": {},
+ "rr5---sn-5hneknek.googlevideo.com.": {},
+ "rr5---sn-5hneknek.gvt1.com.": {},
+ "rr5---sn-5hneknes.googlevideo.com.": {},
+ "rr5---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr5---sn-5uaezndd.googlevideo.com.": {},
+ "rr5---sn-5uaezne6.googlevideo.com.": {},
+ "rr5---sn-5uaezned.googlevideo.com.": {},
+ "rr5---sn-5uaeznez.googlevideo.com.": {},
+ "rr5---sn-5uaeznl6.googlevideo.com.": {},
+ "rr5---sn-5uaeznld.googlevideo.com.": {},
+ "rr5---sn-5uaeznls.googlevideo.com.": {},
+ "rr5---sn-5uaeznly.googlevideo.com.": {},
+ "rr5---sn-5uaeznlz.googlevideo.com.": {},
+ "rr5---sn-5uaeznrz.googlevideo.com.": {},
+ "rr5---sn-5uaezns7.googlevideo.com.": {},
+ "rr5---sn-5uaeznse.googlevideo.com.": {},
+ "rr5---sn-5uaeznsl.googlevideo.com.": {},
+ "rr5---sn-5uaeznss.googlevideo.com.": {},
+ "rr5---sn-5uaezny6.googlevideo.com.": {},
+ "rr5---sn-5uaeznys.googlevideo.com.": {},
+ "rr5---sn-5uaeznyz.googlevideo.com.": {},
+ "rr5---sn-5uaeznze.googlevideo.com.": {},
+ "rr5---sn-5ualdnll.googlevideo.com.": {},
+ "rr5---sn-5ualdnlr.googlevideo.com.": {},
+ "rr5---sn-5ualdnls.googlevideo.com.": {},
+ "rr5---sn-5ualdns6.googlevideo.com.": {},
+ "rr5---sn-5ualdns7.googlevideo.com.": {},
+ "rr5---sn-5ualdnsd.googlevideo.com.": {},
+ "rr5---sn-5ualdnse.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-i5o6k.googlevideo.com.": {},
+ "rr5---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr5---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr5---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-2iay.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-ab5l.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-ab5s.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-nh4e.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-p5ie.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-vgqe.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-xfge.googlevideo.com.": {},
+ "rr5---sn-8xgp1vo-xfgl.googlevideo.com.": {},
+ "rr5---sn-9gv76n7e.googlevideo.com.": {},
+ "rr5---sn-9gv76n7s.googlevideo.com.": {},
+ "rr5---sn-9gv76n7z.googlevideo.com.": {},
+ "rr5---sn-9gv7ene6.googlevideo.com.": {},
+ "rr5---sn-9gv7ened.googlevideo.com.": {},
+ "rr5---sn-9gv7zn7e.googlevideo.com.": {},
+ "rr5---sn-9gv7zn7r.googlevideo.com.": {},
+ "rr5---sn-a5m7lnl6.googlevideo.com.": {},
+ "rr5---sn-a5m7lnl6.gvt1.com.": {},
+ "rr5---sn-a5m7lnld.googlevideo.com.": {},
+ "rr5---sn-a5mekn6d.googlevideo.com.": {},
+ "rr5---sn-a5mekn6k.googlevideo.com.": {},
+ "rr5---sn-a5mekn6l.googlevideo.com.": {},
+ "rr5---sn-a5mekn6r.googlevideo.com.": {},
+ "rr5---sn-a5mekn6r.gvt1.com.": {},
+ "rr5---sn-a5mekn6s.googlevideo.com.": {},
+ "rr5---sn-a5mekn6z.googlevideo.com.": {},
+ "rr5---sn-a5mekn6z.gvt1.com.": {},
+ "rr5---sn-a5meknd6.googlevideo.com.": {},
+ "rr5---sn-a5meknd6.gvt1.com.": {},
+ "rr5---sn-a5meknde.googlevideo.com.": {},
+ "rr5---sn-a5mekndl.googlevideo.com.": {},
+ "rr5---sn-a5mekndl.gvt1.com.": {},
+ "rr5---sn-a5mekndz.googlevideo.com.": {},
+ "rr5---sn-a5mekndz.gvt1.com.": {},
+ "rr5---sn-a5meknsd.googlevideo.com.": {},
+ "rr5---sn-a5meknsd.gvt1.com.": {},
+ "rr5---sn-a5meknsy.googlevideo.com.": {},
+ "rr5---sn-a5meknsy.gvt1.com.": {},
+ "rr5---sn-a5meknzk.googlevideo.com.": {},
+ "rr5---sn-a5meknzl.googlevideo.com.": {},
+ "rr5---sn-a5meknzr.googlevideo.com.": {},
+ "rr5---sn-a5meknzs.googlevideo.com.": {},
+ "rr5---sn-a5mlrnek.googlevideo.com.": {},
+ "rr5---sn-a5mlrnl6.googlevideo.com.": {},
+ "rr5---sn-a5mlrnl6.gvt1.com.": {},
+ "rr5---sn-a5mlrnll.googlevideo.com.": {},
+ "rr5---sn-a5mlrnll.gvt1.com.": {},
+ "rr5---sn-a5mlrnls.googlevideo.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-a5msenek.gvt1.com.": {},
+ "rr5---sn-a5msener.googlevideo.com.": {},
+ "rr5---sn-a5msener.gvt1.com.": {},
+ "rr5---sn-a5msenes.googlevideo.com.": {},
+ "rr5---sn-a5msenl7.googlevideo.com.": {},
+ "rr5---sn-a5msenl7.gvt1.com.": {},
+ "rr5---sn-a5msenle.googlevideo.com.": {},
+ "rr5---sn-a5msenle.gvt1.com.": {},
+ "rr5---sn-a5msenll.googlevideo.com.": {},
+ "rr5---sn-ab5l6ndr.googlevideo.com.": {},
+ "rr5---sn-ab5l6ndy.googlevideo.com.": {},
+ "rr5---sn-ab5l6nk6.googlevideo.com.": {},
+ "rr5---sn-ab5l6nk6.gvt1.com.": {},
+ "rr5---sn-ab5l6nkd.googlevideo.com.": {},
+ "rr5---sn-ab5l6nr6.googlevideo.com.": {},
+ "rr5---sn-ab5l6nr6.gvt1.com.": {},
+ "rr5---sn-ab5l6nrd.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrk.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrl.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrl.gvt1.com.": {},
+ "rr5---sn-ab5l6nrr.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrs.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrs.gvt1.com.": {},
+ "rr5---sn-ab5l6nrz.googlevideo.com.": {},
+ "rr5---sn-ab5l6nrz.gvt1.com.": {},
+ "rr5---sn-ab5sznld.googlevideo.com.": {},
+ "rr5---sn-ab5sznld.gvt1.com.": {},
+ "rr5---sn-ab5sznlk.googlevideo.com.": {},
+ "rr5---sn-ab5sznly.googlevideo.com.": {},
+ "rr5---sn-ab5sznz6.googlevideo.com.": {},
+ "rr5---sn-ab5sznz6.gvt1.com.": {},
+ "rr5---sn-ab5sznzd.googlevideo.com.": {},
+ "rr5---sn-ab5sznze.googlevideo.com.": {},
+ "rr5---sn-ab5sznze.gvt1.com.": {},
+ "rr5---sn-ab5sznzk.googlevideo.com.": {},
+ "rr5---sn-ab5sznzl.googlevideo.com.": {},
+ "rr5---sn-ab5sznzl.gvt1.com.": {},
+ "rr5---sn-ab5sznzr.googlevideo.com.": {},
+ "rr5---sn-ab5sznzr.gvt1.com.": {},
+ "rr5---sn-ab5sznzs.googlevideo.com.": {},
+ "rr5---sn-ab5sznzs.gvt1.com.": {},
+ "rr5---sn-ab5sznzy.googlevideo.com.": {},
+ "rr5---sn-ab5sznzz.googlevideo.com.": {},
+ "rr5---sn-aigl6n6s.googlevideo.com.": {},
+ "rr5---sn-aigl6ned.googlevideo.com.": {},
+ "rr5---sn-aigl6nek.googlevideo.com.": {},
+ "rr5---sn-aigl6ner.googlevideo.com.": {},
+ "rr5---sn-aigl6ney.googlevideo.com.": {},
+ "rr5---sn-aigl6nl7.googlevideo.com.": {},
+ "rr5---sn-aigl6nl7.gvt1.com.": {},
+ "rr5---sn-aigl6ns6.googlevideo.com.": {},
+ "rr5---sn-aigl6nsd.googlevideo.com.": {},
+ "rr5---sn-aigl6nsd.gvt1.com.": {},
+ "rr5---sn-aigl6nsk.googlevideo.com.": {},
+ "rr5---sn-aigl6nsr.googlevideo.com.": {},
+ "rr5---sn-aigl6nz7.googlevideo.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-aigzrn76.googlevideo.com.": {},
+ "rr5---sn-aigzrn7d.googlevideo.com.": {},
+ "rr5---sn-aigzrn7e.googlevideo.com.": {},
+ "rr5---sn-aigzrn7k.googlevideo.com.": {},
+ "rr5---sn-aigzrn7l.googlevideo.com.": {},
+ "rr5---sn-aigzrn7s.googlevideo.com.": {},
+ "rr5---sn-aigzrn7z.googlevideo.com.": {},
+ "rr5---sn-aigzrne7.googlevideo.com.": {},
+ "rr5---sn-aigzrnld.googlevideo.com.": {},
+ "rr5---sn-aigzrnse.googlevideo.com.": {},
+ "rr5---sn-aigzrnsl.googlevideo.com.": {},
+ "rr5---sn-aigzrnsr.googlevideo.com.": {},
+ "rr5---sn-aigzrnss.googlevideo.com.": {},
+ "rr5---sn-aigzrnsz.googlevideo.com.": {},
+ "rr5---sn-aigzrnsz.gvt1.com.": {},
+ "rr5---sn-aigzrnz7.googlevideo.com.": {},
+ "rr5---sn-aigzrnz7.gvt1.com.": {},
+ "rr5---sn-aigzrnze.googlevideo.com.": {},
+ "rr5---sn-apn7en7e.googlevideo.com.": {},
+ "rr5---sn-apn7en7l.googlevideo.com.": {},
+ "rr5---sn-apn7en7s.googlevideo.com.": {},
+ "rr5---sn-c0q7lnz7.googlevideo.com.": {},
+ "rr5---sn-cvb7lne7.googlevideo.com.": {},
+ "rr5---sn-cvb7lnl7.googlevideo.com.": {},
+ "rr5---sn-cvb7lnls.googlevideo.com.": {},
+ "rr5---sn-cvb7lnlz.googlevideo.com.": {},
+ "rr5---sn-cvb7sn7k.googlevideo.com.": {},
+ "rr5---sn-cvb7sn7r.googlevideo.com.": {},
+ "rr5---sn-h0jeened.googlevideo.com.": {},
+ "rr5---sn-h0jeenek.googlevideo.com.": {},
+ "rr5---sn-h0jeener.googlevideo.com.": {},
+ "rr5---sn-h0jeenl6.googlevideo.com.": {},
+ "rr5---sn-h0jeenld.googlevideo.com.": {},
+ "rr5---sn-h0jeenle.googlevideo.com.": {},
+ "rr5---sn-h0jeln7e.googlevideo.com.": {},
+ "rr5---sn-h0jeln7l.googlevideo.com.": {},
+ "rr5---sn-h0jelne6.googlevideo.com.": {},
+ "rr5---sn-h0jelne7.googlevideo.com.": {},
+ "rr5---sn-h0jelnes.googlevideo.com.": {},
+ "rr5---sn-h0jelnez.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-hp57kndk.googlevideo.com.": {},
+ "rr5---sn-hp57kndr.googlevideo.com.": {},
+ "rr5---sn-hp57knds.googlevideo.com.": {},
+ "rr5---sn-hp57kndy.googlevideo.com.": {},
+ "rr5---sn-hp57kndy.gvt1.com.": {},
+ "rr5---sn-hp57kndz.googlevideo.com.": {},
+ "rr5---sn-hp57yn7y.googlevideo.com.": {},
+ "rr5---sn-hp57yne7.googlevideo.com.": {},
+ "rr5---sn-hp57ynee.googlevideo.com.": {},
+ "rr5---sn-hp57ynl6.googlevideo.com.": {},
+ "rr5---sn-hp57ynlr.googlevideo.com.": {},
+ "rr5---sn-hp57ynly.googlevideo.com.": {},
+ "rr5---sn-hp57ynly.gvt1.com.": {},
+ "rr5---sn-hp57yns7.googlevideo.com.": {},
+ "rr5---sn-hp57yns7.gvt1.com.": {},
+ "rr5---sn-hp57ynse.googlevideo.com.": {},
+ "rr5---sn-hp57ynsl.googlevideo.com.": {},
+ "rr5---sn-hp57ynsl.gvt1.com.": {},
+ "rr5---sn-hp57ynss.googlevideo.com.": {},
+ "rr5---sn-i3b7kn6s.googlevideo.com.": {},
+ "rr5---sn-i3b7knld.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-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-i5h7lner.googlevideo.com.": {},
+ "rr5---sn-i5h7lnll.googlevideo.com.": {},
+ "rr5---sn-i5h7lnls.googlevideo.com.": {},
+ "rr5---sn-i5heen7r.googlevideo.com.": {},
+ "rr5---sn-i5heen7s.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-nh4z.googlevideo.com.": {},
+ "rr5---sn-jvhj5nu-qufe.googlevideo.com.": {},
+ "rr5---sn-jvhj5nu-qufl.googlevideo.com.": {},
+ "rr5---sn-jvhj5nu-qufs.googlevideo.com.": {},
+ "rr5---sn-jxopj-n5oe.googlevideo.com.": {},
+ "rr5---sn-jxopj-n5oe.gvt1.com.": {},
+ "rr5---sn-jxopj-nh4e.googlevideo.com.": {},
+ "rr5---sn-jxopj-nh4e.gvt1.com.": {},
+ "rr5---sn-n4v7snee.googlevideo.com.": {},
+ "rr5---sn-n4v7sney.googlevideo.com.": {},
+ "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-n4v7sns7.gvt1.com.": {},
+ "rr5---sn-n4v7snse.googlevideo.com.": {},
+ "rr5---sn-npoe7ndl.googlevideo.com.": {},
+ "rr5---sn-npoe7nds.googlevideo.com.": {},
+ "rr5---sn-npoe7ne6.googlevideo.com.": {},
+ "rr5---sn-npoe7ne7.googlevideo.com.": {},
+ "rr5---sn-npoe7ned.googlevideo.com.": {},
+ "rr5---sn-npoe7nek.googlevideo.com.": {},
+ "rr5---sn-npoe7ner.googlevideo.com.": {},
+ "rr5---sn-npoe7nes.googlevideo.com.": {},
+ "rr5---sn-npoe7ney.googlevideo.com.": {},
+ "rr5---sn-npoe7nez.googlevideo.com.": {},
+ "rr5---sn-npoe7nl6.googlevideo.com.": {},
+ "rr5---sn-npoe7nlz.googlevideo.com.": {},
+ "rr5---sn-npoe7ns6.googlevideo.com.": {},
+ "rr5---sn-npoe7ns7.googlevideo.com.": {},
+ "rr5---sn-npoe7nsd.googlevideo.com.": {},
+ "rr5---sn-npoe7nsk.googlevideo.com.": {},
+ "rr5---sn-npoe7nsr.googlevideo.com.": {},
+ "rr5---sn-npoe7nss.googlevideo.com.": {},
+ "rr5---sn-npoe7nsy.googlevideo.com.": {},
+ "rr5---sn-npoe7nz7.googlevideo.com.": {},
+ "rr5---sn-npoeene6.googlevideo.com.": {},
+ "rr5---sn-npoeened.googlevideo.com.": {},
+ "rr5---sn-npoeenee.googlevideo.com.": {},
+ "rr5---sn-npoeenek.googlevideo.com.": {},
+ "rr5---sn-npoeener.googlevideo.com.": {},
+ "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-npoeens7.googlevideo.com.": {},
+ "rr5---sn-npoldn76.googlevideo.com.": {},
+ "rr5---sn-npoldn7d.googlevideo.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-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr5---sn-nv47ln6e.googlevideo.com.": {},
+ "rr5---sn-nv47lnly.googlevideo.com.": {},
+ "rr5---sn-nv47lns6.googlevideo.com.": {},
+ "rr5---sn-nv47zn7r.googlevideo.com.": {},
+ "rr5---sn-nv47zn7y.googlevideo.com.": {},
+ "rr5---sn-nv47zne7.googlevideo.com.": {},
+ "rr5---sn-nv47znee.googlevideo.com.": {},
+ "rr5---sn-nv47znel.googlevideo.com.": {},
+ "rr5---sn-nx57ynsd.googlevideo.com.": {},
+ "rr5---sn-nx57ynsd.gvt1.com.": {},
+ "rr5---sn-nx57ynse.googlevideo.com.": {},
+ "rr5---sn-nx57ynsk.googlevideo.com.": {},
+ "rr5---sn-nx57ynsl.googlevideo.com.": {},
+ "rr5---sn-nx57ynss.googlevideo.com.": {},
+ "rr5---sn-nx57ynss.gvt1.com.": {},
+ "rr5---sn-nx57ynsz.googlevideo.com.": {},
+ "rr5---sn-nx57ynsz.gvt1.com.": {},
+ "rr5---sn-nx5s7n76.googlevideo.com.": {},
+ "rr5---sn-nx5s7n76.gvt1.com.": {},
+ "rr5---sn-nx5s7n7d.googlevideo.com.": {},
+ "rr5---sn-nx5s7n7d.gvt1.com.": {},
+ "rr5---sn-nx5s7n7s.googlevideo.com.": {},
+ "rr5---sn-nx5s7n7y.googlevideo.com.": {},
+ "rr5---sn-nx5s7n7y.gvt1.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.": {},
+ "rr5---sn-o097znsl.googlevideo.com.": {},
+ "rr5---sn-o097znsr.googlevideo.com.": {},
+ "rr5---sn-o097znsr.gvt1.com.": {},
+ "rr5---sn-o097znss.googlevideo.com.": {},
+ "rr5---sn-o097znsz.googlevideo.com.": {},
+ "rr5---sn-o097znz7.googlevideo.com.": {},
+ "rr5---sn-o097znzd.googlevideo.com.": {},
+ "rr5---sn-o097znze.googlevideo.com.": {},
+ "rr5---sn-o097znzk.googlevideo.com.": {},
+ "rr5---sn-o097znzr.googlevideo.com.": {},
+ "rr5---sn-p5qddn76.googlevideo.com.": {},
+ "rr5---sn-p5qddn7d.googlevideo.com.": {},
+ "rr5---sn-p5qddn7k.googlevideo.com.": {},
+ "rr5---sn-p5qddn7z.googlevideo.com.": {},
+ "rr5---sn-p5qddn7z.gvt1.com.": {},
+ "rr5---sn-p5qlsn6l.googlevideo.com.": {},
+ "rr5---sn-p5qlsn76.googlevideo.com.": {},
+ "rr5---sn-p5qlsn7d.googlevideo.com.": {},
+ "rr5---sn-p5qlsn7l.googlevideo.com.": {},
+ "rr5---sn-p5qlsn7s.googlevideo.com.": {},
+ "rr5---sn-p5qlsnd6.googlevideo.com.": {},
+ "rr5---sn-p5qlsndd.googlevideo.com.": {},
+ "rr5---sn-p5qlsndz.googlevideo.com.": {},
+ "rr5---sn-p5qlsny6.googlevideo.com.": {},
+ "rr5---sn-p5qs7n6d.googlevideo.com.": {},
+ "rr5---sn-p5qs7nsk.googlevideo.com.": {},
+ "rr5---sn-p5qs7nsr.googlevideo.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-q4fl6n6d.gvt1.com.": {},
+ "rr5---sn-q4fl6n6s.googlevideo.com.": {},
+ "rr5---sn-q4fl6n6y.googlevideo.com.": {},
+ "rr5---sn-q4fl6n6y.gvt1.com.": {},
+ "rr5---sn-q4fl6n6z.googlevideo.com.": {},
+ "rr5---sn-q4fl6n6z.gvt1.com.": {},
+ "rr5---sn-q4fl6nd6.googlevideo.com.": {},
+ "rr5---sn-q4fl6nd6.gvt1.com.": {},
+ "rr5---sn-q4fl6nd7.googlevideo.com.": {},
+ "rr5---sn-q4fl6nd7.gvt1.com.": {},
+ "rr5---sn-q4fl6nde.googlevideo.com.": {},
+ "rr5---sn-q4fl6ndl.googlevideo.com.": {},
+ "rr5---sn-q4fl6ndl.gvt1.com.": {},
+ "rr5---sn-q4fl6nds.googlevideo.com.": {},
+ "rr5---sn-q4fl6nds.gvt1.com.": {},
+ "rr5---sn-q4fl6ndz.googlevideo.com.": {},
+ "rr5---sn-q4fl6nlz.googlevideo.com.": {},
+ "rr5---sn-q4fl6ns6.googlevideo.com.": {},
+ "rr5---sn-q4fl6ns6.gvt1.com.": {},
+ "rr5---sn-q4fl6ns7.googlevideo.com.": {},
+ "rr5---sn-q4fl6ns7.gvt1.com.": {},
+ "rr5---sn-q4fl6nsd.googlevideo.com.": {},
+ "rr5---sn-q4fl6nsd.gvt1.com.": {},
+ "rr5---sn-q4fl6nsk.googlevideo.com.": {},
+ "rr5---sn-q4fl6nsk.gvt1.com.": {},
+ "rr5---sn-q4fl6nsl.googlevideo.com.": {},
+ "rr5---sn-q4fl6nsr.googlevideo.com.": {},
+ "rr5---sn-q4fl6nss.googlevideo.com.": {},
+ "rr5---sn-q4fl6nsy.googlevideo.com.": {},
+ "rr5---sn-q4fl6nsy.gvt1.com.": {},
+ "rr5---sn-q4fl6nz6.googlevideo.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-q4flrn7y.googlevideo.com.": {},
+ "rr5---sn-q4flrne6.googlevideo.com.": {},
+ "rr5---sn-q4flrne7.googlevideo.com.": {},
+ "rr5---sn-q4flrnee.googlevideo.com.": {},
+ "rr5---sn-q4flrnee.gvt1.com.": {},
+ "rr5---sn-q4flrnek.googlevideo.com.": {},
+ "rr5---sn-q4flrnel.googlevideo.com.": {},
+ "rr5---sn-q4flrnel.gvt1.com.": {},
+ "rr5---sn-q4flrner.googlevideo.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-q4flrnl6.gvt1.com.": {},
+ "rr5---sn-q4flrnl7.googlevideo.com.": {},
+ "rr5---sn-q4flrnl7.gvt1.com.": {},
+ "rr5---sn-q4flrnld.googlevideo.com.": {},
+ "rr5---sn-q4flrnld.gvt1.com.": {},
+ "rr5---sn-q4flrnle.googlevideo.com.": {},
+ "rr5---sn-q4flrnle.gvt1.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-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-q4fzen7s.googlevideo.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-q4fzenee.gvt1.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-vgqskn66.googlevideo.com.": {},
+ "rr5---sn-vgqskn66.gvt1.com.": {},
+ "rr5---sn-vgqskn67.googlevideo.com.": {},
+ "rr5---sn-vgqskn67.gvt1.com.": {},
+ "rr5---sn-vgqskn6d.googlevideo.com.": {},
+ "rr5---sn-vgqskn6s.googlevideo.com.": {},
+ "rr5---sn-vgqskn6z.googlevideo.com.": {},
+ "rr5---sn-vgqskne6.googlevideo.com.": {},
+ "rr5---sn-vgqskned.googlevideo.com.": {},
+ "rr5---sn-vgqsknek.googlevideo.com.": {},
+ "rr5---sn-vgqsknes.googlevideo.com.": {},
+ "rr5---sn-vgqsknes.gvt1.com.": {},
+ "rr5---sn-vgqsknez.googlevideo.com.": {},
+ "rr5---sn-vgqsknez.gvt1.com.": {},
+ "rr5---sn-vgqsknld.googlevideo.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-vgqsknls.gvt1.com.": {},
+ "rr5---sn-vgqsknly.googlevideo.com.": {},
+ "rr5---sn-vgqsknlz.googlevideo.com.": {},
+ "rr5---sn-vgqskns7.googlevideo.com.": {},
+ "rr5---sn-vgqsknse.googlevideo.com.": {},
+ "rr5---sn-vgqsknsk.googlevideo.com.": {},
+ "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-vgqsknze.gvt1.com.": {},
+ "rr5---sn-vgqsknzk.googlevideo.com.": {},
+ "rr5---sn-vgqsknzk.gvt1.com.": {},
+ "rr5---sn-vgqsknzl.googlevideo.com.": {},
+ "rr5---sn-vgqsknzr.googlevideo.com.": {},
+ "rr5---sn-vgqsknzr.gvt1.com.": {},
+ "rr5---sn-vgqsknzs.googlevideo.com.": {},
+ "rr5---sn-vgqsknzs.gvt1.com.": {},
+ "rr5---sn-vgqsknzy.googlevideo.com.": {},
+ "rr5---sn-vgqsknzz.googlevideo.com.": {},
+ "rr5---sn-vgqsrn66.googlevideo.com.": {},
+ "rr5---sn-vgqsrn67.googlevideo.com.": {},
+ "rr5---sn-vgqsrn6e.googlevideo.com.": {},
+ "rr5---sn-vgqsrn6e.gvt1.com.": {},
+ "rr5---sn-vgqsrn6l.googlevideo.com.": {},
+ "rr5---sn-vgqsrn6z.googlevideo.com.": {},
+ "rr5---sn-vgqsrn6z.gvt1.com.": {},
+ "rr5---sn-vgqsrne6.googlevideo.com.": {},
+ "rr5---sn-vgqsrnek.googlevideo.com.": {},
+ "rr5---sn-vgqsrnek.gvt1.com.": {},
+ "rr5---sn-vgqsrnes.googlevideo.com.": {},
+ "rr5---sn-vgqsrnez.googlevideo.com.": {},
+ "rr5---sn-vgqsrnez.gvt1.com.": {},
+ "rr5---sn-vgqsrnl6.googlevideo.com.": {},
+ "rr5---sn-vgqsrnld.googlevideo.com.": {},
+ "rr5---sn-vgqsrnll.googlevideo.com.": {},
+ "rr5---sn-vgqsrnls.googlevideo.com.": {},
+ "rr5---sn-vgqsrnlz.googlevideo.com.": {},
+ "rr5---sn-vgqsrnlz.gvt1.com.": {},
+ "rr5---sn-vgqsrns6.googlevideo.com.": {},
+ "rr5---sn-vgqsrns6.gvt1.com.": {},
+ "rr5---sn-vgqsrnsd.googlevideo.com.": {},
+ "rr5---sn-vgqsrnsd.gvt1.com.": {},
+ "rr5---sn-vgqsrnsr.googlevideo.com.": {},
+ "rr5---sn-vgqsrnsr.gvt1.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-vgqsrnzz.gvt1.com.": {},
+ "rr5.sn-q4fl6nsd.googlevideo.com.": {},
+ "rr6---sn-2aqu-hoas7.googlevideo.com.": {},
+ "rr6---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr6---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr6---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr6---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr6---sn-42u-nbozl.googlevideo.com.": {},
+ "rr6---sn-42u-nbozz.googlevideo.com.": {},
+ "rr6---sn-5pgnugx5h-hn2z.googlevideo.com.": {},
+ "rr6---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr6---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr6---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr6---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-2iae7.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-a5me.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-vgqe.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-xfge.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-xfgl.googlevideo.com.": {},
+ "rr6---sn-8xgp1vo-xfgs.googlevideo.com.": {},
+ "rr6---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr6---sn-i5f5ppuxa-ioal.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-nh4l.googlevideo.com.": {},
+ "rr6---sn-jvhj5nu-nh4s.googlevideo.com.": {},
+ "rr6---sn-jvhj5nu-nh4z.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-n5oe.gvt1.com.": {},
+ "rr6---sn-jxopj-nh4e.googlevideo.com.": {},
+ "rr6---sn-jxopj-nh4e.gvt1.com.": {},
+ "rr6---sn-nv0ui4gvou-hape.googlevideo.com.": {},
+ "rr7---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr7---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr7---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr7---sn-2vgu0b5auxaxjvh-v2vl.googlevideo.com.": {},
+ "rr7---sn-42u-nboze.googlevideo.com.": {},
+ "rr7---sn-42u-nbozz.googlevideo.com.": {},
+ "rr7---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr7---sn-8qj-i5ody.googlevideo.com.": {},
+ "rr7---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rr7---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr7---sn-8qj-nbo66.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-2ial.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-a5me.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-p5ie.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-vgqe.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-xfge.googlevideo.com.": {},
+ "rr7---sn-8xgp1vo-xfgl.googlevideo.com.": {},
+ "rr7---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr7---sn-jvhj5nu-nh4e.googlevideo.com.": {},
+ "rr7---sn-jvhj5nu-nh4l.googlevideo.com.": {},
+ "rr7---sn-jvhj5nu-nh4z.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.": {},
+ "rr7---sn-jxopj-n5oe.gvt1.com.": {},
+ "rr8---sn-2vgu0b5auxaxjvh-apnd.googlevideo.com.": {},
+ "rr8---sn-2vgu0b5auxaxjvh-v2vd.googlevideo.com.": {},
+ "rr8---sn-2vgu0b5auxaxjvh-v2ve.googlevideo.com.": {},
+ "rr8---sn-42u-nbozz.googlevideo.com.": {},
+ "rr8---sn-8qj-i5o6k.googlevideo.com.": {},
+ "rr8---sn-8qj-i5ozr.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-2iae.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-2ial.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-ab5s.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-ab5z.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-p5ie.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-vgqe.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-xfge.googlevideo.com.": {},
+ "rr8---sn-8xgp1vo-xfgl.googlevideo.com.": {},
+ "rr8---sn-bvvbax-2iae.googlevideo.com.": {},
+ "rr8---sn-bvvbax-2ial.googlevideo.com.": {},
+ "rr9---sn-8qj-i5ozd.googlevideo.com.": {},
+ "rs.cricketwireless.com.": {},
+ "rs1.qq.com.": {},
+ "rs2.qq.com.": {},
+ "rsfhealth-my.sharepoint.com.": {},
+ "rsx.afterpay.com.": {},
+ "rt.teramind.co.": {},
+ "rt3055.infolinks.com.": {},
+ "rtb-apac.rtbserve.io.": {},
+ "rtb-eu.rtbserve.io.": {},
+ "rtb-useast.creativedot.net.": {},
+ "rtb-useast.openrtb.in.": {},
+ "rtb-useast.rtbserve.io.": {},
+ "rtb-uswest.rtbserve.io.": {},
+ "rtb2-apac.xaprio.net.": {},
+ "rtb2-eu.xaprio.net.": {},
+ "rtb2-useast.xaprio.net.": {},
+ "rtb2-uswest.xaprio.net.": {},
+ "rtbasia.com.": {},
+ "rtbsuperhub.com.": {},
+ "rtbwave.com.": {},
+ "rtc.platform.dbankcloud.com.": {},
+ "rtp-static.marketo.com.": {},
+ "rttf.citrix.com.": {},
+ "ru1.chat.si.riotgames.com.": {},
+ "ruijienetworks.com.": {},
+ "rumble.com.": {},
+ "rumt-sg.com.": {},
+ "rushenterprises.sharepoint.com.": {},
+ "rutgersconnect-my.sharepoint.com.": {},
+ "rxsafeway-my.sharepoint.com.": {},
+ "rxsafeway.sharepoint.com.": {},
+ "s-cs.send.microad.jp.": {},
+ "s-rtb-pb.send.microad.jp.": {},
+ "s.ccsyncuuid.net.": {},
+ "s.dblks.net.": {},
+ "s.deepl.com.": {},
+ "s.exitbee.com.": {},
+ "s.optifine.net.": {},
+ "s.seedtag.com.": {},
+ "s1.thcdn.com.": {},
+ "s13emagst.akamaized.net.": {},
+ "s3.us-east-005.backblazeb2.com.": {},
+ "s3.us-west-004.backblazeb2.com.": {},
+ "s8.addthis.com.": {},
+ "sa1.chat.si.riotgames.com.": {},
+ "sa2.chat.si.riotgames.com.": {},
+ "sa3.chat.si.riotgames.com.": {},
+ "sabre-app.quantummetric.com.": {},
+ "sabre-sync.quantummetric.com.": {},
+ "saintasaph.remotepc.com.": {},
+ "salesbridge-my.sharepoint.com.": {},
+ "salesbridge.sharepoint.com.": {},
+ "saltlakecity.remotepc.com.": {},
+ "samhealthanon.genetec.com.": {},
+ "samip.genetec.com.": {},
+ "sampkac.genetec.com.": {},
+ "sams-checkin.mobile.walmart.com.": {},
+ "samsclub.quantummetric.com.": {},
+ "san.foxsports.com.edgekey.net.": {},
+ "san.support.hp.com.edgekey.net.": {},
+ "sanantonio.remotepc.com.": {},
+ "sandbox.wdesk.com.": {},
+ "sandboxclient.retinavue.net.": {},
+ "sandboxregister.retinavue.net.": {},
+ "sandiego.remotepc.com.": {},
+ "sandiegodc.remotepc.com.": {},
+ "sanjagh.com.": {},
+ "sanjose.remotepc.com.": {},
+ "santandernet-my.sharepoint.com.": {},
+ "santandernet.sharepoint.com.": {},
+ "santiago.remotepc.com.": {},
+ "saopaulo.remotepc.com.": {},
+ "saopaulo1.remotepc.com.": {},
+ "saskpower-my.sharepoint.com.": {},
+ "sasoffice365-my.sharepoint.com.": {},
+ "saspeed.igamecj.com.": {},
+ "sat02pap001.storage.live.com.": {},
+ "sat02pap001files.storage.live.com.": {},
+ "sat02pap002.storage.live.com.": {},
+ "sat02pap002files.storage.live.com.": {},
+ "sat02pap003.storage.live.com.": {},
+ "sat02pap003files.storage.live.com.": {},
+ "sat02pap004.storage.live.com.": {},
+ "sat02pap004files.storage.live.com.": {},
+ "sat02pap005.storage.live.com.": {},
+ "sat02pap005files.storage.live.com.": {},
+ "sav.cynet.com.": {},
+ "sc.zoom.us.": {},
+ "scasurgery-my.sharepoint.com.": {},
+ "scasurgery.sharepoint.com.": {},
+ "schneidercorp.com.": {},
+ "sciener.cn.": {},
+ "scontent-ams2-1.cdninstagram.com.": {},
+ "scontent-ams2-1.xx.fbcdn.net.": {},
+ "scontent-ams4-1.cdninstagram.com.": {},
+ "scontent-ams4-1.xx.fbcdn.net.": {},
+ "scontent-arn2-1.cdninstagram.com.": {},
+ "scontent-arn2-1.xx.fbcdn.net.": {},
+ "scontent-atl3-1.cdninstagram.com.": {},
+ "scontent-atl3-1.xx.fbcdn.net.": {},
+ "scontent-atl3-2.cdninstagram.com.": {},
+ "scontent-atl3-2.xx.fbcdn.net.": {},
+ "scontent-ber1-1.cdninstagram.com.": {},
+ "scontent-ber1-1.xx.fbcdn.net.": {},
+ "scontent-bog1-1.cdninstagram.com.": {},
+ "scontent-bog1-1.xx.fbcdn.net.": {},
+ "scontent-bog2-1.cdninstagram.com.": {},
+ "scontent-bog2-1.xx.fbcdn.net.": {},
+ "scontent-bos5-1.cdninstagram.com.": {},
+ "scontent-bos5-1.xx.fbcdn.net.": {},
+ "scontent-bru2-1.cdninstagram.com.": {},
+ "scontent-bru2-1.xx.fbcdn.net.": {},
+ "scontent-cdg4-1.cdninstagram.com.": {},
+ "scontent-cdg4-1.xx.fbcdn.net.": {},
+ "scontent-cdg4-2.cdninstagram.com.": {},
+ "scontent-cdg4-2.xx.fbcdn.net.": {},
+ "scontent-cdg4-3.cdninstagram.com.": {},
+ "scontent-cdg4-3.xx.fbcdn.net.": {},
+ "scontent-cgk1-1.cdninstagram.com.": {},
+ "scontent-cgk1-1.xx.fbcdn.net.": {},
+ "scontent-cgk1-2.cdninstagram.com.": {},
+ "scontent-cgk1-2.xx.fbcdn.net.": {},
+ "scontent-den4-1.cdninstagram.com.": {},
+ "scontent-den4-1.xx.fbcdn.net.": {},
+ "scontent-dfw5-1.cdninstagram.com.": {},
+ "scontent-dfw5-1.xx.fbcdn.net.": {},
+ "scontent-dfw5-2.cdninstagram.com.": {},
+ "scontent-dfw5-2.xx.fbcdn.net.": {},
+ "scontent-dus1-1.cdninstagram.com.": {},
+ "scontent-dus1-1.xx.fbcdn.net.": {},
+ "scontent-fml20-1.cdninstagram.com.": {},
+ "scontent-fml20-1.xx.fbcdn.net.": {},
+ "scontent-fra3-1.cdninstagram.com.": {},
+ "scontent-fra3-1.xx.fbcdn.net.": {},
+ "scontent-fra3-2.cdninstagram.com.": {},
+ "scontent-fra3-2.xx.fbcdn.net.": {},
+ "scontent-fra5-1.cdninstagram.com.": {},
+ "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.": {},
+ "scontent-gru2-1.xx.fbcdn.net.": {},
+ "scontent-gru2-2.cdninstagram.com.": {},
+ "scontent-gru2-2.xx.fbcdn.net.": {},
+ "scontent-ham3-1.cdninstagram.com.": {},
+ "scontent-ham3-1.xx.fbcdn.net.": {},
+ "scontent-hel3-1.cdninstagram.com.": {},
+ "scontent-hkg1-1.cdninstagram.com.": {},
+ "scontent-hkg1-1.xx.fbcdn.net.": {},
+ "scontent-hkg1-2.cdninstagram.com.": {},
+ "scontent-hkg1-2.xx.fbcdn.net.": {},
+ "scontent-hkg4-1.cdninstagram.com.": {},
+ "scontent-hkg4-1.xx.fbcdn.net.": {},
+ "scontent-hkg4-2.cdninstagram.com.": {},
+ "scontent-hkg4-2.xx.fbcdn.net.": {},
+ "scontent-hou1-1.cdninstagram.com.": {},
+ "scontent-hou1-1.xx.fbcdn.net.": {},
+ "scontent-iad3-1.cdninstagram.com.": {},
+ "scontent-iad3-1.xx.fbcdn.net.": {},
+ "scontent-iad3-2.cdninstagram.com.": {},
+ "scontent-iad3-2.xx.fbcdn.net.": {},
+ "scontent-lax3-1.cdninstagram.com.": {},
+ "scontent-lax3-1.xx.fbcdn.net.": {},
+ "scontent-lax3-2.cdninstagram.com.": {},
+ "scontent-lax3-2.xx.fbcdn.net.": {},
+ "scontent-lga3-1.cdninstagram.com.": {},
+ "scontent-lga3-1.xx.fbcdn.net.": {},
+ "scontent-lga3-2.cdninstagram.com.": {},
+ "scontent-lga3-2.xx.fbcdn.net.": {},
+ "scontent-lhr6-1.cdninstagram.com.": {},
+ "scontent-lhr6-1.xx.fbcdn.net.": {},
+ "scontent-lhr6-2.cdninstagram.com.": {},
+ "scontent-lhr6-2.xx.fbcdn.net.": {},
+ "scontent-lhr8-1.cdninstagram.com.": {},
+ "scontent-lhr8-1.xx.fbcdn.net.": {},
+ "scontent-lhr8-2.cdninstagram.com.": {},
+ "scontent-lhr8-2.xx.fbcdn.net.": {},
+ "scontent-lis1-1.cdninstagram.com.": {},
+ "scontent-lis1-1.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-mnl1-1.xx.fbcdn.net.": {},
+ "scontent-mnl1-2.xx.fbcdn.net.": {},
+ "scontent-mrs2-1.xx.fbcdn.net.": {},
+ "scontent-mrs2-2.xx.fbcdn.net.": {},
+ "scontent-msp1-1.cdninstagram.com.": {},
+ "scontent-msp1-1.xx.fbcdn.net.": {},
+ "scontent-mty2-1.cdninstagram.com.": {},
+ "scontent-mty2-1.xx.fbcdn.net.": {},
+ "scontent-muc2-1.cdninstagram.com.": {},
+ "scontent-muc2-1.xx.fbcdn.net.": {},
+ "scontent-nrt1-2.cdninstagram.com.": {},
+ "scontent-ord5-1.cdninstagram.com.": {},
+ "scontent-ord5-1.xx.fbcdn.net.": {},
+ "scontent-ord5-2.cdninstagram.com.": {},
+ "scontent-ord5-2.xx.fbcdn.net.": {},
+ "scontent-pmo1-1.xx.fbcdn.net.": {},
+ "scontent-prg1-1.cdninstagram.com.": {},
+ "scontent-prg1-1.xx.fbcdn.net.": {},
+ "scontent-qro1-1.cdninstagram.com.": {},
+ "scontent-qro1-1.xx.fbcdn.net.": {},
+ "scontent-qro1-2.cdninstagram.com.": {},
+ "scontent-qro1-2.xx.fbcdn.net.": {},
+ "scontent-sea1-1.cdninstagram.com.": {},
+ "scontent-sea1-1.xx.fbcdn.net.": {},
+ "scontent-sin6-1.cdninstagram.com.": {},
+ "scontent-sin6-1.xx.fbcdn.net.": {},
+ "scontent-sin6-2.cdninstagram.com.": {},
+ "scontent-sin6-2.xx.fbcdn.net.": {},
+ "scontent-sin6-3.cdninstagram.com.": {},
+ "scontent-sin6-3.xx.fbcdn.net.": {},
+ "scontent-sin6-4.cdninstagram.com.": {},
+ "scontent-sin6-4.xx.fbcdn.net.": {},
+ "scontent-sjc3-1.cdninstagram.com.": {},
+ "scontent-sjc3-1.xx.fbcdn.net.": {},
+ "scontent-ssn1-1.cdninstagram.com.": {},
+ "scontent-vie1-1.cdninstagram.com.": {},
+ "scontent-vie1-1.xx.fbcdn.net.": {},
+ "scontent-waw1-1.cdninstagram.com.": {},
+ "scontent-waw1-1.xx.fbcdn.net.": {},
+ "scontent-xsp1-1.cdninstagram.com.": {},
+ "scontent-xsp1-1.xx.fbcdn.net.": {},
+ "scontent-xsp1-2.cdninstagram.com.": {},
+ "scontent-xsp1-2.xx.fbcdn.net.": {},
+ "scontent-xsp1-3.cdninstagram.com.": {},
+ "scontent-xsp1-3.xx.fbcdn.net.": {},
+ "scontent-xsp2-1.cdninstagram.com.": {},
+ "scontent-xsp2-1.xx.fbcdn.net.": {},
+ "scontent-yyz1-1.cdninstagram.com.": {},
+ "scontent-yyz1-1.xx.fbcdn.net.": {},
+ "scraper2.onlineradiobox.com.": {},
+ "scus.his.arc.azure.com.": {},
+ "scus.his.hybridcompute.trafficmanager.net.": {},
+ "sdeconfig.kaspersky-labs.com.": {},
+ "sdk.51.la.": {},
+ "sdk.beizi.biz.": {},
+ "sdk.qcloud.com.": {},
+ "sdkgate.pushv3.easebar.com.": {},
+ "sdktmp.hubcloud.com.cn.": {},
+ "sdn.lxdns.com.": {},
+ "sdn.qtlcname.com.": {},
+ "seabroadnet.com.": {},
+ "seagate.com.": {},
+ "seagullscientific.com.": {},
+ "sealsubscriptions.com.": {},
+ "search.dnssearch.org.": {},
+ "search.namequery.com.": {},
+ "search.us.namequery.com.": {},
+ "searchanise.com.": {},
+ "searchnews-dre.dt.dbankcloud.com.": {},
+ "searchserverapi.com.": {},
+ "seattle.remotepc.com.": {},
+ "seattle2.remotepc.com.": {},
+ "seattle3.remotepc.com.": {},
+ "secaucus.remotepc.com.": {},
+ "secure.accurint.com.": {},
+ "secure.appex-rf.msn.com.edgekey.net.": {},
+ "secure.channel4.com.edgekey.net.": {},
+ "secure.syndetics.com.": {},
+ "secure5.arcot.com.": {},
+ "securelink.rivhs.com.": {},
+ "securelink.valleywisehealth.org.": {},
+ "securityapi.d3-pr-tm.com.": {},
+ "securitybankcorporation-my.sharepoint.com.": {},
+ "seedtag.com.": {},
+ "select1.notoldrb.monster.": {},
+ "select10.notoldrb.monster.": {},
+ "select3.notoldrb.monster.": {},
+ "select4.notoldrb.monster.": {},
+ "select5.notoldrb.monster.": {},
+ "select6.notoldrb.monster.": {},
+ "select7.notoldrb.monster.": {},
+ "select9.notoldrb.monster.": {},
+ "selectmedia.asia.": {},
+ "send.microad.jp.": {},
+ "sendibt3.com.": {},
+ "sensei.ruselabs.com.": {},
+ "sensorsdata.cn.": {},
+ "sensorsdata.com.": {},
+ "sentry.appodeal.com.": {},
+ "sentry.archive.org.": {},
+ "sentry.ksztone.com.": {},
+ "sentry.quillbot.com.": {},
+ "serv.vuukle.com.": {},
+ "serve.pubapp.network.": {},
+ "served-by.pixfuture.com.": {},
+ "server.10m.com.cn.": {},
+ "server.bidstack.com.": {},
+ "serverforge.org.": {},
+ "service.minute.ly.": {},
+ "services.lego.com.": {},
+ "services.starfinanz.de.": {},
+ "services.ucp.kaspersky-labs.com.": {},
+ "servicetitan.com.": {},
+ "servt.vuukle.com.": {},
+ "servx.opamarketplace.com.": {},
+ "sessionstack.com.": {},
+ "settings.live.net.": {},
+ "settings.luckyorange.com.": {},
+ "sewjn80htn-3.algolianet.com.": {},
+ "sf-express.com.": {},
+ "sg.api.translator.voice.gcloudsdk.com.": {},
+ "sg.mmstat.com.": {},
+ "sg.rr2.internet-intl.spanner.alipaydns.com.": {},
+ "sgmbocast.com.": {},
+ "sgpcas.ezvizlife.com.": {},
+ "sgtm.journeys.com.": {},
+ "shaka.ruselabs.com.": {},
+ "share.connect.aig.": {},
+ "shared.tournament.a.pvp.net.": {},
+ "sharkninja-prd-cus-001.azure-api.net.": {},
+ "sharpschool.com.": {},
+ "shc6.y.qq.com.": {},
+ "shopify-gtm-suite.getelevar.com.": {},
+ "shoplazza.com.": {},
+ "shortpixel.ai.": {},
+ "shoutout.wix.com.": {},
+ "shp.ee.": {},
+ "shutterfly-app.quantummetric.com.": {},
+ "shutterfly-sync.quantummetric.com.": {},
+ "shuzilm.cn.": {},
+ "signet.groupbycloud.com.": {},
+ "signin.ultipro.com.": {},
+ "simpshopifyapps.com.": {},
+ "sin-itm-radar-testobject.citrix.com.": {},
+ "sina.com.": {},
+ "sinaimg.cn.": {},
+ "sip.ringcentral.com.": {},
+ "sip113-1121.ringcentral.com.": {},
+ "sip113-1131.ringcentral.com.": {},
+ "sip113-1141.ringcentral.com.": {},
+ "sip121-1121.ringcentral.com.": {},
+ "sip121-1131.ringcentral.com.": {},
+ "sip123-1121.ringcentral.com.": {},
+ "sip123-1131.ringcentral.com.": {},
+ "sip131-1131.ringcentral.com.": {},
+ "sip132-1131.ringcentral.com.": {},
+ "sip132-1141.ringcentral.com.": {},
+ "sip421-121.ringcentral.biz.": {},
+ "sisense.com.": {},
+ "sitemaji.com.": {},
+ "sjc.zoom.us.": {},
+ "sjc04pap001.storage.live.com.": {},
+ "sjc04pap001files.storage.live.com.": {},
+ "sjc04pap002.storage.live.com.": {},
+ "sjc04pap002files.storage.live.com.": {},
+ "skims.com.": {},
+ "skybet.com.": {},
+ "skyward-lisdprod.iscorp.com.": {},
+ "skyward-ocprod.iscorp.com.": {},
+ "skyward.iscorp.com.": {},
+ "sl.streamhub.tech.": {},
+ "slb.cynet.com.": {},
+ "slickdealscdn.com.cdn.cloudflare.net.": {},
+ "sling-app.quantummetric.com.": {},
+ "sm.cn.": {},
+ "sm1.selectmedia.asia.": {},
+ "smartcloudcon.com.": {},
+ "smartcommunications.cloud.": {},
+ "smoot-searchv2-aapse1c.v.aaplimg.com.": {},
+ "smoot-searchv2-aeuc1a.v.aaplimg.com.": {},
+ "smoot-searchv2-aeuc1b.v.aaplimg.com.": {},
+ "smoot-searchv2-aeuw1b.v.aaplimg.com.": {},
+ "smoot-searchv2-aeuw3b.v.aaplimg.com.": {},
+ "smoot-searchv2-aeuw3c.v.aaplimg.com.": {},
+ "smoot-searchv2-ause1a.v.aaplimg.com.": {},
+ "smoot-searchv2-ause1b.v.aaplimg.com.": {},
+ "smoot-searchv2-ause1c.v.aaplimg.com.": {},
+ "smoot-searchv2-ause2a.v.aaplimg.com.": {},
+ "smoot-searchv2-ause2b.v.aaplimg.com.": {},
+ "smoot-searchv2-ause2c.v.aaplimg.com.": {},
+ "smoot-searchv2-ausw2b.v.aaplimg.com.": {},
+ "smoot-searchv2-ausw2c.v.aaplimg.com.": {},
+ "smrcy-my.sharepoint.com.": {},
+ "smrcy.sharepoint.com.": {},
+ "sms.ads.heytapmobi.com.": {},
+ "sn2files.storage.live.com.": {},
+ "sn3301files.storage.live.com.": {},
+ "snap-storage-cdn.l.google.com.": {},
+ "snippet.affilimatejs.com.": {},
+ "snz04pap001.storage.live.com.": {},
+ "snz04pap001files.storage.live.com.": {},
+ "snz04pap002.storage.live.com.": {},
+ "snz04pap002files.storage.live.com.": {},
+ "socialize.us1.gigya.com.": {},
+ "sockets.stackexchange.com.": {},
+ "sofia.remotepc.com.": {},
+ "sogoucdn.com.": {},
+ "sohu.com.": {},
+ "sohucs.com.": {},
+ "solid.preyproject.com.": {},
+ "solitaireevents.microsoftcasualgames.com.": {},
+ "solve-api.forethought.ai.": {},
+ "somplo.com.": {},
+ "sonar-akl1-1.xx.fbcdn.net.": {},
+ "sonar-ams2-1.xx.fbcdn.net.": {},
+ "sonar-ams4-1.xx.fbcdn.net.": {},
+ "sonar-arn2-1.xx.fbcdn.net.": {},
+ "sonar-atl3-1.xx.fbcdn.net.": {},
+ "sonar-atl3-2.xx.fbcdn.net.": {},
+ "sonar-bcn1-1.xx.fbcdn.net.": {},
+ "sonar-ber1-1.xx.fbcdn.net.": {},
+ "sonar-bkk1-1.xx.fbcdn.net.": {},
+ "sonar-bkk1-2.xx.fbcdn.net.": {},
+ "sonar-bog1-1.xx.fbcdn.net.": {},
+ "sonar-bog2-1.xx.fbcdn.net.": {},
+ "sonar-bom1-1.xx.fbcdn.net.": {},
+ "sonar-bom1-2.xx.fbcdn.net.": {},
+ "sonar-bos5-1.xx.fbcdn.net.": {},
+ "sonar-bru2-1.xx.fbcdn.net.": {},
+ "sonar-ccu1-1.xx.fbcdn.net.": {},
+ "sonar-ccu1-2.xx.fbcdn.net.": {},
+ "sonar-cdg4-1.xx.fbcdn.net.": {},
+ "sonar-cdg4-2.xx.fbcdn.net.": {},
+ "sonar-cdg4-3.xx.fbcdn.net.": {},
+ "sonar-cgk1-1.xx.fbcdn.net.": {},
+ "sonar-cgk1-2.xx.fbcdn.net.": {},
+ "sonar-cgk1-3.xx.fbcdn.net.": {},
+ "sonar-cph2-1.xx.fbcdn.net.": {},
+ "sonar-cpt1-1.xx.fbcdn.net.": {},
+ "sonar-del1-1.xx.fbcdn.net.": {},
+ "sonar-del1-2.xx.fbcdn.net.": {},
+ "sonar-del2-1.xx.fbcdn.net.": {},
+ "sonar-del2-2.xx.fbcdn.net.": {},
+ "sonar-den4-1.xx.fbcdn.net.": {},
+ "sonar-dfw5-1.xx.fbcdn.net.": {},
+ "sonar-dfw5-2.xx.fbcdn.net.": {},
+ "sonar-doh1-1.xx.fbcdn.net.": {},
+ "sonar-dub4-1.xx.fbcdn.net.": {},
+ "sonar-dus1-1.xx.fbcdn.net.": {},
+ "sonar-eze1-1.xx.fbcdn.net.": {},
+ "sonar-fco2-1.xx.fbcdn.net.": {},
+ "sonar-fml20-1.xx.fbcdn.net.": {},
+ "sonar-for1-1.xx.fbcdn.net.": {},
+ "sonar-fra3-1.xx.fbcdn.net.": {},
+ "sonar-fra3-2.xx.fbcdn.net.": {},
+ "sonar-fra5-1.xx.fbcdn.net.": {},
+ "sonar-fra5-2.xx.fbcdn.net.": {},
+ "sonar-gig4-1.xx.fbcdn.net.": {},
+ "sonar-gig4-2.xx.fbcdn.net.": {},
+ "sonar-gmp1-1.xx.fbcdn.net.": {},
+ "sonar-gru1-1.xx.fbcdn.net.": {},
+ "sonar-gru1-2.xx.fbcdn.net.": {},
+ "sonar-gru2-1.xx.fbcdn.net.": {},
+ "sonar-gru2-2.xx.fbcdn.net.": {},
+ "sonar-gua1-1.xx.fbcdn.net.": {},
+ "sonar-ham3-1.xx.fbcdn.net.": {},
+ "sonar-hbe1-1.xx.fbcdn.net.": {},
+ "sonar-hbe1-2.xx.fbcdn.net.": {},
+ "sonar-hel3-1.xx.fbcdn.net.": {},
+ "sonar-hkg1-1.xx.fbcdn.net.": {},
+ "sonar-hkg1-2.xx.fbcdn.net.": {},
+ "sonar-hkg4-1.xx.fbcdn.net.": {},
+ "sonar-hkg4-2.xx.fbcdn.net.": {},
+ "sonar-hou1-1.xx.fbcdn.net.": {},
+ "sonar-hyd1-1.xx.fbcdn.net.": {},
+ "sonar-iad3-1.xx.fbcdn.net.": {},
+ "sonar-iad3-2.xx.fbcdn.net.": {},
+ "sonar-iev1-1.xx.fbcdn.net.": {},
+ "sonar-ist1-1.xx.fbcdn.net.": {},
+ "sonar-itm1-1.xx.fbcdn.net.": {},
+ "sonar-jnb1-1.xx.fbcdn.net.": {},
+ "sonar-jnb2-1.xx.fbcdn.net.": {},
+ "sonar-kul2-1.xx.fbcdn.net.": {},
+ "sonar-kul2-2.xx.fbcdn.net.": {},
+ "sonar-kul3-1.xx.fbcdn.net.": {},
+ "sonar-lax3-1.xx.fbcdn.net.": {},
+ "sonar-lax3-2.xx.fbcdn.net.": {},
+ "sonar-lga3-1.xx.fbcdn.net.": {},
+ "sonar-lga3-2.xx.fbcdn.net.": {},
+ "sonar-lhr6-1.xx.fbcdn.net.": {},
+ "sonar-lhr6-2.xx.fbcdn.net.": {},
+ "sonar-lhr8-1.xx.fbcdn.net.": {},
+ "sonar-lhr8-2.xx.fbcdn.net.": {},
+ "sonar-lim1-1.xx.fbcdn.net.": {},
+ "sonar-lis1-1.xx.fbcdn.net.": {},
+ "sonar-los2-1.xx.fbcdn.net.": {},
+ "sonar-maa2-1.xx.fbcdn.net.": {},
+ "sonar-maa2-2.xx.fbcdn.net.": {},
+ "sonar-mad1-1.xx.fbcdn.net.": {},
+ "sonar-mad2-1.xx.fbcdn.net.": {},
+ "sonar-man2-1.xx.fbcdn.net.": {},
+ "sonar-mba1-1.xx.fbcdn.net.": {},
+ "sonar-mct1-1.xx.fbcdn.net.": {},
+ "sonar-mia3-1.xx.fbcdn.net.": {},
+ "sonar-mia3-2.xx.fbcdn.net.": {},
+ "sonar-mnl1-1.xx.fbcdn.net.": {},
+ "sonar-mnl1-2.xx.fbcdn.net.": {},
+ "sonar-mrs2-1.xx.fbcdn.net.": {},
+ "sonar-mrs2-2.xx.fbcdn.net.": {},
+ "sonar-msp1-1.xx.fbcdn.net.": {},
+ "sonar-mty2-1.xx.fbcdn.net.": {},
+ "sonar-muc2-1.xx.fbcdn.net.": {},
+ "sonar-mxp1-1.xx.fbcdn.net.": {},
+ "sonar-mxp2-1.xx.fbcdn.net.": {},
+ "sonar-nrt1-1.xx.fbcdn.net.": {},
+ "sonar-nrt1-2.xx.fbcdn.net.": {},
+ "sonar-ord5-1.xx.fbcdn.net.": {},
+ "sonar-ord5-2.xx.fbcdn.net.": {},
+ "sonar-otp1-1.xx.fbcdn.net.": {},
+ "sonar-pmo1-1.xx.fbcdn.net.": {},
+ "sonar-pnq1-1.xx.fbcdn.net.": {},
+ "sonar-pnq1-2.xx.fbcdn.net.": {},
+ "sonar-prg1-1.xx.fbcdn.net.": {},
+ "sonar-qro1-1.xx.fbcdn.net.": {},
+ "sonar-qro1-2.xx.fbcdn.net.": {},
+ "sonar-scl2-1.xx.fbcdn.net.": {},
+ "sonar-sea1-1.xx.fbcdn.net.": {},
+ "sonar-sin6-1.xx.fbcdn.net.": {},
+ "sonar-sin6-2.xx.fbcdn.net.": {},
+ "sonar-sin6-3.xx.fbcdn.net.": {},
+ "sonar-sin6-4.xx.fbcdn.net.": {},
+ "sonar-sjc3-1.xx.fbcdn.net.": {},
+ "sonar-sof1-1.xx.fbcdn.net.": {},
+ "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-tpe1-1.xx.fbcdn.net.": {},
+ "sonar-vie1-1.xx.fbcdn.net.": {},
+ "sonar-waw1-1.xx.fbcdn.net.": {},
+ "sonar-xsp1-1.xx.fbcdn.net.": {},
+ "sonar-xsp1-2.xx.fbcdn.net.": {},
+ "sonar-xsp1-3.xx.fbcdn.net.": {},
+ "sonar-xsp2-1.xx.fbcdn.net.": {},
+ "sonar-xxb1-1.xx.fbcdn.net.": {},
+ "sonar-yyz1-1.xx.fbcdn.net.": {},
+ "sonar-zrh1-1.xx.fbcdn.net.": {},
+ "sonar.viously.com.": {},
+ "sooners-my.sharepoint.com.": {},
+ "sound-ai-stream.alibaba.com.": {},
+ "southcarolina.remotepc.com.": {},
+ "sp.replit.com.": {},
+ "sparteo.com.": {},
+ "spc2com-my.sharepoint.com.": {},
+ "spcdn.incartupsell.com.": {},
+ "spectrumhealth-my.sharepoint.com.": {},
+ "spectrumhealth.sharepoint.com.": {},
+ "speechify.com.": {},
+ "spiny.ai.": {},
+ "spion.savvy.security.": {},
+ "splash-online-decision.xiaohongshu.com.": {},
+ "splash.unity.cn.": {},
+ "sportsmans.com.": {},
+ "sportsuggest-dre.things.dbankcloud.cn.": {},
+ "springfieldclinic-my.sharepoint.com.": {},
+ "springfieldclinic.sharepoint.com.": {},
+ "src.ebay-us.com.": {},
+ "ssafp.samsclub.com.": {},
+ "ssc.independent.co.uk.": {},
+ "ssctech.com.": {},
+ "ssl.microsofttranslator.com.": {},
+ "sso.services.box.net.": {},
+ "ssp.hbrd.io.": {},
+ "ssp.hybrid.ai.": {},
+ "sstatic.net.": {},
+ "st-sysupgrade.vivo.com.cn.": {},
+ "staffbase.com.": {},
+ "standaard.be.": {},
+ "staples.com.": {},
+ "stardustgod.com.": {},
+ "starfinanz.de.": {},
+ "startssl.com.": {},
+ "stat.360safe.com.": {},
+ "stat.flashtalking.com.edgekey.net.": {},
+ "stat.lianmeng.360.cn.": {},
+ "stat.mixi.media.": {},
+ "stat.pdfforge.org.": {},
+ "stat2.notoldrb.monster.": {},
+ "stat3.notoldrb.monster.": {},
+ "stat4.notoldrb.monster.": {},
+ "stat5.notoldrb.monster.": {},
+ "stat6.notoldrb.monster.": {},
+ "stat7.notoldrb.monster.": {},
+ "statad.ru.": {},
+ "stathome.org.": {},
+ "static-ams2-1.xx.fbcdn.net.": {},
+ "static-ams4-1.xx.fbcdn.net.": {},
+ "static-assets.highwebmedia.com.": {},
+ "static-atl3-1.xx.fbcdn.net.": {},
+ "static-atl3-2.xx.fbcdn.net.": {},
+ "static-bos5-1.xx.fbcdn.net.": {},
+ "static-den4-1.xx.fbcdn.net.": {},
+ "static-dfw5-1.xx.fbcdn.net.": {},
+ "static-dfw5-2.xx.fbcdn.net.": {},
+ "static-hou1-1.xx.fbcdn.net.": {},
+ "static-iad3-1.xx.fbcdn.net.": {},
+ "static-iad3-2.xx.fbcdn.net.": {},
+ "static-lax3-1.xx.fbcdn.net.": {},
+ "static-lax3-2.xx.fbcdn.net.": {},
+ "static-lga3-1.xx.fbcdn.net.": {},
+ "static-lga3-2.xx.fbcdn.net.": {},
+ "static-lhr6-1.xx.fbcdn.net.": {},
+ "static-lhr6-2.xx.fbcdn.net.": {},
+ "static-lhr8-1.xx.fbcdn.net.": {},
+ "static-man2-1.xx.fbcdn.net.": {},
+ "static-mia3-1.xx.fbcdn.net.": {},
+ "static-mia3-2.xx.fbcdn.net.": {},
+ "static-msp1-1.xx.fbcdn.net.": {},
+ "static-ord5-1.xx.fbcdn.net.": {},
+ "static-ord5-2.xx.fbcdn.net.": {},
+ "static-sea1-1.xx.fbcdn.net.": {},
+ "static-sjc3-1.xx.fbcdn.net.": {},
+ "static-waw1-1.xx.fbcdn.net.": {},
+ "static.chewy.com.": {},
+ "static.dable.io.": {},
+ "static.etracker.com.": {},
+ "static.fastly.carvana.io.": {},
+ "static.independent.co.uk.": {},
+ "static.mobilkoy.ru.": {},
+ "static.rgpub.io.": {},
+ "static.standard.co.uk.": {},
+ "static.thcdn.com.": {},
+ "static1.mixi.media.": {},
+ "static3.mixi.media.": {},
+ "static4.mixi.media.": {},
+ "static7.mixi.media.": {},
+ "static8.mixi.media.": {},
+ "staticw2.yotpo.com-v1.edgekey.net.": {},
+ "statistic.live.126.net.": {},
+ "stats.aeries.com.": {},
+ "stats.bannernow.com.": {},
+ "stats.mightytext.co.": {},
+ "stats.rip.": {},
+ "stats.transitapp.com.": {},
+ "statsig.anthropic.com.": {},
+ "stblaw0-my.sharepoint.com.": {},
+ "stcdn.leadconnectorhq.com.": {},
+ "stcharleshealthsystem-my.sharepoint.com.": {},
+ "stericyclecorp-my.sharepoint.com.": {},
+ "stevemadden.com.": {},
+ "stg-data.ads.heytapmobi.com.": {},
+ "stlouis.remotepc.com.": {},
+ "stockholm.remotepc.com.": {},
+ "stocks-analytics-events.apple.com.": {},
+ "stockx-app.quantummetric.com.": {},
+ "storagecraft.com.": {},
+ "store-dra-hispace-dbankcloud-cn-dra.appacc.dbankedge.net.": {},
+ "store-dra.hispace.dbankcloud.cn.": {},
+ "store.epicgames.com.": {},
+ "store.qq.com.": {},
+ "streamhub.tech.": {},
+ "streetviewpixels-pa.googleapis.com.": {},
+ "stse02.ultipro.com.": {},
+ "stsew02.ultipro.com.": {},
+ "stsn02.ultipro.com.": {},
+ "studentsecisd-my.sharepoint.com.": {},
+ "studentsecuedu66932-my.sharepoint.com.": {},
+ "studentsecuedu66932.sharepoint.com.": {},
+ "studenttuhsd-my.sharepoint.com.": {},
+ "studenttuhsd.sharepoint.com.": {},
+ "stun.2talk.com.": {},
+ "stun.cdnbye.com.": {},
+ "stun1.ringcentral.com.": {},
+ "stun101.signon.gravityshavings.net.": {},
+ "stun102.signon.gravityshavings.net.": {},
+ "stun103.signon.gravityshavings.net.": {},
+ "stun104.signon.gravityshavings.net.": {},
+ "stun105.signon.gravityshavings.net.": {},
+ "stun106.signon.gravityshavings.net.": {},
+ "stun107.signon.gravityshavings.net.": {},
+ "stun108.signon.gravityshavings.net.": {},
+ "stun2.ringcentral.com.": {},
+ "styx.data.dashlane.com.": {},
+ "subway-sync.quantummetric.com.": {},
+ "suggest.v-mate.mobi.": {},
+ "sumari-prod-1.srv.jbisumari.org.": {},
+ "sumari.jbi.global.": {},
+ "sumologic.com.": {},
+ "sunamerica.com.": {},
+ "sunmedia.tv.": {},
+ "sunmi.com.": {},
+ "sunocoinc-my.sharepoint.com.": {},
+ "sunocoinc.sharepoint.com.": {},
+ "suntcontent.se.": {},
+ "supl.qxwz.com.": {},
+ "support.microsoft.com.": {},
+ "support.powerschool.com.": {},
+ "sutterhealth-my.sharepoint.com.": {},
+ "sv-ookla.geolinks.com.": {},
+ "sv8.cyberhaven.io.": {},
+ "svlive.serraview.com.": {},
+ "sweatco.in.": {},
+ "switch.babybus.com.": {},
+ "swsim.stamps.com.": {},
+ "sydney-sydney-bing-com.trafficmanager.net.": {},
+ "sydney.bing.com.": {},
+ "sydney.remotepc.com.": {},
+ "sync-1-us-west1-g.sync.services.mozilla.com.": {},
+ "sync.bidence.net.": {},
+ "sync.inmobi.com.": {},
+ "sync.lunamedia.live.": {},
+ "sync.opendsp.ru.": {},
+ "sync.videowalldirect.com.": {},
+ "syndetics.com.": {},
+ "sysdk.cl2009.com.": {},
+ "systemreportservices.genetec.com.": {},
+ "t-apil2.mythad.com.": {},
+ "t-odx.op-mobile.opera.com.": {},
+ "t.adcell.com.": {},
+ "t.marketingcloudfx.com.": {},
+ "t.mookie1.com.": {},
+ "t.nit.ro.": {},
+ "t.poki.io.": {},
+ "t.wepay.com.": {},
+ "t1.ssl.ak.dynamic.tiles.virtualearth.net.": {},
+ "t2.nativendo.de.": {},
+ "t24469.featuretv.live.": {},
+ "t2711.featuretv.live.": {},
+ "t3.xiaohongshu.com.": {},
+ "t9638.featuretv.live.": {},
+ "t97182.featuretv.live.": {},
+ "t9790.featuretv.live.": {},
+ "tafaop.xiazaisoft111.com.": {},
+ "tag.winister.app.": {},
+ "tagcommander.com.": {},
+ "tags.johnlewis.com.": {},
+ "tags.natwest.com.": {},
+ "taipei.remotepc.com.": {},
+ "tampa.remotepc.com.": {},
+ "tanx.com.": {},
+ "tao.barstoolsports.com.": {},
+ "taobao.com.": {},
+ "tapecontent.net.": {},
+ "tapestry-app.quantummetric.com.": {},
+ "tarrantcounty-my.sharepoint.com.": {},
+ "tasteofhome.com.": {},
+ "taylorfarms1com-my.sharepoint.com.": {},
+ "tb.sb-cd.com.": {},
+ "tbcache.com.": {},
+ "tccprod01.honeywell.com.": {},
+ "tccprod02.honeywell.com.": {},
+ "tccprod03.honeywell.com.": {},
+ "tcdnlive.com.": {},
+ "tclclouds.com.": {},
+ "tdcservices.tandemdiabetes.com.": {},
+ "tdm.qq.com.": {},
+ "teams.office.com.": {},
+ "teamviewer.com.": {},
+ "tec-do.cn.": {},
+ "techgus.com.": {},
+ "teddymobile.cn.": {},
+ "tegna.profiles.tagger.opecloud.com.": {},
+ "telaviv.remotepc.com.": {},
+ "telecom.shuzilm.cn.": {},
+ "telemetry-sdk-inmobi-comtm.trafficmanager.net.": {},
+ "telemetry.savvy.security.": {},
+ "telephony.goog.": {},
+ "teleport.media.": {},
+ "tencent-cloud.com.": {},
+ "tencent-cloud.net.": {},
+ "tencent.com.": {},
+ "tencentmusic.com.": {},
+ "tenda.com.cn.": {},
+ "tenpay.com.": {},
+ "teraswitch.com.": {},
+ "terms3.hicloud.com.": {},
+ "test.internetdownloadmanager.com.": {},
+ "testnjjhb.com.": {},
+ "tgp.qq.com.": {},
+ "tgpa.qq.com.": {},
+ "thanhnien.vn.": {},
+ "thd.mr-in.com.": {},
+ "thenewsbox.net.": {},
+ "theoks.net.": {},
+ "thetracker.org.": {},
+ "thinkific.com.": {},
+ "thinkingdata.cn.": {},
+ "thm.visa.com.": {},
+ "thm12.visa.com.": {},
+ "tigermailauburn-my.sharepoint.com.": {},
+ "tile.osm.org.": {},
+ "time.akamai.com.edgekey.net.": {},
+ "time.ecansol.net.": {},
+ "time.lmtlabs.com.": {},
+ "time.nest.com.": {},
+ "time.pool.aliyun.com.": {},
+ "time.walb.tech.": {},
+ "time1.aliyun.com.": {},
+ "time1.google.com.": {},
+ "time2.aliyun.com.": {},
+ "time2.google.com.": {},
+ "time3.aliyun.com.": {},
+ "time3.google.com.": {},
+ "time4.google.com.": {},
+ "timi-esports.qq.com.": {},
+ "timken-my.sharepoint.com.": {},
+ "tkx.mp.lura.live.": {},
+ "tlivecdn.com.": {},
+ "tlivesource.com.": {},
+ "tloxp.tlo.com.": {},
+ "tls-amap.dingtalk.com.": {},
+ "tls12.eu01.nr-data.net.cdn.cloudflare.net.": {},
+ "tls12.newrelic.com.cdn.cloudflare.net.": {},
+ "tlsext.com.": {},
+ "tm.barclays.co.uk.": {},
+ "tm.bdc-cdn.com.": {},
+ "tm.cybersource.com.": {},
+ "tm.regions.com.": {},
+ "tmall.com.": {},
+ "tmbbank-my.sharepoint.com.": {},
+ "tmbbank.sharepoint.com.": {},
+ "tmbi.com.": {},
+ "tmc-g2.tm-4.office.com.": {},
+ "tmfp.klarna.com.": {},
+ "tmga.qq.com.": {},
+ "tmge.alicdn.com.": {},
+ "tmobile-app.quantummetric.com.": {},
+ "tmobile-sync.quantummetric.com.": {},
+ "tmx.bestbuy.com.": {},
+ "tmx.monzo.co.uk.": {},
+ "tmx.tdbank.com.": {},
+ "tmx.uptodate.com.": {},
+ "tngdigital.com.my.": {},
+ "tocoding.com.": {},
+ "together.plex.tv.": {},
+ "tollbrothersinc-my.sharepoint.com.": {},
+ "tollbrothersinc.sharepoint.com.": {},
+ "top-widgets-va.us-east-1.log.aliyuncs.com.": {},
+ "toronto.remotepc.com.": {},
+ "tos-d-maliva16-up.byteoversea.net.": {},
+ "tosee-upg.tocoding.com.": {},
+ "towerhealth-my.sharepoint.com.": {},
+ "towerhealth.sharepoint.com.": {},
+ "tplay.qq.com.": {},
+ "tpns.gz2.tencent.com.": {},
+ "tpns.sgp.tencent.com.": {},
+ "tpns.sh.tencent.com.": {},
+ "tpns.tencent.com.": {},
+ "tpsservice-files-inner.cn-hangzhou.oss-cdn.aliyun-inc.com.": {},
+ "tr-tmc-geo.office.com.": {},
+ "tr1.chat.si.riotgames.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-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.loopme.me.": {},
+ "track.sendlane.com.": {},
+ "track.ultimate-guitar.com.": {},
+ "tracker-udp.gbitt.info.": {},
+ "tracker.best61.com.": {},
+ "tracker.bitsearch.to.": {},
+ "tracker.ccp.ovh.": {},
+ "tracker.files.fm.": {},
+ "tracker.grepler.com.": {},
+ "tracker.hyper-torrent.com.": {},
+ "tracker.moeking.me.": {},
+ "tracker.newtvcdn.com.": {},
+ "tracker.openwebtorrent.com.": {},
+ "tracker.theoks.net.": {},
+ "tracker1.bt.moack.co.kr.": {},
+ "tracker2.dler.org.": {},
+ "tracking.eu.antskre.com.": {},
+ "tracking.ksztone.com.": {},
+ "tradplusad.com.": {},
+ "traffic.omny.fm.": {},
+ "transec.usbank.com.": {},
+ "transplace-my.sharepoint.com.": {},
+ "traversal.syncromsp.com.": {},
+ "treas.gov.": {},
+ "treasury.gov.": {},
+ "tribalfusion.com.": {},
+ "trophy.ww.np.community.playstation.net.": {},
+ "trovit.com.": {},
+ "ts1.qq.com.": {},
+ "ts2.qq.com.": {},
+ "tsa.gov.": {},
+ "tse1.explicit.bing.net.": {},
+ "tse1.mm.bing.net.": {},
+ "tse2.explicit.bing.net.": {},
+ "tse2.mm.bing.net.": {},
+ "tse3.explicit.bing.net.": {},
+ "tse3.mm.bing.net.": {},
+ "tse4.explicit.bing.net.": {},
+ "tse4.mm.bing.net.": {},
+ "tsms-dre.security.dbankcloud.cn.": {},
+ "tt.browser.360.cn.": {},
+ "ttcache.com.": {},
+ "ttigroup-my.sharepoint.com.": {},
+ "ttigroup.sharepoint.com.": {},
+ "tubecup.net.": {},
+ "tuhsdk12azus-my.sharepoint.com.": {},
+ "tunnel.googlezip.net.": {},
+ "tuoitre.vn.": {},
+ "turn.aristotleinsight.com.": {},
+ "turnerusd202org-my.sharepoint.com.": {},
+ "tusd1-my.sharepoint.com.": {},
+ "tusd1.sharepoint.com.": {},
+ "tuya.com.": {},
+ "tw.ntp.org.cn.": {},
+ "twcgov-my.sharepoint.com.": {},
+ "twcgov.sharepoint.com.": {},
+ "tx-mirror.tier.net.": {},
+ "txcmmov.a.etoote.com.": {},
+ "txdot-my.sharepoint.com.": {},
+ "txdot.sharepoint.com.": {},
+ "txoag.sharepoint.com.": {},
+ "txqcmmov.a.etoote.com.": {},
+ "txryan.com.": {},
+ "u.4dex.io.": {},
+ "uapi.mp.360.cn.": {},
+ "uber.zoom.us.": {},
+ "uc.chatra-usercontent.com.": {},
+ "uc.cn.": {},
+ "uci.edog.cdn.office.net.edgekey.net.": {},
+ "ucweb.com.": {},
+ "udemycdn.com.": {},
+ "ue.lenovomm.cn.": {},
+ "ugc.bazaarvoice.com.": {},
+ "uhabo.com.": {},
+ "ui5.sap.com.": {},
+ "uk-api.asm.skype.com.": {},
+ "uk-prod.asyncgw.teams.microsoft.com.": {},
+ "uk3-word-collab.officeapps.live.com.": {},
+ "ukc-excel-collab.officeapps.live.com.": {},
+ "ukg.com.": {},
+ "uks.his.arc.azure.com.": {},
+ "uksouth-gas.guestconfiguration.azure.com.": {},
+ "ukyuh.tech.": {},
+ "ulikecam.com.": {},
+ "uline-app.quantummetric.com.": {},
+ "uline-sync.quantummetric.com.": {},
+ "ulinq.asia.": {},
+ "ulta-app.quantummetric.com.": {},
+ "ulta-sync.quantummetric.com.": {},
+ "ulta.com.": {},
+ "ulta.quantummetric.com.": {},
+ "ultipro.com.": {},
+ "ultiprotime.com.": {},
+ "ultiproworkplace.com.": {},
+ "umainesystem-my.sharepoint.com.": {},
+ "umeng.com.": {},
+ "ums-telemetry-cn.heytapmobi.com.": {},
+ "uncw4-my.sharepoint.com.": {},
+ "unicom.shuzilm.cn.": {},
+ "unified-inbox-1-gw.ultipro.com.": {},
+ "unified-inbox-2-gw.ultipro.com.": {},
+ "union.barstoolsports.com.": {},
+ "union.ucweb.com.": {},
+ "unipay.qq.com.": {},
+ "united.quantummetric.com.": {},
+ "unitrends.com.": {},
+ "unity.cn.": {},
+ "unity3d.com.": {},
+ "universityofwieauclaire-my.sharepoint.com.": {},
+ "up.cfxdewifi.top.": {},
+ "update-master.ixsystems.com.": {},
+ "update.360safe.com.": {},
+ "update.huorong.cn.": {},
+ "update.kingsoftstore.com.": {},
+ "update.pdfforge.org.": {},
+ "update.vivaldi.com.": {},
+ "updatechannel.sharegate.com.": {},
+ "updater.techsmith.com.": {},
+ "updaterservices.genetec.com.": {},
+ "updatesnl.macrium.com.": {},
+ "upload.app.box.com.": {},
+ "upload.box.com.": {},
+ "upload.ent.box.com.": {},
+ "upload.gifshow.com.": {},
+ "uploads.gamecoast.net.": {},
+ "upravel.com.": {},
+ "upscore.com.": {},
+ "upselling.apps.seabroadnet.com.": {},
+ "upstart.com.": {},
+ "upyun.com.": {},
+ "urekamedia.com.": {},
+ "url.cn.": {},
+ "us-api.asm.skype.com.": {},
+ "us-central1-adaptive-growth.cloudfunctions.net.": {},
+ "us-central1-addshoppers-data-production.cloudfunctions.net.": {},
+ "us-central1-aeo-datasci-microsrv-pr-afe8.cloudfunctions.net.": {},
+ "us-central1-affilimate.cloudfunctions.net.": {},
+ "us-central1-amp-error-reporting.cloudfunctions.net.": {},
+ "us-central1-blaze-today.cloudfunctions.net.": {},
+ "us-central1-bps-oi-production.cloudfunctions.net.": {},
+ "us-central1-castify-notifications-prod.cloudfunctions.net.": {},
+ "us-central1-clubroom-prod.cloudfunctions.net.": {},
+ "us-central1-cohinc-146020.cloudfunctions.net.": {},
+ "us-central1-custom-site-analytics.cloudfunctions.net.": {},
+ "us-central1-digitalproducts-gabbo.cloudfunctions.net.": {},
+ "us-central1-ds-specials-dev.cloudfunctions.net.": {},
+ "us-central1-fsgenergy-shared.cloudfunctions.net.": {},
+ "us-central1-gaggle-staging.cloudfunctions.net.": {},
+ "us-central1-justbuild-cdb86.cloudfunctions.net.": {},
+ "us-central1-live-prod-1-1.cloudfunctions.net.": {},
+ "us-central1-locket-4252a.cloudfunctions.net.": {},
+ "us-central1-mikmak-microservices.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-sq-sgtm-prod.cloudfunctions.net.": {},
+ "us-central1-teach-monster.cloudfunctions.net.": {},
+ "us-central1-webgltest-17af1.cloudfunctions.net.": {},
+ "us-central1-wetterapp-1.cloudfunctions.net.": {},
+ "us-den-anx-r008.router.teamviewer.com.": {},
+ "us-east-1.log.aliyuncs.com.": {},
+ "us-east4-chkp-gcp-rnd-threat-hunt-box.cloudfunctions.net.": {},
+ "us-las-gcp-r001.router.teamviewer.com.": {},
+ "us-lax-anx-r013.router.teamviewer.com.": {},
+ "us-pftk-temu-com.trafficmanager.net.": {},
+ "us-prod.asyncgw.teams.microsoft.com.": {},
+ "us-sea-anx-r001.router.teamviewer.com.": {},
+ "us-sea-anx-r002.router.teamviewer.com.": {},
+ "us-sea-anx-r003.router.teamviewer.com.": {},
+ "us-sea-anx-r004.router.teamviewer.com.": {},
+ "us-sea-anx-r005.router.teamviewer.com.": {},
+ "us-sea-anx-r007.router.teamviewer.com.": {},
+ "us-sea-anx-r008.router.teamviewer.com.": {},
+ "us-spectrum.rcs.telephony.goog.": {},
+ "us-west-1.log.aliyuncs.com.": {},
+ "us.att.rcs.telephony.goog.": {},
+ "us.peerhub.net.": {},
+ "us.pftk.temu.com.": {},
+ "us.rtbsystem.org.": {},
+ "us.tmobile.rcs.telephony.goog.": {},
+ "us.tracfone.rcs.telephony.goog.": {},
+ "us.uscc.rcs.telephony.goog.": {},
+ "us.xfinity.rcs.telephony.goog.": {},
+ "us01.ws-api.ringcentral.com.": {},
+ "us02.ws-api.ringcentral.com.": {},
+ "us02log.zoom.us.": {},
+ "us02polling.zoom.us.": {},
+ "us02web.zoom.us.": {},
+ "us02www3.zoom.us.": {},
+ "us03.ws-api.ringcentral.com.": {},
+ "us04asyncim.zoom.us.": {},
+ "us04web.zoom.us.": {},
+ "us04www3.zoom.us.": {},
+ "us05web.zoom.us.": {},
+ "us05www3.zoom.us.": {},
+ "us06log.zoom.us.": {},
+ "us06polling.zoom.us.": {},
+ "us06web.zoom.us.": {},
+ "us06www3.zoom.us.": {},
+ "us1.ecdn2.bumbcdn.com.": {},
+ "us2-live.inside-graph.com.": {},
+ "us2.rtbsystem.org.": {},
+ "us3a-excel-collab.officeapps.live.com.": {},
+ "us4-wms.zoho.com.": {},
+ "us4b-excel-collab.officeapps.live.com.": {},
+ "us4b-word-collab.officeapps.live.com.": {},
+ "us4s-excel-collab.officeapps.live.com.": {},
+ "us8.list-manage.com.": {},
+ "usbank.quantummetric.com.": {},
+ "usc-excel-collab.officeapps.live.com.": {},
+ "usc-powerpoint-collab.officeapps.live.com.": {},
+ "usc-word-collab.officeapps.live.com.": {},
+ "userexperience.thehut.net.": {},
+ "userflow.com.": {},
+ "userlike.com.": {},
+ "usgcorp-my.sharepoint.com.": {},
+ "usgcorp.sharepoint.com.": {},
+ "usgs.gov.": {},
+ "usii-my.sharepoint.com.": {},
+ "usii.sharepoint.com.": {},
+ "uslbm-my.sharepoint.com.": {},
+ "ussav.cynet.com.": {},
+ "usslb.cynet.com.": {},
+ "uu.qq.com.": {},
+ "uuidksinc.net.": {},
+ "ux.21cn.com.": {},
+ "uxfeedback.ru.": {},
+ "uyunad.com.": {},
+ "v.streaming.qq.com.": {},
+ "v.vivintsky.com.": {},
+ "v2-zj-shxcm.kwaicdn.com.": {},
+ "v39-as.tiktokcdn.com.": {},
+ "v39-ca.tiktokcdn.com.": {},
+ "v39-id.gts.byteoversea.net.": {},
+ "v39-id.tiktokcdn.com.": {},
+ "v39-my.tiktokcdn.com.": {},
+ "v39-tr.tiktokcdn.com.": {},
+ "v39-us.gts.byteoversea.net.": {},
+ "v39-us.tiktokcdn.com.": {},
+ "v39.byteicdn.com.": {},
+ "v6-gdvod.kwaicdn.com.": {},
+ "v8.analytics.pinsightmedia.com.": {},
+ "v8engine.pinsightmedia.com.": {},
+ "v9-iq.tiktokcdn.com.": {},
+ "v9-th.tiktokcdn.com.": {},
+ "va.v.liveperson.net.": {},
+ "vador.com.": {},
+ "vak345.com.": {},
+ "vancitycu.sharepoint.com.": {},
+ "vast.playmatic.video.": {},
+ "vb17123filippaaniketos.pw.": {},
+ "vbw.vivoglobal.com.": {},
+ "vcdn.cloud.": {},
+ "vclient-api.smoothwall.com.": {},
+ "vconf.f.360.cn.": {},
+ "verifone365-my.sharepoint.com.": {},
+ "verticals.wix.com.": {},
+ "veteransunited.sharepoint.com.": {},
+ "vexgateway.fastly.carvana.io.": {},
+ "vgorigin.hakunaymatata.com.": {},
+ "viadcorp-my.sharepoint.com.": {},
+ "vibe.co.": {},
+ "vibeaconstr.onezapp.com.": {},
+ "vicoo.tech.": {},
+ "video-atl3-1.xx.fbcdn.net.": {},
+ "video-atl3-2.xx.fbcdn.net.": {},
+ "video-bos5-1.xx.fbcdn.net.": {},
+ "video-den4-1.xx.fbcdn.net.": {},
+ "video-dfw5-1.xx.fbcdn.net.": {},
+ "video-dfw5-2.xx.fbcdn.net.": {},
+ "video-hou1-1.xx.fbcdn.net.": {},
+ "video-iad3-1.xx.fbcdn.net.": {},
+ "video-iad3-2.xx.fbcdn.net.": {},
+ "video-lax3-1.xx.fbcdn.net.": {},
+ "video-lax3-2.xx.fbcdn.net.": {},
+ "video-lga3-1.xx.fbcdn.net.": {},
+ "video-lga3-2.xx.fbcdn.net.": {},
+ "video-lhr6-1.xx.fbcdn.net.": {},
+ "video-lhr6-2.xx.fbcdn.net.": {},
+ "video-lhr8-1.xx.fbcdn.net.": {},
+ "video-lhr8-2.xx.fbcdn.net.": {},
+ "video-mia3-1.xx.fbcdn.net.": {},
+ "video-mia3-2.xx.fbcdn.net.": {},
+ "video-msp1-1.xx.fbcdn.net.": {},
+ "video-ord5-1.xx.fbcdn.net.": {},
+ "video-ord5-2.xx.fbcdn.net.": {},
+ "video-sea1-1.xx.fbcdn.net.": {},
+ "video-sjc3-1.xx.fbcdn.net.": {},
+ "video.dailymail.co.uk.": {},
+ "videocloud.cn-hangzhou.log.aliyuncs.com.": {},
+ "videoqoe-fastly.stvidtest.net.": {},
+ "videos.dailymail.co.uk.": {},
+ "vidmate.net.": {},
+ "vieon.vn.": {},
+ "view2fa.prosourcehosted.com.": {},
+ "vik-ca.moonactive.net.": {},
+ "vintagemonster.onefootball.com.": {},
+ "viously.com.": {},
+ "vipads.live.": {},
+ "virusinfo-cloudscan-cn.heytapmobi.com.": {},
+ "visainc-my.sharepoint.com.": {},
+ "visainc.sharepoint.com.": {},
+ "visionserviceplan-my.sharepoint.com.": {},
+ "visionserviceplan.sharepoint.com.": {},
+ "visit-prod-us.occa.ocs.oraclecloud.com.": {},
+ "visitor.fiftyt.com.": {},
+ "visitors.live.": {},
+ "vitalant-my.sharepoint.com.": {},
+ "vividseats.com.": {},
+ "vlscppe.microsoft.com.": {},
+ "vms-videos.minutemediaservices.com.": {},
+ "vn-viettel.rcs.telephony.goog.": {},
+ "vn.pool.ntp.org.": {},
+ "vod.ngb.haplat.net.": {},
+ "vod3.ngb.haplat.net.": {},
+ "vod5.ngb.haplat.net.": {},
+ "voe.sx.": {},
+ "voice.gcloudcs.com.": {},
+ "voice.telephony.goog.": {},
+ "voicecloud.cn.": {},
+ "void.omiapp.me.": {},
+ "volusiastudents-my.sharepoint.com.": {},
+ "vox.amp.permutive.com.": {},
+ "vpn.cshealthforlife.com.": {},
+ "vpn.garypools.com.": {},
+ "vpp-license-proxy.aliyuncs.com.": {},
+ "vzuu.com.": {},
+ "w.deepl.com.": {},
+ "w3.mp.lura.live.": {},
+ "w3.org.cdn.cloudflare.net.": {},
+ "w3dist.mp.lura.live.": {},
+ "w3id-ns.sso.ibm.com.edgekey.net.": {},
+ "wallpaperflare.com.": {},
+ "walmart.quantummetric.com.": {},
+ "walshgroup-my.sharepoint.com.": {},
+ "wap.cmpassport.com.": {},
+ "warsaw.remotepc.com.": {},
+ "washoeschools-my.sharepoint.com.": {},
+ "washoeschools.sharepoint.com.": {},
+ "waynek12inus-my.sharepoint.com.": {},
+ "wcpss-my.sharepoint.com.": {},
+ "wcpss.sharepoint.com.": {},
+ "weather-analytics-events.apple.com.": {},
+ "weather-server-sg.allawnos.com.": {},
+ "weather-widget-events.apple.com.": {},
+ "weather.swishapps.ai.": {},
+ "weathercn.com.": {},
+ "weatheroffer.com.": {},
+ "web-g.kontiki.com.": {},
+ "web.fe.1drv.com.": {},
+ "web.voice.telephony.goog.": {},
+ "web.wg1.kontiki.com.": {},
+ "web1.remotepc.com.": {},
+ "web3.drfirst.com.": {},
+ "webapi.teamviewer.com.": {},
+ "webmd.com.": {},
+ "weborama-tech.ru.": {},
+ "webrad.io.": {},
+ "wechatos.net.": {},
+ "weibocdn.com.": {},
+ "wellspan-my.sharepoint.com.": {},
+ "wellspan.sharepoint.com.": {},
+ "wesingapp.com.": {},
+ "wesingcdn.com.": {},
+ "westerngovernorsuniversity-my.sharepoint.com.": {},
+ "westpalm.remotepc.com.": {},
+ "westrockco-my.sharepoint.com.": {},
+ "westrockco.sharepoint.com.": {},
+ "wetype.weixin.qq.com.": {},
+ "weu.his.arc.azure.com.": {},
+ "wf-proxy-01.algolia.com.": {},
+ "wf-proxy-02.algolia.com.": {},
+ "wf-proxy-03.algolia.com.": {},
+ "whatismyip.akamai.com.": {},
+ "whatismyipaddress.com.": {},
+ "whizzco.com.": {},
+ "widebundle.com.": {},
+ "widgets.leadconnectorhq.com.": {},
+ "wifispot.io.": {},
+ "wildcard.cdn.optimizely.com.edgekey.net.": {},
+ "wildcard.marketo.net.edgekey.net.": {},
+ "wildcard.scene7.com.edgekey.net.": {},
+ "williamssonomainc-app.quantummetric.com.": {},
+ "win-rtb2-useast.xaprio.net.": {},
+ "win1807.ipv6.microsoft.com.": {},
+ "win1910.ipv6.microsoft.com.": {},
+ "winscp.net.": {},
+ "wkhpe.com.": {},
+ "wlgore-my.sharepoint.com.": {},
+ "wlgore.sharepoint.com.": {},
+ "wmg-my.sharepoint.com.": {},
+ "word-collab.officeapps.live.com.": {},
+ "workdaycdn.com.cn.": {},
+ "worldnic.com.": {},
+ "worldtimeserver.com.": {},
+ "worldwideexpress2323-my.sharepoint.com.": {},
+ "wosign.com.": {},
+ "wp.safe.360.cn.": {},
+ "wpk-auth.ucweb.com.": {},
+ "wpp.net.": {},
+ "wr.moyoung.com.": {},
+ "wr.pvp.net.": {},
+ "ws-assets.zoominfo.com.": {},
+ "ws-school.v.aaplimg.com.": {},
+ "ws.business.apple.com.": {},
+ "ws.gleap.io.": {},
+ "ws.mybib.com.": {},
+ "ws.qualified.com.": {},
+ "ws.reamaze.com.": {},
+ "ws.school.apple.com.": {},
+ "ws.tildacdn.com.": {},
+ "wsfcsk12nc-my.sharepoint.com.": {},
+ "wsfsbank-my.sharepoint.com.": {},
+ "wsfsbank.sharepoint.com.": {},
+ "wsms.haplat.net.": {},
+ "wsoversea.com.": {},
+ "wtpsnj-my.sharepoint.com.": {},
+ "wtsparadigm.com.": {},
+ "wunderground.com.": {},
+ "wus2.his.arc.azure.com.": {},
+ "wwt.sharepoint.com.": {},
+ "www.agoda.com.": {},
+ "www.aigconnect.aig.": {},
+ "www.automizely-analytics.com.": {},
+ "www.basspro.com.": {},
+ "www.bbc.co.uk.": {},
+ "www.bbc.co.uk.pri.bbc.co.uk.": {},
+ "www.bbc.com.": {},
+ "www.bbc.com.pri.bbc.com.": {},
+ "www.bbthat.com.": {},
+ "www.bose.com.": {},
+ "www.box.com.": {},
+ "www.breitbart.com.": {},
+ "www.bridge2bwb.com.": {},
+ "www.canva.com.": {},
+ "www.chewy.com.": {},
+ "www.cmpassport.com.": {},
+ "www.cnbc.com.": {},
+ "www.croxyproxy.com.": {},
+ "www.currys.co.uk.": {},
+ "www.dashlane.com.": {},
+ "www.directv.com.": {},
+ "www.discover.com.": {},
+ "www.docusign.com.": {},
+ "www.esosuite.net.": {},
+ "www.etsy.com.": {},
+ "www.evtrust.com.": {},
+ "www.fmcschedule.com.": {},
+ "www.gbnews.com.": {},
+ "www.google.org.": {},
+ "www.greatschools.org.": {},
+ "www.handmadewithjoann.com.": {},
+ "www.ifconfig.io.": {},
+ "www.imanageshare.com.": {},
+ "www.in.gov.": {},
+ "www.independent.co.uk.": {},
+ "www.inoreader.com.": {},
+ "www.jimmyjohns.com.": {},
+ "www.landsend.com.": {},
+ "www.lexisnexis.com.": {},
+ "www.life360.com.cdn.cloudflare.net.": {},
+ "www.llbean.net.": {},
+ "www.logmeininc.com.": {},
+ "www.loopnet.com.": {},
+ "www.macys.com.": {},
+ "www.mediamarkt.de.": {},
+ "www.mendeley.com.": {},
+ "www.menti.com.": {},
+ "www.myquickcloud.com.": {},
+ "www.nativecos.com.": {},
+ "www.nzz.ch.": {},
+ "www.overleaf.com.": {},
+ "www.pingler.com.": {},
+ "www.pool.ntp.org.": {},
+ "www.printfriendly.com.": {},
+ "www.rawstory.com.": {},
+ "www.searchanise.com.": {},
+ "www.sephora.com.edgekey.net.": {},
+ "www.sevenrooms.com.": {},
+ "www.shopdisney.com.": {},
+ "www.snokido.com.": {},
+ "www.soso.com.": {},
+ "www.standard.co.uk.": {},
+ "www.startssl.com.": {},
+ "www.tomodoko.com.": {},
+ "www.uber-assets.com.": {},
+ "www.usbank.com.": {},
+ "www.users.storage.live.com.": {},
+ "www.usnews.com.": {},
+ "www.vipads.live.": {},
+ "www.virustotal.com.": {},
+ "www.wifispot.io.": {},
+ "www.wix.com.": {},
+ "www.worldtimeserver.com.": {},
+ "www.yext-pixel.com.": {},
+ "www.zoom.us.": {},
+ "www1.remotepc.com.": {},
+ "www2.deepl.com.": {},
+ "www3.zoom.us.": {},
+ "wwwtvcdn.com.": {},
+ "wxliaotian.top.": {},
+ "wxqcloud.qq.com.": {},
+ "wxqcloud.qq.com.cn.": {},
+ "wyze.com.": {},
+ "x-flow.app.": {},
+ "x.research.qq.com.": {},
+ "xapads.com.": {},
+ "xbox-guide-public.rec.mp.microsoft.com.": {},
+ "xfs-hd01.batcg.org.": {},
+ "xfs-hd03.batcg.org.": {},
+ "xfs-hd04.batcg.org.": {},
+ "xfs-hd06.batcg.org.": {},
+ "xfs-hd07.batcg.org.": {},
+ "xfs-hd08.batcg.org.": {},
+ "xfs-hd09.batcg.org.": {},
+ "xfs-hd10.batcg.org.": {},
+ "xfs-s100.batcg.org.": {},
+ "xfs-s101.batcg.org.": {},
+ "xfs-s102.batcg.org.": {},
+ "xfs-s103.batcg.org.": {},
+ "xfs-s104.batcg.org.": {},
+ "xfs-s105.batcg.org.": {},
+ "xfs-s106.batcg.org.": {},
+ "xfs-s107.batcg.org.": {},
+ "xfs-s108.batcg.org.": {},
+ "xfs-s109.batcg.org.": {},
+ "xfs-s110.batcg.org.": {},
+ "xfs-s111.batcg.org.": {},
+ "xfs-s112.batcg.org.": {},
+ "xfs-s113.batcg.org.": {},
+ "xfs-s114.batcg.org.": {},
+ "xfs-s115.batcg.org.": {},
+ "xfs-s116.batcg.org.": {},
+ "xfs-s117.batcg.org.": {},
+ "xfs-s118.batcg.org.": {},
+ "xfs-s119.batcg.org.": {},
+ "xfs-s120.batcg.org.": {},
+ "xfs-s121.batcg.org.": {},
+ "xfs-s122.batcg.org.": {},
+ "xfs-s123.batcg.org.": {},
+ "xfs-s124.batcg.org.": {},
+ "xfs-s125.batcg.org.": {},
+ "xfs-s126.batcg.org.": {},
+ "xfs-s127.batcg.org.": {},
+ "xfs-s128.batcg.org.": {},
+ "xfs-s129.batcg.org.": {},
+ "xfs-s130.batcg.org.": {},
+ "xfs-uh00.batcg.org.": {},
+ "xiaoyi-css-us-15d.oss-us-west-1.aliyuncs.com.": {},
+ "xiaoyi-css-us-7d.oss-us-west-1.aliyuncs.com.": {},
+ "xiaoyi.com.": {},
+ "xjp-msgacs.m.taobao.com.": {},
+ "xlivesex.com.": {},
+ "xmcsrv.net.": {},
+ "xml-eu-v4.ezmob.com.": {},
+ "xml-v4.ezmob.com.": {},
+ "xml.acertb.com.": {},
+ "xml.cow-timerbudder.org.": {},
+ "xml.ezmob.com.": {},
+ "xml.green-resultsbid.com.": {},
+ "xml.popmonetizer.net.": {},
+ "xml.yellow-resultsbidder.com.": {},
+ "xml.zeusadx.com.": {},
+ "xnet.da.mgtv.com.": {},
+ "xp002.itsupport247.net.": {},
+ "xp004.itsupport247.net.": {},
+ "xp005.itsupport247.net.": {},
+ "xp006.itsupport247.net.": {},
+ "xp007.itsupport247.net.": {},
+ "xp008.itsupport247.net.": {},
+ "xp009.itsupport247.net.": {},
+ "xp015.itsupport247.net.": {},
+ "xp016.itsupport247.net.": {},
+ "xp017.itsupport247.net.": {},
+ "xp018.itsupport247.net.": {},
+ "xp019.itsupport247.net.": {},
+ "xpdrwp.itsupport247.net.": {},
+ "xtom.com.": {},
+ "yalla.live.": {},
+ "yealink.com.": {},
+ "ymmobi.com.": {},
+ "yomedia.vn.": {},
+ "yomeno.xyz.": {},
+ "youngjoygame.com.": {},
+ "yp.cdnstream1.com.": {},
+ "yunxindns.com.": {},
+ "z-m-scontent-lis1-1.xx.fbcdn.net.": {},
+ "z-m-scontent-ord5-2.xx.fbcdn.net.": {},
+ "z.cdn.adpool.bet.": {},
+ "z.cdn.ftd.agency.": {},
+ "z.cdn.trafficbass.com.": {},
+ "zego.im.": {},
+ "zemanta.com.": {},
+ "zimgs.cn.": {},
+ "zjcdn.com.yangyi19.com.": {},
+ "zmedia.vn.": {},
+ "zohopublic.eu.": {},
+ "zoom.us.": {},
+ "ztoken.uyunad.com.": {},
+ "zui.com.": {},
+ "zurich.remotepc.com.": {},
+}
diff --git a/internal/ecscache/ecsblocklist_generate.go b/internal/ecscache/ecsblocklist_generate.go
new file mode 100644
index 0000000..2a414a0
--- /dev/null
+++ b/internal/ecscache/ecsblocklist_generate.go
@@ -0,0 +1,74 @@
+//go:build generate
+
+package main
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+ "os"
+ "slices"
+ "text/template"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/golibs/httphdr"
+ "github.com/AdguardTeam/golibs/log"
+)
+
+func main() {
+ c := &http.Client{
+ Timeout: 10 * time.Second,
+ }
+
+ req, err := http.NewRequest(http.MethodGet, fakeECSBlocklistURL, nil)
+ check(err)
+
+ req.Header.Add(httphdr.UserAgent, agdhttp.UserAgent())
+
+ resp, err := c.Do(req)
+ check(err)
+ defer log.OnCloserError(resp.Body, log.ERROR)
+
+ out, err := os.OpenFile("./ecsblocklist.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o664)
+ check(err)
+ defer log.OnCloserError(out, log.ERROR)
+
+ contents, err := io.ReadAll(resp.Body)
+ check(err)
+
+ lines := bytes.Split(contents, []byte("\n"))
+ lines = lines[:len(lines)-1]
+
+ slices.SortStableFunc(lines, bytes.Compare)
+
+ tmpl, err := template.New("main").Parse(tmplStr)
+ check(err)
+
+ err = tmpl.Execute(out, lines)
+ check(err)
+}
+
+// fakeECSBlocklistURL is the default URL from where to get ECS fake domains.
+const fakeECSBlocklistURL = `https://filters.adtidy.org/dns/fake-ecs-blacklist`
+
+// tmplStr is the template of the generated Go code.
+const tmplStr = `// Code generated by go run ./ecsblocklist_generate.go; DO NOT EDIT.
+
+package ecscache
+
+// FakeECSFQDNs contains all domains that indicate ECS support, but in fact
+// don't have one.
+var FakeECSFQDNs = map[string]struct{}{
+{{- range $_, $h := . }}
+ {{ printf "\"%s.\": {}," $h }}
+{{- end }}
+}
+`
+
+// check is a simple error checker.
+func check(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/ecscache/ecscache.go b/internal/ecscache/ecscache.go
index a484dae..8f8f131 100644
--- a/internal/ecscache/ecscache.go
+++ b/internal/ecscache/ecscache.go
@@ -1,11 +1,10 @@
// Package ecscache implements a EDNS Client Subnet (ECS) aware DNS cache that
-// can be used as a dnsserver.Middleware.
+// can be used as a [dnsserver.Middleware].
package ecscache
import (
"context"
"fmt"
- "sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -17,14 +16,16 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/syncutil"
"github.com/bluele/gcache"
"github.com/miekg/dns"
)
-// EDNS Client Subnet (ECS) Aware LRU Cache
-
// Middleware is a dnsserver.Middleware with ECS-aware caching.
type Middleware struct {
+ // cloner is the memory-efficient cloner of DNS messages.
+ cloner *dnsmsg.Cloner
+
// cache is the LRU cache for results indicating no support for ECS.
cache gcache.Cache
@@ -35,7 +36,7 @@ type Middleware struct {
geoIP geoip.Interface
// cacheReqPool is a pool of cache requests.
- cacheReqPool *sync.Pool
+ cacheReqPool *syncutil.Pool[cacheRequest]
// cacheMinTTL is the minimum supported TTL for cache items.
cacheMinTTL time.Duration
@@ -46,6 +47,9 @@ type Middleware struct {
// MiddlewareConfig is the configuration structure for NewMiddleware.
type MiddlewareConfig struct {
+ // Cloner is used to clone messages taken from cache.
+ Cloner *dnsmsg.Cloner
+
// GeoIP is the GeoIP database used to get subnets for countries. It must
// not be nil.
GeoIP geoip.Interface
@@ -69,16 +73,15 @@ type MiddlewareConfig struct {
// be nil.
func NewMiddleware(c *MiddlewareConfig) (m *Middleware) {
return &Middleware{
- cache: gcache.New(c.Size).LRU().Build(),
- ecsCache: gcache.New(c.ECSSize).LRU().Build(),
+ cloner: c.Cloner,
+ cache: gcache.New(c.Size).LRU().Build(),
+ ecsCache: gcache.New(c.ECSSize).LRU().Build(),
+ geoIP: c.GeoIP,
+ cacheReqPool: syncutil.NewPool(func() (req *cacheRequest) {
+ return &cacheRequest{}
+ }),
cacheMinTTL: c.MinTTL,
useTTLOverride: c.UseTTLOverride,
- geoIP: c.GeoIP,
- cacheReqPool: &sync.Pool{
- New: func() (req any) {
- return &cacheRequest{}
- },
- },
}
}
@@ -109,11 +112,11 @@ func writeCachedResponse(
// writeResponse, where this information is retrieved from the upstream
metrics.ECSCacheLookupTotalHits.Inc()
- if respIsECSDependent {
- metrics.ECSCacheLookupHasSupportHits.Inc()
- } else {
- metrics.ECSCacheLookupNoSupportHits.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.
@@ -162,21 +165,28 @@ func ecsFamFromReq(ri *agd.RequestInfo) (ecsFam netutil.AddrFamily) {
return netutil.AddrFamilyIPv6
}
-// locFromReq returns the country and ASN from the request information using
-// either the contents of the EDNS Client Subnet option or the real remote
-// address.
-func locFromReq(ri *agd.RequestInfo) (ctry agd.Country, asn agd.ASN) {
+// locFromReq returns the location from the request information using either the
+// contents of the EDNS Client Subnet option or the real remote address.
+func locFromReq(ri *agd.RequestInfo) (l *geoip.Location) {
+ var ctry geoip.Country
+ var subdiv string
+ var asn geoip.ASN
if ecs := ri.ECS; ecs != nil && ecs.Location != nil {
ctry = ecs.Location.Country
+ subdiv = ecs.Location.TopSubdivision
asn = ecs.Location.ASN
}
- if ctry == agd.CountryNone && ri.Location != nil {
+ if ctry == geoip.CountryNone && ri.Location != nil {
ctry = ri.Location.Country
asn = ri.Location.ASN
}
- return ctry, asn
+ return &geoip.Location{
+ Country: ctry,
+ TopSubdivision: subdiv,
+ ASN: asn,
+ }
}
// writeUpstreamResponse processes, caches, and writes the response to rw as
@@ -202,12 +212,8 @@ func (mw *Middleware) writeUpstreamResponse(
metrics.ECSCacheLookupTotalMisses.Inc()
- // TODO(e.burkov, a.garipov): Think about ways to mitigate the situation
- // where an authoritative nameserver incorrectly echoes our ECS data.
- //
- // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.2.1.
- respIsECSDependent := scope != 0
- if respIsECSDependent {
+ respIsECS := respIsECSDependent(scope, req.Question[0].Name)
+ if respIsECS {
metrics.ECSCacheLookupHasSupportMisses.Inc()
metrics.ECSHasSupportCacheSize.Set(float64(mw.ecsCache.Len(false)))
} else {
@@ -217,7 +223,7 @@ func (mw *Middleware) writeUpstreamResponse(
cr.subnet = netutil.ZeroPrefix(ecsFam)
}
- mw.set(resp, cr, respIsECSDependent)
+ mw.set(resp, cr, respIsECS)
// Set the AD bit and ECS information here, where it is safe to do so, since
// a clone of the otherwise filtered response has already been set to cache.
@@ -259,7 +265,7 @@ func (mh *mwHandler) ServeDNS(
req *dns.Msg,
) (err error) {
mw := mh.mw
- cr := mw.cacheReqPool.Get().(*cacheRequest)
+ cr := mw.cacheReqPool.Get()
defer func() {
mw.cacheReqPool.Put(cr)
err = errors.Annotate(err, "ecs-cache: %w")
@@ -282,24 +288,29 @@ func (mh *mwHandler) ServeDNS(
cr.subnet = netutil.ZeroPrefix(ecsFam)
} else {
- ctry, asn := locFromReq(ri)
- cr.subnet, err = mw.geoIP.SubnetByLocation(ctry, asn, ecsFam)
+ loc := locFromReq(ri)
+ cr.subnet, err = mw.geoIP.SubnetByLocation(loc, ecsFam)
if err != nil {
- return fmt.Errorf("getting subnet for country %s (family: %d): %w", ctry, ecsFam, err)
+ return fmt.Errorf(
+ "getting subnet for country %s (family: %d): %w",
+ loc.Country,
+ ecsFam,
+ err,
+ )
}
- optlog.Debug3("ecscache: got ctry %s, asn %d, subnet %s", ctry, asn, cr.subnet)
+ optlog.Debug3("ecscache: got ctry %s, asn %d, subnet %s", loc.Country, loc.ASN, cr.subnet)
}
// Try getting a cached result using the subnet of the location or zero one
// when explicitly requested by user. If there is one, write, increment the
// metrics, and return. See also [writeCachedResponse].
- resp, found, respIsECSDependent := mw.get(req, cr)
- if found {
- optlog.Debug1("ecscache: using cached response (ecs-aware: %t)", respIsECSDependent)
+ resp, respIsECS := mw.get(req, cr)
+ if resp != nil {
+ optlog.Debug1("ecscache: using cached response (ecs-aware: %t)", respIsECS)
// Don't wrap the error, because it's informative enough as is.
- return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, respIsECSDependent)
+ return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, respIsECS)
}
log.Debug("ecscache: no cached response")
@@ -307,7 +318,8 @@ func (mh *mwHandler) ServeDNS(
// Perform an upstream request with the ECS data for the location or zero
// one on circumstances described above. If successful, write, increment
// the metrics, and return. See also [writeUpstreamResponse].
- ecsReq := dnsmsg.Clone(req)
+ ecsReq := mw.cloner.Clone(req)
+
err = setECS(ecsReq, &agd.ECS{
Subnet: cr.subnet,
Scope: 0,
@@ -330,3 +342,20 @@ func (mh *mwHandler) ServeDNS(
// Don't wrap the error, because it's informative enough as is.
return mw.writeUpstreamResponse(ctx, rw, req, resp, ri, cr, ecsFam)
}
+
+// respIsECSDependent returns true if the response should be considered as ESC
+// dependent.
+//
+// TODO(e.burkov, a.garipov): Think about ways to mitigate the situation
+// where an authoritative nameserver incorrectly echoes our ECS data.
+//
+// See https://datatracker.ietf.org/doc/html/rfc7871#section-7.2.1.
+func respIsECSDependent(scope uint8, host string) (ok bool) {
+ if scope == 0 {
+ return false
+ }
+
+ _, isFake := FakeECSFQDNs[host]
+
+ return !isFake
+}
diff --git a/internal/ecscache/ecscache_test.go b/internal/ecscache/ecscache_test.go
index 5bae3d4..0a67b96 100644
--- a/internal/ecscache/ecscache_test.go
+++ b/internal/ecscache/ecscache_test.go
@@ -14,11 +14,13 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/ecscache"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "golang.org/x/exp/maps"
)
func TestMain(m *testing.M) {
@@ -203,7 +205,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) {
withCache := newWithCache(
t,
handler,
- agd.CountryNone,
+ geoip.CountryNone,
netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
minTTL,
tc.minTTL != nil,
@@ -228,17 +230,16 @@ func TestMiddleware_Wrap_noECS(t *testing.T) {
}
}
-func TestMiddleware_Wrap_ecs(t *testing.T) {
- aReqNoECS := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
+const prefixLen = 24
+
+// newAReq returns new test A request with ECS option.
+func newAReq(hostname string, ip net.IP) (req *dns.Msg) {
+ aReqNoECS := dnsservertest.NewReq(hostname, dns.TypeA, dns.ClassINET)
aReqNoECS.SetEdns0(dnsmsg.DefaultEDNSUDPSize, false)
- aReq := aReqNoECS.Copy()
- opt := aReq.Extra[len(aReq.Extra)-1].(*dns.OPT)
+ req = aReqNoECS.Copy()
+ opt := req.Extra[len(req.Extra)-1].(*dns.OPT)
- const prefixLen = 24
-
- ip := net.IP{1, 2, 3, 0}
- subnet := netip.PrefixFrom(netip.AddrFrom4([4]byte(ip)), prefixLen)
opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
Family: uint16(netutil.AddrFamilyIPv4),
@@ -247,7 +248,18 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
Address: ip,
})
- const ctry = agd.CountryAD
+ return req
+}
+
+func TestMiddleware_Wrap_ecs(t *testing.T) {
+ aReqNoECS := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
+
+ ip := net.IP{1, 2, 3, 0}
+ aReq := newAReq(reqHostname, ip)
+ fakeECSReq := newAReq(maps.Keys(ecscache.FakeECSFQDNs)[0], ip)
+
+ subnet := netip.PrefixFrom(netip.AddrFrom4([4]byte(ip)), prefixLen)
+ const ctry = geoip.CountryAD
defaultCtrySubnet := netip.MustParsePrefix("1.2.0.0/16")
ecsExtra := dnsservertest.NewECSExtra(
@@ -260,14 +272,14 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
testCases := []struct {
req *dns.Msg
respECS dns.RR
- ecs *agd.ECS
+ wantECS *agd.ECS
ctrySubnet netip.Prefix
name string
}{{
req: aReq,
respECS: ecsExtra,
- ecs: &agd.ECS{
- Location: &agd.Location{
+ wantECS: &agd.ECS{
+ Location: &geoip.Location{
Country: ctry,
},
Subnet: subnet,
@@ -278,8 +290,8 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
}, {
req: aReq,
respECS: ecsExtra,
- ecs: &agd.ECS{
- Location: &agd.Location{
+ wantECS: &agd.ECS{
+ Location: &geoip.Location{
Country: ctry,
},
Subnet: subnet,
@@ -290,13 +302,13 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
}, {
req: aReqNoECS,
respECS: ecsExtra,
- ecs: nil,
+ wantECS: nil,
ctrySubnet: defaultCtrySubnet,
name: "edns_no_ecs",
}, {
req: aReq,
respECS: ecsExtra,
- ecs: nil,
+ wantECS: nil,
ctrySubnet: defaultCtrySubnet,
name: "country_from_ip",
}, {
@@ -307,15 +319,27 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
0,
0,
),
- ecs: &agd.ECS{
- Location: &agd.Location{
- Country: agd.CountryNone,
+ wantECS: &agd.ECS{
+ Location: &geoip.Location{
+ Country: geoip.CountryNone,
},
Subnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
Scope: 0,
},
ctrySubnet: defaultCtrySubnet,
name: "zero_ecs",
+ }, {
+ req: fakeECSReq,
+ respECS: ecsExtra,
+ wantECS: &agd.ECS{
+ Location: &geoip.Location{
+ Country: ctry,
+ },
+ Subnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
+ Scope: 0,
+ },
+ ctrySubnet: defaultCtrySubnet,
+ name: "fake_ecs_domain",
}}
const N = 5
@@ -344,10 +368,10 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
withCache := newWithCache(t, handler, ctry, tc.ctrySubnet, 0, false)
ri := &agd.RequestInfo{
- Location: &agd.Location{
+ Location: &geoip.Location{
Country: ctry,
},
- ECS: tc.ecs,
+ ECS: tc.wantECS,
Host: tc.req.Question[0].Name,
RemoteIP: remoteIP,
}
@@ -357,47 +381,39 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
msg = exchange(t, ri, withCache, tc.req)
}
require.NotNil(t, msg)
-
assert.Equal(t, 1, numReq)
require.NotEmpty(t, msg.Answer)
assert.Equal(t, defaultTTL, msg.Answer[0].Header().Ttl)
- respOpt := msg.IsEdns0()
- if tc.ecs == nil {
- if respOpt != nil {
- require.Empty(t, respOpt.Option)
- }
-
- return
- }
-
- require.Len(t, respOpt.Option, 1)
- subnetOpt := testutil.RequireTypeAssert[*dns.EDNS0_SUBNET](t, respOpt.Option[0])
-
- assert.Equal(t, net.IP(tc.ecs.Subnet.Addr().AsSlice()), subnetOpt.Address)
- assert.Equal(t, uint8(tc.ecs.Subnet.Bits()), subnetOpt.SourceNetmask)
- assert.Equal(t, uint8(tc.ecs.Subnet.Bits()), subnetOpt.SourceScope)
+ assertEDNSOpt(t, tc.wantECS, msg.IsEdns0())
})
}
}
+// assertEDNSOpt is a helper function that checks ECS and EDNS0 options.
+func assertEDNSOpt(t *testing.T, ecs *agd.ECS, edns *dns.OPT) {
+ t.Helper()
+
+ if ecs == nil {
+ if edns != nil {
+ assert.Empty(t, edns.Option)
+ }
+
+ return
+ }
+
+ require.Len(t, edns.Option, 1)
+ subnetOpt := testutil.RequireTypeAssert[*dns.EDNS0_SUBNET](t, edns.Option[0])
+
+ assert.Equal(t, net.IP(ecs.Subnet.Addr().AsSlice()), subnetOpt.Address)
+ assert.Equal(t, uint8(ecs.Subnet.Bits()), subnetOpt.SourceNetmask)
+ assert.Equal(t, uint8(ecs.Subnet.Bits()), subnetOpt.SourceScope)
+}
+
func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
// Helper values and functions
- const respSendTimeout = 1 * time.Second
-
- newResp := func(t *testing.T, req *dns.Msg, answer, extra dns.RR) (resp *dns.Msg) {
- t.Helper()
-
- return dnsservertest.NewResp(
- dns.RcodeSuccess,
- req,
- dnsservertest.SectionAnswer{answer},
- dnsservertest.SectionExtra{extra},
- )
- }
-
reqNoECS := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
reqNoECS.SetEdns0(dnsmsg.DefaultEDNSUDPSize, false)
@@ -423,7 +439,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
Address: netutil.IPv4Zero(),
})
- const ctry = agd.CountryAD
+ const ctry = geoip.CountryAD
ctrySubnet := netip.PrefixFrom(remoteIP, 16).Masked()
ctryECS := dnsservertest.NewECSExtra(
@@ -434,38 +450,17 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
)
zeroECS := dnsservertest.NewECSExtra(netutil.IPv4Zero(), uint16(netutil.AddrFamilyIPv4), 0, 0)
- pt := testutil.PanicT{}
- respCh := make(chan *dns.Msg, 1)
- handler := dnsserver.HandlerFunc(
- func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error {
- resp, ok := testutil.RequireReceive(pt, respCh, respSendTimeout)
- require.True(pt, ok)
-
- return rw.WriteMsg(ctx, req, resp)
- },
- )
-
answerA := dnsservertest.NewA(reqHostname, defaultTTL, netip.MustParseAddr("1.2.3.4"))
answerB := dnsservertest.NewA(reqHostname, defaultTTL, netip.MustParseAddr("5.6.7.8"))
// Tests
- // request is a single request in a sequence. answer and extra are
- // prerequisites for configuring handler's response before resolving msg,
- // those should be nil when the response is expected to come from cache.
- type request = struct {
- answer dns.RR
- extra dns.RR
- msg *dns.Msg
- wantAns []dns.RR
- }
-
testCases := []struct {
name string
- sequence []request
+ sequence sequence
}{{
name: "no_ecs_first",
- sequence: []request{{
+ sequence: sequence{{
answer: answerA,
extra: ctryECS,
msg: reqNoECS,
@@ -478,7 +473,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
}},
}, {
name: "ecs_first",
- sequence: []request{{
+ sequence: sequence{{
answer: answerA,
extra: ctryECS,
msg: reqWithECS,
@@ -491,7 +486,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
}},
}, {
name: "zero_after_no_ecs",
- sequence: []request{{
+ sequence: sequence{{
answer: answerA,
extra: ctryECS,
msg: reqNoECS,
@@ -504,7 +499,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
}},
}, {
name: "different_caches",
- sequence: []request{{
+ sequence: sequence{{
answer: answerA,
extra: ctryECS,
msg: reqWithECS,
@@ -527,7 +522,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
}},
}, {
name: "no_ecs_upstream",
- sequence: []request{{
+ sequence: sequence{{
answer: answerA,
extra: zeroECS,
msg: reqZeroECS,
@@ -551,38 +546,83 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
}}
for _, tc := range testCases {
- withCache := newWithCache(t, handler, ctry, ctrySubnet, 0, false)
-
t.Run(tc.name, func(t *testing.T) {
- for i, req := range tc.sequence {
- if req.answer != nil && req.extra != nil {
- resp := newResp(t, req.msg, req.answer, req.extra)
- testutil.RequireSend(t, respCh, resp, respSendTimeout)
- }
-
- subnet, _, err := dnsmsg.ECSFromMsg(req.msg)
- require.NoError(t, err)
-
- ri := &agd.RequestInfo{
- Location: &agd.Location{Country: ctry},
- ECS: nil,
- Host: req.msg.Question[0].Name,
- RemoteIP: remoteIP,
- }
- if subnet != (netip.Prefix{}) {
- ri.ECS = &agd.ECS{Subnet: subnet, Scope: 0}
- }
-
- // Make sure each step succeeded.
- require.True(t, t.Run(fmt.Sprintf("step_%d", i), func(t *testing.T) {
- got := exchange(t, ri, withCache, req.msg)
- assert.Equal(t, req.wantAns, got.Answer)
- }))
- }
+ tc.sequence.run(t, ctry, ctrySubnet)
})
}
}
+// request is a single request in a sequence. answer and extra are
+// prerequisites for configuring handler's response before resolving msg,
+// those should be nil when the response is expected to come from cache.
+type request = struct {
+ answer dns.RR
+ extra dns.RR
+ msg *dns.Msg
+ wantAns []dns.RR
+}
+
+// sequence is a list of requests.
+type sequence []request
+
+// run is a helper method for testing ECS cache middleware with sequence of
+// ordered requests.
+func (s sequence) run(t *testing.T, ctry geoip.Country, ctrySubnet netip.Prefix) {
+ t.Helper()
+
+ const respSendTimeout = 1 * time.Second
+
+ newResp := func(t *testing.T, req *dns.Msg, answer, extra dns.RR) (resp *dns.Msg) {
+ t.Helper()
+
+ return dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ req,
+ dnsservertest.SectionAnswer{answer},
+ dnsservertest.SectionExtra{extra},
+ )
+ }
+
+ pt := testutil.PanicT{}
+ respCh := make(chan *dns.Msg, 1)
+ handler := dnsserver.HandlerFunc(
+ func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error {
+ resp, ok := testutil.RequireReceive(pt, respCh, respSendTimeout)
+ require.True(pt, ok)
+
+ return rw.WriteMsg(ctx, req, resp)
+ },
+ )
+
+ withCache := newWithCache(t, handler, ctry, ctrySubnet, 0, false)
+
+ for i, req := range s {
+ if req.answer != nil && req.extra != nil {
+ resp := newResp(t, req.msg, req.answer, req.extra)
+ testutil.RequireSend(t, respCh, resp, respSendTimeout)
+ }
+
+ subnet, _, err := dnsmsg.ECSFromMsg(req.msg)
+ require.NoError(t, err)
+
+ ri := &agd.RequestInfo{
+ Location: &geoip.Location{Country: ctry},
+ ECS: nil,
+ Host: req.msg.Question[0].Name,
+ RemoteIP: remoteIP,
+ }
+ if subnet != (netip.Prefix{}) {
+ ri.ECS = &agd.ECS{Subnet: subnet, Scope: 0}
+ }
+
+ // Make sure each step succeeded.
+ require.True(t, t.Run(fmt.Sprintf("step_%d", i), func(t *testing.T) {
+ got := exchange(t, ri, withCache, req.msg)
+ assert.Equal(t, req.wantAns, got.Answer)
+ }))
+ }
+}
+
// exchange resolves req with h using context with ri.
func exchange(
t testing.TB,
@@ -610,7 +650,7 @@ func exchange(
func newWithCache(
t testing.TB,
h dnsserver.Handler,
- wantCtry agd.Country,
+ wantCtry geoip.Country,
geoIPNet netip.Prefix,
minTTL time.Duration,
useTTLOverride bool,
@@ -622,15 +662,14 @@ func newWithCache(
// TODO(a.garipov): Actually test ASNs once we have the data.
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
- ctry agd.Country,
- _ agd.ASN,
+ l *geoip.Location,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {
- require.Equal(pt, wantCtry, ctry)
+ require.Equal(pt, wantCtry, l.Country)
return geoIPNet, nil
},
- OnData: func(_ string, _ netip.Addr) (_ *agd.Location, _ error) {
+ OnData: func(_ string, _ netip.Addr) (_ *geoip.Location, _ error) {
panic("not implemented")
},
}
@@ -638,6 +677,7 @@ func newWithCache(
return dnsserver.WithMiddlewares(
h,
ecscache.NewMiddleware(&ecscache.MiddlewareConfig{
+ Cloner: agdtest.NewCloner(),
GeoIP: geoIP,
Size: 100,
ECSSize: 100,
diff --git a/internal/errcoll/errcoll.go b/internal/errcoll/errcoll.go
index 1574d92..839b300 100644
--- a/internal/errcoll/errcoll.go
+++ b/internal/errcoll/errcoll.go
@@ -1,19 +1,24 @@
+// Package errcoll contains implementations of error collectors, most notably
+// Sentry.
package errcoll
import (
+ "context"
"fmt"
- "runtime"
+
+ "github.com/AdguardTeam/golibs/log"
)
-// Common Functionality
-
-// caller returns the caller position using the appropriate depth.
-func caller(depth int) (callerPos string) {
- callerPos = ""
- _, callerFile, callerLine, ok := runtime.Caller(depth)
- if ok {
- callerPos = fmt.Sprintf("%s:%d", callerFile, callerLine)
- }
-
- return callerPos
+// Interface is the interface for error collectors that process information
+// about errors, possibly sending them to a remote location.
+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)
}
diff --git a/internal/errcoll/sentry.go b/internal/errcoll/sentry.go
index abf5edb..d3a289c 100644
--- a/internal/errcoll/sentry.go
+++ b/internal/errcoll/sentry.go
@@ -18,9 +18,7 @@ import (
"golang.org/x/sys/unix"
)
-// Sentry API Error Collector
-
-// SentryErrorCollector is an [agd.ErrorCollector] that sends errors to a
+// SentryErrorCollector is an [Interface] implementation that sends errors to a
// Sentry-like HTTP API.
type SentryErrorCollector struct {
sentry *sentry.Client
@@ -35,10 +33,9 @@ func NewSentryErrorCollector(cli *sentry.Client) (c *SentryErrorCollector) {
}
// type check
-var _ agd.ErrorCollector = (*SentryErrorCollector)(nil)
+var _ Interface = (*SentryErrorCollector)(nil)
-// Collect implements the [agd.ErrorCollector] interface for
-// *SentryErrorCollector.
+// 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)
@@ -58,7 +55,7 @@ func (c *SentryErrorCollector) Collect(ctx context.Context, err error) {
// ErrorFlushCollector collects information about errors, possibly sending them
// to a remote location. The collected errors should be flushed with the Flush.
type ErrorFlushCollector interface {
- agd.ErrorCollector
+ Interface
// Flush waits until the underlying transport sends any buffered events to
// the sentry server, blocking for at most the predefined timeout.
@@ -213,12 +210,12 @@ func tagsFromCtx(ctx context.Context) (tags sentryTags) {
tags["dns_server_proto"] = si.Proto.String()
}
- if ci, ok := dnsserver.ClientInfoFromContext(ctx); ok {
- tags["dns_client_tls_server_name"] = toASCII(ci.TLSServerName)
- if ci.URL != nil {
+ if ri, ok := dnsserver.RequestInfoFromContext(ctx); ok {
+ tags["dns_client_tls_server_name"] = toASCII(ri.TLSServerName)
+ if ri.URL != nil {
// Provide only the path and the query to fit into Sentry's 200
- // character limit.
- tags["dns_client_url_path"] = ci.URL.RequestURI()
+ // characters limit.
+ tags["dns_client_url_path"] = ri.URL.RequestURI()
}
}
diff --git a/internal/errcoll/sentry_test.go b/internal/errcoll/sentry_test.go
index fdee6df..e0c86f4 100644
--- a/internal/errcoll/sentry_test.go
+++ b/internal/errcoll/sentry_test.go
@@ -3,6 +3,7 @@ package errcoll_test
import (
"context"
"fmt"
+ "maps"
"testing"
"time"
@@ -13,7 +14,6 @@ import (
"github.com/getsentry/sentry-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "golang.org/x/exp/maps"
)
// testSentryTransport is a sentry.Transport for tests.
diff --git a/internal/errcoll/writer.go b/internal/errcoll/writer.go
index 3be16d0..faf28b9 100644
--- a/internal/errcoll/writer.go
+++ b/internal/errcoll/writer.go
@@ -1,5 +1,3 @@
-// Package errcoll contains implementations of the agd.ErrorCollector
-// interface.
package errcoll
import (
@@ -7,24 +5,44 @@ import (
"fmt"
"io"
"time"
+
+ "github.com/AdguardTeam/golibs/errors"
)
-// Simple Writer Collector
-
-// WriterErrorCollector is an agd.ErrorCollector that writes errors to a file.
+// WriterErrorCollector is an [Interface] implementation that writes errors to
+// an [io.Writer].
type WriterErrorCollector struct {
w io.Writer
}
-// NewWriterErrorCollector returns a new WriterErrorCollector.
+// NewWriterErrorCollector returns a new properly initialized
+// *WriterErrorCollector.
func NewWriterErrorCollector(w io.Writer) (c *WriterErrorCollector) {
return &WriterErrorCollector{
w: w,
}
}
-// Collect implements the agd.ErrorCollector interface for
-// *WriterErrorCollector.
+// type check
+var _ Interface = (*WriterErrorCollector)(nil)
+
+// Collect implements the [Interface] interface for *WriterErrorCollector.
func (c *WriterErrorCollector) Collect(ctx context.Context, err error) {
- _, _ = fmt.Fprintf(c.w, "%s: %s: caught error: %s\n", time.Now(), caller(2), err)
+ var (
+ sentryRepErr SentryReportableError
+ isIface bool
+ isReportable bool
+ )
+ if isIface = errors.As(err, &sentryRepErr); isIface {
+ isReportable = sentryRepErr.IsSentryReportable()
+ }
+
+ _, _ = fmt.Fprintf(
+ c.w,
+ "%s: caught error: %s (sentry iface: %t, reportable: %t)\n",
+ time.Now(),
+ err,
+ isIface,
+ isReportable,
+ )
}
diff --git a/internal/errcoll/writer_test.go b/internal/errcoll/writer_test.go
index 42ba129..5da95b2 100644
--- a/internal/errcoll/writer_test.go
+++ b/internal/errcoll/writer_test.go
@@ -15,7 +15,7 @@ func TestWriterErrorCollector(t *testing.T) {
c := errcoll.NewWriterErrorCollector(buf)
c.Collect(context.Background(), errors.Error("test error"))
- wantRx := `.*: .*errcoll/writer_test.go:[0-9]+: caught error: test error.*`
+ wantRx := `.*: caught error: test error.*`
got := buf.String()
assert.Regexp(t, wantRx, got)
}
diff --git a/internal/filter/filter_test.go b/internal/filter/filter_test.go
index e198952..724fdc4 100644
--- a/internal/filter/filter_test.go
+++ b/internal/filter/filter_test.go
@@ -182,6 +182,7 @@ func prepareConf(t testing.TB) (c *filter.DefaultStorageConfig) {
Now: time.Now,
ErrColl: nil,
Resolver: nil,
+ Cloner: agdtest.NewCloner(),
CacheDir: cacheDir,
CustomFilterCacheSize: 100,
SafeSearchCacheSize: 100,
diff --git a/internal/filter/hashprefix/filter.go b/internal/filter/hashprefix/filter.go
index 2378fab..4e6c523 100644
--- a/internal/filter/hashprefix/filter.go
+++ b/internal/filter/hashprefix/filter.go
@@ -9,7 +9,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "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/internal/resultcache"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
@@ -22,6 +24,9 @@ import (
// FilterConfig is the hash-prefix filter configuration structure.
type FilterConfig struct {
+ // Cloner is used to clone messages taken from filtering-result cache.
+ Cloner *dnsmsg.Cloner
+
// Hashes are the hostname hashes for this filter.
Hashes *Storage
@@ -29,7 +34,7 @@ type FilterConfig struct {
URL *url.URL
// ErrColl is used to collect non-critical and rare errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// Resolver is used to resolve hosts for the hash-prefix filter.
Resolver agdnet.Resolver
@@ -59,17 +64,18 @@ type FilterConfig struct {
CacheSize int
// MaxSize is the maximum size in bytes of the downloadable rule-list.
- MaxSize int64
+ MaxSize uint64
}
// Filter is a filter that matches hosts by their hashes based on a
// hash-prefix table.
type Filter struct {
+ cloner *dnsmsg.Cloner
hashes *Storage
refr *internal.Refreshable
resCache *resultcache.Cache[*internal.ResultModified]
resolver agdnet.Resolver
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
id agd.FilterListID
repHost string
}
@@ -78,6 +84,7 @@ type Filter struct {
func NewFilter(c *FilterConfig) (f *Filter, err error) {
id := c.ID
f = &Filter{
+ cloner: c.Cloner,
hashes: c.Hashes,
resCache: resultcache.New[*internal.ResultModified](c.CacheSize),
resolver: c.Resolver,
@@ -126,7 +133,7 @@ func (f *Filter) FilterRequest(
return nil, nil
}
- return rm.CloneForReq(req), nil
+ return rm.CloneForReq(f.cloner, req), nil
}
fam, ok := isFilterable(qt)
@@ -169,7 +176,7 @@ func (f *Filter) FilterRequest(
// down the pipeline don't interfere with the cached value.
//
// See AGDNS-359.
- f.resCache.Set(cacheKey, rm.Clone())
+ f.resCache.Set(cacheKey, rm.Clone(f.cloner))
f.updateCacheSizeMetrics(f.resCache.ItemCount())
return rm, nil
@@ -219,7 +226,7 @@ func (f *Filter) filteredResponse(
ips, err := f.resolver.LookupNetIP(ctx, fam, f.repHost)
if err != nil {
- agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err)
+ errcoll.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err)
return ri.Messages.NewMsgSERVFAIL(req), nil
}
@@ -263,17 +270,13 @@ func (f *Filter) updateCacheLookupsMetrics(hit bool) {
panic(fmt.Errorf("unsupported filter list id %s", id))
}
- if hit {
- hitsMetric.Inc()
- } else {
- missesMetric.Inc()
- }
+ metrics.IncrementCond(hit, hitsMetric, missesMetric)
}
// type check
-var _ agd.Refresher = (*Filter)(nil)
+var _ agdservice.Refresher = (*Filter)(nil)
-// Refresh implements the [agd.Refresher] interface for *hashPrefixFilter.
+// Refresh implements the [agdservice.Refresher] interface for *Filter.
func (f *Filter) Refresh(ctx context.Context) (err error) {
return f.refresh(ctx, false)
}
diff --git a/internal/filter/hashprefix/filter_test.go b/internal/filter/hashprefix/filter_test.go
index 0ecf68a..4b911ff 100644
--- a/internal/filter/hashprefix/filter_test.go
+++ b/internal/filter/hashprefix/filter_test.go
@@ -30,6 +30,7 @@ func TestFilter_FilterRequest(t *testing.T) {
replIP := netip.MustParseAddr("1.2.3.4")
f, err := hashprefix.NewFilter(&hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: strg,
URL: srvURL,
ErrColl: &agdtest.ErrorCollector{
@@ -209,6 +210,7 @@ func TestFilter_Refresh(t *testing.T) {
require.NoError(t, err)
f, err := hashprefix.NewFilter(&hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: strg,
URL: srvURL,
ErrColl: &agdtest.ErrorCollector{
@@ -264,6 +266,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) {
replIP := netip.MustParseAddr("1.2.3.4")
fconf := &hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: strg,
URL: srvURL,
ErrColl: &agdtest.ErrorCollector{
diff --git a/internal/filter/hashprefix/matcher.go b/internal/filter/hashprefix/matcher.go
index 7f83826..cc9eaee 100644
--- a/internal/filter/hashprefix/matcher.go
+++ b/internal/filter/hashprefix/matcher.go
@@ -76,17 +76,18 @@ func prefixesFromStr(prefixesStr string) (hashPrefixes []Prefix, err error) {
prefixSet := stringutil.NewSet()
prefixStrs := strings.Split(prefixesStr, ".")
for _, s := range prefixStrs {
- if len(s) != PrefixEncLen {
+ switch l := len(s); l {
+ case PrefixEncLen:
+ // A valid prefix; go on.
+ case legacyPrefixEncLen:
// Some legacy clients send eight-character hashes instead of
// four-character ones. For now, remove the final four characters.
//
// TODO(a.garipov): Either remove this crutch or support such
// prefixes better.
- if len(s) == legacyPrefixEncLen {
- s = s[:PrefixEncLen]
- } else {
- return nil, fmt.Errorf("bad hash len for %q", s)
- }
+ s = s[:PrefixEncLen]
+ default:
+ return nil, fmt.Errorf("bad hash len %d for %q", l, s)
}
prefixSet.Add(s)
diff --git a/internal/filter/hashprefix/matcher_test.go b/internal/filter/hashprefix/matcher_test.go
index 1c57288..3eed512 100644
--- a/internal/filter/hashprefix/matcher_test.go
+++ b/internal/filter/hashprefix/matcher_test.go
@@ -16,7 +16,7 @@ import (
// type check
//
// TODO(a.garipov): Move this into the actual package instead of keeping it in
-// the test package if [filter.Storage] and [filter.compFilter] are moved.
+// the test package if [filter.Storage] is moved.
var _ filter.HashMatcher = (*hashprefix.Matcher)(nil)
func TestMatcher(t *testing.T) {
diff --git a/internal/filter/hashprefix/storage.go b/internal/filter/hashprefix/storage.go
index 49a522f..5209b21 100644
--- a/internal/filter/hashprefix/storage.go
+++ b/internal/filter/hashprefix/storage.go
@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"sync"
+ "sync/atomic"
)
// Storage stores hashes of the filtered hostnames. All methods are safe for
@@ -14,19 +15,29 @@ import (
//
// TODO(a.garipov): See if we could unexport this.
type Storage struct {
- // mu protects hashSuffixes.
- mu *sync.RWMutex
- hashSuffixes map[Prefix][]suffix
+ // resetMu makes sure that only one reset is taking place at a time. It
+ // also protects prev.
+ resetMu *sync.Mutex
+
+ // hashSuffixes contains the hashSuffixes map. It is an atomic pointer to
+ // make sure that calls to [Storage.Reset] do not block [Storage.Matches]
+ // and thus filtering.
+ hashSuffixes *atomic.Pointer[suffixMap]
}
+// suffixMap is a convenient alias for a map of has prefixes to its suffixes.
+type suffixMap = map[Prefix][]suffix
+
// NewStorage returns a new hash storage containing hashes of the domain names
// listed in hostnames, one domain name per line.
func NewStorage(hostnames string) (s *Storage, err error) {
s = &Storage{
- mu: &sync.RWMutex{},
- hashSuffixes: map[Prefix][]suffix{},
+ resetMu: &sync.Mutex{},
+ hashSuffixes: &atomic.Pointer[suffixMap]{},
}
+ s.hashSuffixes.Store(&suffixMap{})
+
if hostnames != "" {
_, err = s.Reset(hostnames)
if err != nil {
@@ -46,13 +57,11 @@ func (s *Storage) Hashes(prefs []Prefix) (hashes []string) {
return nil
}
- s.mu.RLock()
- defer s.mu.RUnlock()
-
// First, calculate the number of hashes to allocate the buffer.
+ hashSuffixes := *s.hashSuffixes.Load()
l := 0
for _, pref := range prefs {
- hashSufs := s.hashSuffixes[pref]
+ hashSufs := hashSuffixes[pref]
l += len(hashSufs)
}
@@ -71,7 +80,7 @@ func (s *Storage) Hashes(prefs []Prefix) (hashes []string) {
// performance hit.
var buf [hashEncLen]byte
for _, pref := range prefs {
- hashSufs := s.hashSuffixes[pref]
+ hashSufs := hashSuffixes[pref]
for _, suf := range hashSufs {
// nolint:looppointer // Slicing is safe; used for encoding.
hex.Encode(buf[:], pref[:])
@@ -113,22 +122,24 @@ func (s *Storage) Matches(host string) (ok bool) {
return false
}
-// Reset resets the hosts in the index using the domain names listed in
-// hostnames, one domain name per line, and returns the total number of
-// processed rules.
-func (s *Storage) Reset(hostnames string) (n int, err error) {
- s.mu.Lock()
- defer s.mu.Unlock()
+// loadHashSuffixes returns hash suffixes for the given prefix. It is safe for
+// concurrent use. sufs must not be modified.
+func (s *Storage) loadHashSuffixes(pref Prefix) (sufs []suffix, ok bool) {
+ suffixes := *s.hashSuffixes.Load()
+ sufs, ok = suffixes[pref]
- // Delete all elements without allocating a new map to save space and
- // improve performance.
- //
- // This is optimized, see https://github.com/golang/go/issues/20138.
- //
- // TODO(a.garipov): Use clear once golang/go#56351 is implemented.
- for pref := range s.hashSuffixes {
- delete(s.hashSuffixes, pref)
- }
+ return sufs, ok
+}
+
+// Reset resets the hosts in the index using the domain names listed in
+// hostnames and returns the total number of processed rules. hostnames should
+// be a list of valid, lowercased domain names, one per line, and may include
+// empty lines and comments ('#' at the first position).
+func (s *Storage) Reset(hostnames string) (n int, err error) {
+ s.resetMu.Lock()
+ defer s.resetMu.Unlock()
+
+ next := make(suffixMap, len(*s.hashSuffixes.Load()))
sc := bufio.NewScanner(strings.NewReader(hostnames))
for sc.Scan() {
@@ -140,7 +151,7 @@ func (s *Storage) Reset(hostnames string) (n int, err error) {
sum := sha256.Sum256([]byte(host))
pref := Prefix(sum[:PrefixLen])
suf := suffix(sum[PrefixLen:])
- s.hashSuffixes[pref] = append(s.hashSuffixes[pref], suf)
+ next[pref] = append(next[pref], suf)
n++
}
@@ -150,16 +161,10 @@ func (s *Storage) Reset(hostnames string) (n int, err error) {
return 0, fmt.Errorf("scanning hosts: %w", err)
}
+ s.hashSuffixes.Store(&next)
+
+ // Do not try to clear and reuse the previous map. Any attempt to do that
+ // in a thread-safe fashion will result in excessive locking and complexity.
+
return n, nil
}
-
-// loadHashSuffixes returns hash suffixes for the given prefix. It is safe for
-// concurrent use. sufs must not be modified.
-func (s *Storage) loadHashSuffixes(pref Prefix) (sufs []suffix, ok bool) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- sufs, ok = s.hashSuffixes[pref]
-
- return sufs, ok
-}
diff --git a/internal/filter/hashprefix/storage_test.go b/internal/filter/hashprefix/storage_test.go
index 98f6a94..b77d625 100644
--- a/internal/filter/hashprefix/storage_test.go
+++ b/internal/filter/hashprefix/storage_test.go
@@ -43,10 +43,13 @@ func TestStorage_Reset(t *testing.T) {
s, err := hashprefix.NewStorage(testHost)
require.NoError(t, err)
+ assert.True(t, s.Matches(testHost))
+
n, err := s.Reset(testOtherHost)
require.NoError(t, err)
assert.Equal(t, 1, n)
+ assert.False(t, s.Matches(testHost))
h := sha256.Sum256([]byte(testOtherHost))
want := []string{hex.EncodeToString(h[:])}
@@ -58,6 +61,15 @@ func TestStorage_Reset(t *testing.T) {
prevHash := sha256.Sum256([]byte(testHost))
prev := s.Hashes([]hashprefix.Prefix{{prevHash[0], prevHash[1]}})
assert.Empty(t, prev)
+
+ // Reset again to make sure that the reuse of the map did not affect the
+ // results.
+ n, err = s.Reset(testOtherHost)
+ require.NoError(t, err)
+
+ assert.Equal(t, 1, n)
+ assert.False(t, s.Matches(testHost))
+ assert.True(t, s.Matches(testOtherHost))
}
// Sinks for benchmarks.
@@ -94,16 +106,16 @@ func BenchmarkStorage_Hashes(b *testing.B) {
})
}
- // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
+ // Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
- // BenchmarkStorage_Hashes/1-16 29928834 41.76 ns/op 0 B/op 0 allocs/op
- // BenchmarkStorage_Hashes/2-16 18693033 63.80 ns/op 0 B/op 0 allocs/op
- // BenchmarkStorage_Hashes/3-16 13492526 92.22 ns/op 0 B/op 0 allocs/op
- // BenchmarkStorage_Hashes/4-16 9542425 109.2 ns/op 0 B/op 0 allocs/op
+ // BenchmarkStorage_Hashes/1-16 156682185 40.31 ns/op 0 B/op 0 allocs/op
+ // BenchmarkStorage_Hashes/2-16 81397060 67.39 ns/op 0 B/op 0 allocs/op
+ // BenchmarkStorage_Hashes/3-16 61833548 104.3 ns/op 0 B/op 0 allocs/op
+ // BenchmarkStorage_Hashes/4-16 44809807 146.9 ns/op 0 B/op 0 allocs/op
}
func BenchmarkStorage_ResetHosts(b *testing.B) {
@@ -126,11 +138,11 @@ func BenchmarkStorage_ResetHosts(b *testing.B) {
require.NoError(b, errSink)
- // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
+ // Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
- // BenchmarkStorage_ResetHosts-16 2212 469343 ns/op 36224 B/op 1002 allocs/op
+ // BenchmarkStorage_ResetHosts-16 16890 344785 ns/op 101968 B/op 1006 allocs/op
}
diff --git a/internal/filter/index.go b/internal/filter/index.go
index 5b46200..32673fc 100644
--- a/internal/filter/index.go
+++ b/internal/filter/index.go
@@ -6,6 +6,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
)
// filterIndexResp is the struct for the JSON response from a filter index API.
@@ -30,13 +31,13 @@ type filterIndexFilterData struct {
// toInternal converts the filters from the index to []*filterIndexFilterData.
func (r *filterIndexResp) toInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
) (fls []*filterIndexFilterData) {
fls = make([]*filterIndexFilterData, 0, len(r.Filters))
for _, rf := range r.Filters {
id, err := agd.NewFilterListID(rf.ID)
if err != nil {
- agd.Collectf(ctx, errColl, "%s: validating id %q: %w", strgLogPrefix, rf.ID, err)
+ errcoll.Collectf(ctx, errColl, "%s: validating id %q: %w", strgLogPrefix, rf.ID, err)
continue
}
@@ -44,7 +45,7 @@ func (r *filterIndexResp) toInternal(
var u *url.URL
u, err = agdhttp.ParseHTTPURL(rf.DownloadURL)
if err != nil {
- agd.Collectf(
+ errcoll.Collectf(
ctx,
errColl,
"%s: validating url %q: %w",
diff --git a/internal/filter/internal/composite/composite.go b/internal/filter/internal/composite/composite.go
index f9d0bef..02bd248 100644
--- a/internal/filter/internal/composite/composite.go
+++ b/internal/filter/internal/composite/composite.go
@@ -128,7 +128,7 @@ func (f *Filter) FilterRequest(
// Firstly, check the profile's rule-list filtering, the custom rules, and
// the rules from blocked services settings.
host := ri.Host
- rlRes := f.filterWithRuleLists(ri, host, ri.QType, req, false)
+ rlRes := f.filterWithRuleLists(ri, host, ri.QType, req)
switch flRes := rlRes.(type) {
case *internal.ResultAllowed:
// Skip any additional filtering if the domain is explicitly allowed by
@@ -172,19 +172,7 @@ func (f *Filter) FilterResponse(
}
for _, ans := range resp.Answer {
- if rr, ok := ans.(*dns.HTTPS); ok {
- r = f.filterHTTPSRecords(rr, ri, resp)
- if r != nil {
- return r, nil
- }
- }
-
- host, rrType, ok := parseRespAnswer(ans)
- if !ok {
- continue
- }
-
- r = f.filterWithRuleLists(ri, host, rrType, resp, true)
+ r = f.filterAnswer(ri, ans)
if r != nil {
break
}
@@ -193,17 +181,28 @@ func (f *Filter) FilterResponse(
return r, nil
}
-// filterHTTPSRecords filters HTTPS answers information through all rule list
+// filterAnswer filters a single answer of a response. r is not nil if the
+// response is filtered.
+func (f *Filter) filterAnswer(ri *agd.RequestInfo, ans dns.RR) (r internal.Result) {
+ if rr, ok := ans.(*dns.HTTPS); ok {
+ return f.filterHTTPSAnswer(ri, rr)
+ }
+
+ host, rrType, ok := parseRespAnswer(ans)
+ if !ok {
+ return nil
+ }
+
+ return f.filterWithRuleLists(ri, host, rrType, nil)
+}
+
+// filterHTTPSAnswer filters HTTPS answers information through all rule list
// filters of the composite filter.
-func (f *Filter) filterHTTPSRecords(
- rr *dns.HTTPS,
- ri *agd.RequestInfo,
- resp *dns.Msg,
-) (r internal.Result) {
+func (f *Filter) filterHTTPSAnswer(ri *agd.RequestInfo, rr *dns.HTTPS) (r internal.Result) {
for _, kv := range rr.Value {
switch kv.Key() {
case dns.SVCB_IPV4HINT, dns.SVCB_IPV6HINT:
- r = f.filterSVCBHint(kv.String(), ri, resp)
+ r = f.filterSVCBHint(kv.String(), ri)
if r != nil {
return r
}
@@ -220,10 +219,9 @@ func (f *Filter) filterHTTPSRecords(
func (f *Filter) filterSVCBHint(
hint string,
ri *agd.RequestInfo,
- resp *dns.Msg,
) (r internal.Result) {
for _, s := range strings.Split(hint, ",") {
- r = f.filterWithRuleLists(ri, s, dns.TypeHTTPS, resp, true)
+ r = f.filterWithRuleLists(ri, s, dns.TypeHTTPS, nil)
if r != nil {
return r
}
@@ -258,13 +256,13 @@ func (f *Filter) isEmpty() (ok bool) {
}
// filterWithRuleLists filters one question's or answer's information through
-// all rule list filters of the composite filter.
+// all rule list filters of the composite filter. If req is nil, the message is
+// assumed to be a response.
func (f *Filter) filterWithRuleLists(
ri *agd.RequestInfo,
host string,
rrType dnsmsg.RRType,
- msg *dns.Msg,
- isAnswer bool,
+ req *dns.Msg,
) (r internal.Result) {
var devName string
if d := ri.Device; d != nil {
@@ -272,6 +270,7 @@ func (f *Filter) filterWithRuleLists(
}
ufRes := &urlFilterResult{}
+ isAnswer := req == nil
for _, rl := range f.ruleLists {
ufRes.add(rl.DNSResult(ri.RemoteIP, devName, host, rrType, isAnswer))
}
@@ -282,7 +281,7 @@ func (f *Filter) filterWithRuleLists(
// dnsrewrite rules only from one list, cause when there is no problem
// with merging them among different lists.
if !isAnswer {
- modified := processDNSRewrites(ri.Messages, msg, dr.DNSRewrites(), host)
+ modified := processDNSRewrites(ri.Messages, req, dr.DNSRewrites(), host)
if modified != nil {
return modified
}
@@ -295,8 +294,7 @@ func (f *Filter) filterWithRuleLists(
ufRes.add(rl.DNSResult(ri.RemoteIP, devName, host, rrType, isAnswer))
}
- mr := rules.NewMatchingResult(ufRes.networkRules, nil)
- if nr := mr.GetBasicResult(); nr != nil {
+ if nr := rules.GetDNSBasicRule(ufRes.networkRules); nr != nil {
return f.ruleDataToResult(nr.FilterListID, nr.RuleText, nr.Whitelist)
}
diff --git a/internal/filter/internal/composite/composite_internal_test.go b/internal/filter/internal/composite/composite_internal_test.go
new file mode 100644
index 0000000..fb097c2
--- /dev/null
+++ b/internal/filter/internal/composite/composite_internal_test.go
@@ -0,0 +1,52 @@
+package composite
+
+import (
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "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/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
+)
+
+func BenchmarkFilter_FilterWithRuleLists(b *testing.B) {
+ blockingRL, err := rulelist.NewFromString(filtertest.BlockRule+"\n", "test", "", 0, false)
+ require.NoError(b, err)
+
+ f := New(&Config{
+ RuleLists: []*rulelist.Refreshable{blockingRL},
+ })
+
+ req := dnsservertest.NewReq(filtertest.ReqFQDN, dns.TypeA, dns.ClassINET)
+ ri := &agd.RequestInfo{
+ Messages: dnsmsg.NewConstructor(nil, &dnsmsg.BlockingModeNullIP{}, filtertest.Staleness),
+ RemoteIP: filtertest.RemoteIP,
+ Host: filtertest.ReqHost,
+ QType: dns.TypeA,
+ QClass: dns.ClassINET,
+ }
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ resultSink = f.filterWithRuleLists(ri, filtertest.ReqHost, dns.TypeCNAME, req)
+ }
+
+ // Most recent results, on a MBP 14 with Apple M1 Pro chip:
+ //
+ // goos: darwin
+ // goarch: arm64
+ // pkg: github.com/AdguardTeam/urlfilter
+ // BenchmarkFilter_FilterWithRuleLists
+ // BenchmarkFilter_FilterWithRuleLists-8 1623212 698.0 ns/op 161 B/op 6 allocs/op
+}
diff --git a/internal/filter/internal/composite/composite_test.go b/internal/filter/internal/composite/composite_test.go
index 73cbcda..e35914a 100644
--- a/internal/filter/internal/composite/composite_test.go
+++ b/internal/filter/internal/composite/composite_test.go
@@ -56,7 +56,7 @@ func newImmutable(tb testing.TB, text string, id agd.FilterListID) (rl *rulelist
}
// newReqData returns data for calling FilterRequest. The context uses
-// [filtertest.Timeout] and [tb.Cleanup] is used for its cancellation. Both req
+// [filtertest.Timeout] and [tb.Cleanup] is used for its cancelation. Both req
// and ri use [filtertest.ReqFQDN], [dns.TypeA], and [dns.ClassINET] for the
// request data.
func newReqData(tb testing.TB) (ctx context.Context, req *dns.Msg, ri *agd.RequestInfo) {
@@ -66,6 +66,7 @@ func newReqData(tb testing.TB) (ctx context.Context, req *dns.Msg, ri *agd.Reque
req = dnsservertest.NewReq(filtertest.ReqFQDN, dns.TypeA, dns.ClassINET)
ri = &agd.RequestInfo{
Messages: agdtest.NewConstructor(),
+ RemoteIP: filtertest.RemoteIP,
Host: filtertest.ReqHost,
QType: dns.TypeA,
QClass: dns.ClassINET,
@@ -104,6 +105,39 @@ func TestFilter_nil(t *testing.T) {
}
}
+func TestFilter_FilterRequest_client(t *testing.T) {
+ const (
+ devName = "MyDevice"
+ blockRule = filtertest.BlockRule + "$client=" + devName
+ )
+
+ rl := newFromStr(t, blockRule, testFltListID1)
+
+ wantRes := &internal.ResultBlocked{
+ List: testFltListID1,
+ Rule: blockRule,
+ }
+
+ f := composite.New(&composite.Config{
+ RuleLists: []*rulelist.Refreshable{rl},
+ })
+
+ ctx, req, ri := newReqData(t)
+ res, err := f.FilterRequest(ctx, req, ri)
+ require.NoError(t, err)
+
+ assert.Nil(t, res)
+
+ ri.Device = &agd.Device{
+ Name: devName,
+ }
+
+ res, err = f.FilterRequest(ctx, req, ri)
+ require.NoError(t, err)
+
+ assert.Equal(t, wantRes, res)
+}
+
func TestFilter_FilterRequest_badfilter(t *testing.T) {
const (
blockRule = filtertest.BlockRule
@@ -410,6 +444,7 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) {
const fltListID = agd.FilterListIDGeneralSafeSearch
gen := safesearch.New(&safesearch.Config{
+ Cloner: agdtest.NewCloner(),
Refreshable: &internal.RefreshableConfig{
URL: srvURL,
ID: fltListID,
diff --git a/internal/filter/internal/composite/dnsrewrite.go b/internal/filter/internal/composite/dnsrewrite.go
index 73fcfa4..ddaab3d 100644
--- a/internal/filter/internal/composite/dnsrewrite.go
+++ b/internal/filter/internal/composite/dnsrewrite.go
@@ -225,10 +225,10 @@ func newAnsFromString(
}
if rr == dns.TypeTXT {
- return messages.NewAnsTXT(req, []string{str})
+ return messages.NewAnswerTXT(req, []string{str})
}
- return messages.NewAnsPTR(req, str), nil
+ return messages.NewAnswerPTR(req, str), nil
}
// newAnsFromIP returns a new resource record with an IP address. ip must be an
@@ -244,11 +244,13 @@ func newAnsFromIP(
return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v)
}
+ target := req.Question[0].Name
+
if rr == dns.TypeA {
- return messages.NewAnsA(req, ip)
+ return messages.NewAnswerA(target, ip)
}
- return messages.NewAnsAAAA(req, ip)
+ return messages.NewAnswerAAAA(target, ip)
}
// newAnswerMX returns a new resource record created from DNSMX rules value.
diff --git a/internal/filter/internal/custom/custom.go b/internal/filter/internal/custom/custom.go
index eccb4e9..9f1e973 100644
--- a/internal/filter/internal/custom/custom.go
+++ b/internal/filter/internal/custom/custom.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
@@ -20,11 +21,11 @@ import (
// Filters contains custom filters made from custom filtering rules of profiles.
type Filters struct {
cache gcache.Cache
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
}
// New returns a new custom filter storage.
-func New(cache gcache.Cache, errColl agd.ErrorCollector) (f *Filters) {
+func New(cache gcache.Cache, errColl errcoll.Interface) (f *Filters) {
return &Filters{
cache: cache,
errColl: errColl,
@@ -43,11 +44,11 @@ func (f *Filters) Get(ctx context.Context, p *agd.Profile) (rl *rulelist.Immutab
// Report the custom filters cache lookup to prometheus so that we could
// keep track of whether the cache size is enough.
defer func() {
- if rl == nil {
- metrics.FilterCustomCacheLookupsMisses.Inc()
- } else {
- metrics.FilterCustomCacheLookupsHits.Inc()
- }
+ metrics.IncrementCond(
+ rl == nil,
+ metrics.FilterCustomCacheLookupsMisses,
+ metrics.FilterCustomCacheLookupsHits,
+ )
}()
rl = f.get(p)
diff --git a/internal/filter/internal/filtertest/filtertest.go b/internal/filter/internal/filtertest/filtertest.go
index dd67877..76d9789 100644
--- a/internal/filter/internal/filtertest/filtertest.go
+++ b/internal/filter/internal/filtertest/filtertest.go
@@ -47,7 +47,7 @@ const Timeout = 1 * time.Second
// FilterMaxSize is the maximum size of the downloadable rule-list for filtering
// tests.
-const FilterMaxSize = 640 * int64(datasize.KB)
+const FilterMaxSize = 640 * uint64(datasize.KB)
// PrepareRefreshable launches an HTTP server serving the given text and code,
// as well as creates a cache file. If code is zero, the server isn't started.
diff --git a/internal/filter/internal/refreshable.go b/internal/filter/internal/refreshable.go
index e850576..5d00206 100644
--- a/internal/filter/internal/refreshable.go
+++ b/internal/filter/internal/refreshable.go
@@ -14,9 +14,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdio"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
+ "github.com/AdguardTeam/golibs/ioutil"
"github.com/AdguardTeam/golibs/log"
renameio "github.com/google/renameio/v2"
)
@@ -29,7 +29,7 @@ type Refreshable struct {
id agd.FilterListID
cachePath string
staleness time.Duration
- maxSize int64
+ maxSize uint64
}
// RefreshableConfig is the configuration structure for a refreshable filter.
@@ -51,7 +51,7 @@ type RefreshableConfig struct {
Timeout time.Duration
// MaxSize is the maximum size in bytes of the downloadable filter content.
- MaxSize int64
+ MaxSize uint64
}
// NewRefreshable returns a new refreshable filter. c must not be nil.
@@ -181,7 +181,7 @@ func (f *Refreshable) refreshFromURL(
b := &strings.Builder{}
mw := io.MultiWriter(b, tmpFile)
- _, err = io.Copy(mw, agdio.LimitReader(resp.Body, f.maxSize))
+ _, err = io.Copy(mw, ioutil.LimitReader(resp.Body, f.maxSize))
if err != nil {
return "", agdhttp.WrapServerError(fmt.Errorf("reading into file: %w", err), resp)
}
diff --git a/internal/filter/internal/result.go b/internal/filter/internal/result.go
index d0b2a07..08edf2e 100644
--- a/internal/filter/internal/result.go
+++ b/internal/filter/internal/result.go
@@ -83,17 +83,19 @@ func (m *ResultModified) MatchedRule() (id agd.FilterListID, text agd.FilterRule
func (*ResultModified) isResult() {}
// Clone returns a deep clone of m.
-func (m *ResultModified) Clone() (clone *ResultModified) {
+func (m *ResultModified) Clone(c *dnsmsg.Cloner) (clone *ResultModified) {
+ msg := c.Clone(m.Msg)
+
return &ResultModified{
- Msg: dnsmsg.Clone(m.Msg),
+ Msg: msg,
List: m.List,
Rule: m.Rule,
}
}
// CloneForReq returns a deep clone of m with Msg set as a reply to req, if any.
-func (m *ResultModified) CloneForReq(req *dns.Msg) (clone *ResultModified) {
- msg := dnsmsg.Clone(m.Msg)
+func (m *ResultModified) CloneForReq(c *dnsmsg.Cloner, req *dns.Msg) (clone *ResultModified) {
+ msg := c.Clone(m.Msg)
// TODO(a.garipov): This will become invalid if Msg ever contains a
// non-success response, which is not the case currently. If that happens,
diff --git a/internal/filter/internal/safesearch/safesearch.go b/internal/filter/internal/safesearch/safesearch.go
index 63e0ddc..6a4e786 100644
--- a/internal/filter/internal/safesearch/safesearch.go
+++ b/internal/filter/internal/safesearch/safesearch.go
@@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"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/internal/resultcache"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
@@ -22,15 +23,19 @@ import (
// Filter modifies the results of queries to search-engine addresses and
// rewrites them to the IP addresses of their safe versions.
type Filter struct {
+ cloner *dnsmsg.Cloner
resCache *resultcache.Cache[*internal.ResultModified]
flt *rulelist.Refreshable
resolver agdnet.Resolver
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
id agd.FilterListID
}
// Config contains configuration for the safe-search filter.
type Config struct {
+ // Cloner is used to clone messages taken from filtering-result cache.
+ Cloner *dnsmsg.Cloner
+
// Refreshable is the configuration of the refreshable filter-list within
// the safe-search filter.
Refreshable *internal.RefreshableConfig
@@ -39,7 +44,7 @@ type Config struct {
Resolver agdnet.Resolver
// ErrColl is used to report errors of replacement-host resolving.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// CacheTTL is the time to live of the result cache-items.
//
@@ -54,6 +59,7 @@ type Config struct {
// refresh should be called explicitly if necessary.
func New(c *Config) (f *Filter) {
return &Filter{
+ cloner: c.Cloner,
resCache: resultcache.New[*internal.ResultModified](c.CacheSize),
// Don't use the rule list cache, since safeSearch already has its own.
flt: rulelist.NewRefreshable(c.Refreshable, 0, false),
@@ -89,10 +95,10 @@ func (f *Filter) FilterRequest(
return nil, nil
}
- return rm.CloneForReq(req), nil
+ return rm.CloneForReq(f.cloner, req), nil
}
- repHost, ok := f.safeSearchHost(host, qt)
+ repHost, ip, ok := f.safeSearchHost(host, qt)
if !ok {
optlog.Debug2("filter %s: host %q is not on the list", f.id, host)
@@ -101,22 +107,9 @@ func (f *Filter) FilterRequest(
return nil, nil
}
- optlog.Debug2("filter %s: found host %q", f.id, repHost)
-
- ctx, cancel := context.WithTimeout(ctx, internal.DefaultResolveTimeout)
- defer cancel()
-
- var result *dns.Msg
- ips, err := f.resolver.LookupNetIP(ctx, fam, repHost)
+ result, err := f.newRespMsg(ctx, req, fam, ri, ip, repHost)
if err != nil {
- agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err)
-
- result = ri.Messages.NewMsgSERVFAIL(req)
- } else {
- result, err = ri.Messages.NewIPRespMsg(req, ips...)
- if err != nil {
- return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err)
- }
+ return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err)
}
rm = &internal.ResultModified{
@@ -129,7 +122,7 @@ func (f *Filter) FilterRequest(
// down the pipeline don't interfere with the cached value.
//
// See AGDNS-359.
- f.resCache.Set(cacheKey, rm.Clone())
+ f.resCache.Set(cacheKey, rm.Clone(f.cloner))
return rm, nil
}
@@ -139,12 +132,50 @@ func (f *Filter) ID() (id agd.FilterListID) {
return f.id
}
+// newRespMsg returns a response messages for provided ip and host.
+func (f *Filter) newRespMsg(
+ ctx context.Context,
+ req *dns.Msg,
+ fam netutil.AddrFamily,
+ ri *agd.RequestInfo,
+ ip netip.Addr,
+ cname string,
+) (msg *dns.Msg, err error) {
+ host := cname
+ if ip != (netip.Addr{}) {
+ // TODO(d.kolyshev): There is no need to resolve this IP if it matches
+ // question fam.
+ host = ip.String()
+ }
+
+ optlog.Debug2("filter %s: found host %q", f.id, host)
+
+ ctx, cancel := context.WithTimeout(ctx, internal.DefaultResolveTimeout)
+ defer cancel()
+
+ ips, err := f.resolver.LookupNetIP(ctx, fam, host)
+ if err != nil {
+ errcoll.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err)
+
+ return ri.Messages.NewMsgSERVFAIL(req), nil
+ }
+
+ if cname != "" {
+ return ri.Messages.NewCNAMEWithIPs(req, dns.Fqdn(cname), ips...)
+ }
+
+ return ri.Messages.NewIPRespMsg(req, ips...)
+}
+
// safeSearchHost returns the replacement host for the given host and question
// type, if any. qt should be either [dns.TypeA] or [dns.TypeAAAA].
-func (f *Filter) safeSearchHost(host string, qt dnsmsg.RRType) (ssHost string, ok bool) {
+func (f *Filter) safeSearchHost(
+ host string,
+ qt dnsmsg.RRType,
+) (ssHost string, ip netip.Addr, ok bool) {
dr := f.flt.DNSResult(netip.Addr{}, "", host, qt, false)
if dr == nil {
- return "", false
+ return "", netip.Addr{}, false
}
for _, nr := range dr.DNSRewrites() {
@@ -154,20 +185,20 @@ func (f *Filter) safeSearchHost(host string, qt dnsmsg.RRType) (ssHost string, o
}
if nc := drw.NewCNAME; nc != "" {
- return nc, true
+ return nc, netip.Addr{}, true
}
// All the rules in safe search rule lists are expected to have either
// A/AAAA or CNAME type.
switch drw.RRType {
case dns.TypeA, dns.TypeAAAA:
- return drw.Value.(netip.Addr).String(), true
+ return "", drw.Value.(netip.Addr), true
default:
continue
}
}
- return "", false
+ return "", netip.Addr{}, false
}
// Refresh reloads the rule list data. If acceptStale is true, and the cache
diff --git a/internal/filter/internal/safesearch/safesearch_test.go b/internal/filter/internal/safesearch/safesearch_test.go
index b1f4421..6e5f821 100644
--- a/internal/filter/internal/safesearch/safesearch_test.go
+++ b/internal/filter/internal/safesearch/safesearch_test.go
@@ -60,6 +60,7 @@ func TestFilter(t *testing.T) {
require.NoError(t, err)
f := safesearch.New(&safesearch.Config{
+ Cloner: agdtest.NewCloner(),
Refreshable: &internal.RefreshableConfig{
ID: id,
URL: srvURL,
@@ -175,11 +176,14 @@ func TestFilter(t *testing.T) {
require.NoError(t, fltErr)
rm := testutil.RequireTypeAssert[*internal.ResultModified](t, res)
- require.Len(t, rm.Msg.Answer, 1)
+ require.Len(t, rm.Msg.Answer, 2)
assert.Equal(t, rm.Rule, agd.FilterRuleText(testEngineWithDomain))
- a := testutil.RequireTypeAssert[*dns.A](t, rm.Msg.Answer[0])
+ cname := testutil.RequireTypeAssert[*dns.CNAME](t, rm.Msg.Answer[0])
+ assert.Equal(t, dns.Fqdn(testSafeDomain), cname.Target)
+
+ a := testutil.RequireTypeAssert[*dns.A](t, rm.Msg.Answer[1])
assert.Equal(t, net.IP(testIPOfEngineWithDomain.AsSlice()), a.A)
})
}
diff --git a/internal/filter/internal/serviceblock/index.go b/internal/filter/internal/serviceblock/index.go
index 3524ea4..f6f4f5e 100644
--- a/internal/filter/internal/serviceblock/index.go
+++ b/internal/filter/internal/serviceblock/index.go
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
@@ -20,7 +21,7 @@ type indexResp struct {
// toInternal converts the services from the index to serviceRuleLists.
func (r *indexResp) toInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
cacheSize int,
useCache bool,
) (services serviceRuleLists, err error) {
@@ -64,7 +65,7 @@ type indexRespService struct {
// toInternal converts the service from the index to a rule-list filter.
func (svc *indexRespService) toInternal(
ctx context.Context,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
cacheSize int,
useCache bool,
) (svcID agd.BlockedServiceID, rl *rulelist.Immutable, err error) {
diff --git a/internal/filter/internal/serviceblock/serviceblock.go b/internal/filter/internal/serviceblock/serviceblock.go
index 0045f3a..10b762f 100644
--- a/internal/filter/internal/serviceblock/serviceblock.go
+++ b/internal/filter/internal/serviceblock/serviceblock.go
@@ -11,6 +11,7 @@ import (
"sync"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "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"
@@ -32,14 +33,14 @@ type Filter struct {
services serviceRuleLists
// errColl used to collect non-critical and rare errors.
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
}
// serviceRuleLists is convenient alias for an ID to filter mapping.
type serviceRuleLists = map[agd.BlockedServiceID]*rulelist.Immutable
// New returns a fully initialized service blocker.
-func New(refr *internal.Refreshable, errColl agd.ErrorCollector) (f *Filter) {
+func New(refr *internal.Refreshable, errColl errcoll.Interface) (f *Filter) {
return &Filter{
refr: refr,
mu: &sync.RWMutex{},
diff --git a/internal/filter/storage.go b/internal/filter/storage.go
index 4a09032..928d7e7 100644
--- a/internal/filter/storage.go
+++ b/internal/filter/storage.go
@@ -12,6 +12,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite"
@@ -72,7 +75,7 @@ type DefaultStorage struct {
// errColl used to collect non-critical and rare errors, for example caching
// errors.
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
// customFilters is the storage of custom filters for profiles.
customFilters *custom.Filters
@@ -91,7 +94,7 @@ type DefaultStorage struct {
// maxRuleListSize is the maximum size in bytes of the downloadable
// rule-list content.
- maxRuleListSize int64
+ maxRuleListSize uint64
// useRuleListCache, if true, enables rule list cache.
useRuleListCache bool
@@ -141,11 +144,14 @@ type DefaultStorageConfig struct {
Now func() (now time.Time)
// ErrColl is used to collect non-critical and rare errors.
- ErrColl agd.ErrorCollector
+ ErrColl errcoll.Interface
// Resolver is used to resolve hosts in safe search.
Resolver agdnet.Resolver
+ // Cloner is used to clone messages taken from filtering-result caches.
+ Cloner *dnsmsg.Cloner
+
// CacheDir is the path to the directory where the cached filter files are
// put. The directory must exist.
CacheDir string
@@ -178,12 +184,13 @@ type DefaultStorageConfig struct {
// MaxRuleListSize is the maximum size in bytes of the downloadable
// rule-list content.
- MaxRuleListSize int64
+ MaxRuleListSize uint64
}
// NewDefaultStorage returns a new filter storage. c must not be nil.
func NewDefaultStorage(c *DefaultStorageConfig) (s *DefaultStorage, err error) {
genSafeSearch := safesearch.New(&safesearch.Config{
+ Cloner: c.Cloner,
Refreshable: &internal.RefreshableConfig{
URL: c.GeneralSafeSearchRulesURL,
ID: agd.FilterListIDGeneralSafeSearch,
@@ -200,6 +207,7 @@ func NewDefaultStorage(c *DefaultStorageConfig) (s *DefaultStorage, err error) {
})
ytSafeSearch := safesearch.New(&safesearch.Config{
+ Cloner: c.Cloner,
Refreshable: &internal.RefreshableConfig{
URL: c.YoutubeSafeSearchRulesURL,
ID: agd.FilterListIDYoutubeSafeSearch,
@@ -462,13 +470,13 @@ func (s *DefaultStorage) HasListID(id agd.FilterListID) (ok bool) {
}
// type check
-var _ agd.Refresher = (*DefaultStorage)(nil)
+var _ agdservice.Refresher = (*DefaultStorage)(nil)
// strgLogPrefix is the logging prefix for reportable errors and logs that
// DefaultStorage.Refresh uses.
const strgLogPrefix = "filter storage: refresh"
-// Refresh implements the agd.Refresher interface for *DefaultStorage.
+// Refresh implements the [agdservice.Refresher] interface for *DefaultStorage.
func (s *DefaultStorage) Refresh(ctx context.Context) (err error) {
return s.refresh(ctx, false)
}
@@ -499,7 +507,7 @@ func (s *DefaultStorage) refresh(ctx context.Context, acceptStale bool) (err err
err = s.services.Refresh(ctx, s.ruleListCacheSize, s.useRuleListCache, acceptStale)
if err != nil {
const errFmt = "refreshing blocked services: %w"
- agd.Collectf(ctx, s.errColl, errFmt, err)
+ errcoll.Collectf(ctx, s.errColl, errFmt, err)
return fmt.Errorf(errFmt, err)
}
@@ -528,7 +536,7 @@ func (s *DefaultStorage) addRuleList(
acceptStale bool,
) {
if _, ok := ruleLists[fl.id]; ok {
- agd.Collectf(ctx, s.errColl, "%s: duplicated id %q", strgLogPrefix, fl.id)
+ errcoll.Collectf(ctx, s.errColl, "%s: duplicated id %q", strgLogPrefix, fl.id)
return
}
@@ -558,7 +566,7 @@ func (s *DefaultStorage) addRuleList(
return
}
- agd.Collectf(ctx, s.errColl, "%s: refreshing %q: %w", strgLogPrefix, fl.id, err)
+ errcoll.Collectf(ctx, s.errColl, "%s: refreshing %q: %w", strgLogPrefix, fl.id, err)
metrics.FilterUpdatedStatus.WithLabelValues(fltIDStr).Set(0)
// If we can't get the new filter, and there is an old version of the same
diff --git a/internal/filter/storage_test.go b/internal/filter/storage_test.go
index 03680ff..f28d230 100644
--- a/internal/filter/storage_test.go
+++ b/internal/filter/storage_test.go
@@ -3,7 +3,6 @@ package filter_test
import (
"context"
"io"
- "net"
"net/netip"
"os"
"path/filepath"
@@ -155,6 +154,7 @@ func TestStorage_FilterFromContext_customAllow(t *testing.T) {
c := prepareConf(t)
c.SafeBrowsing, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: hashes,
ErrColl: errColl,
Resolver: resolver,
@@ -252,6 +252,7 @@ func TestStorage_FilterFromContext_schedule(t *testing.T) {
// Use AdultBlocking, because SafeBrowsing is NOT affected by the schedule.
c.AdultBlocking, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: hashes,
ErrColl: errColl,
Resolver: resolver,
@@ -754,6 +755,7 @@ func TestStorage_FilterFromContext_safeBrowsing(t *testing.T) {
c := prepareConf(t)
c.SafeBrowsing, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
+ Cloner: agdtest.NewCloner(),
Hashes: hashes,
ErrColl: errColl,
Resolver: resolver,
@@ -842,32 +844,44 @@ func TestStorage_FilterFromContext_safeSearch(t *testing.T) {
GeneralSafeSearch: true,
}
+ ttl := uint32(agdtest.FilteredResponseTTLSec)
+
testCases := []struct {
- wantIP netip.Addr
name string
host string
+ want []dns.RR
rrtype uint16
wantLookups int
}{{
- wantIP: safeSearchIPRespIP4,
+ want: []dns.RR{
+ dnsservertest.NewA(safeSearchIPHost, ttl, safeSearchIPRespIP4),
+ },
name: "ip4",
host: safeSearchIPHost,
rrtype: dns.TypeA,
wantLookups: 1,
}, {
- wantIP: safeSearchIPRespIP6,
+ want: []dns.RR{
+ dnsservertest.NewAAAA(safeSearchIPHost, ttl, safeSearchIPRespIP6),
+ },
name: "ip6",
host: safeSearchIPHost,
rrtype: dns.TypeAAAA,
wantLookups: 1,
}, {
- wantIP: safeSearchIPRespIP4,
+ want: []dns.RR{
+ dnsservertest.NewCNAME(safeSearchHost, ttl, safeSearchRespHost),
+ dnsservertest.NewA(safeSearchRespHost, ttl, safeSearchIPRespIP4),
+ },
name: "host_ip4",
host: safeSearchHost,
rrtype: dns.TypeA,
wantLookups: 1,
}, {
- wantIP: safeSearchIPRespIP6,
+ want: []dns.RR{
+ dnsservertest.NewCNAME(safeSearchHost, ttl, safeSearchRespHost),
+ dnsservertest.NewAAAA(safeSearchRespHost, ttl, safeSearchIPRespIP6),
+ },
name: "host_ip6",
host: safeSearchHost,
rrtype: dns.TypeAAAA,
@@ -897,18 +911,7 @@ func TestStorage_FilterFromContext_safeSearch(t *testing.T) {
res := rm.Msg
require.NotNil(t, res)
- require.Len(t, res.Answer, 1)
-
- switch ans := res.Answer[0]; ans := ans.(type) {
- case *dns.A:
- assert.Equal(t, tc.rrtype, ans.Hdr.Rrtype)
- assert.Equal(t, net.IP(tc.wantIP.AsSlice()), ans.A)
- case *dns.AAAA:
- assert.Equal(t, tc.rrtype, ans.Hdr.Rrtype)
- assert.Equal(t, net.IP(tc.wantIP.AsSlice()), ans.AAAA)
- default:
- t.Fatalf("unexpected answer type %T(%[1]v)", ans)
- }
+ require.Equal(t, tc.want, res.Answer)
})
}
}
diff --git a/internal/geoip/asntops.go b/internal/geoip/asntops.go
index d638df4..ce956a6 100644
--- a/internal/geoip/asntops.go
+++ b/internal/geoip/asntops.go
@@ -2,25 +2,21 @@
package geoip
-import "github.com/AdguardTeam/AdGuardDNS/internal/agd"
-
// DefaultTopASNs contains all specially handled ASNs.
-var DefaultTopASNs = map[agd.ASN]struct{}{
+var DefaultTopASNs = map[ASN]struct{}{
2: {},
+ 3: {},
174: {},
209: {},
224: {},
- 559: {},
577: {},
- 680: {},
701: {},
719: {},
- 786: {},
803: {},
812: {},
+ 834: {},
852: {},
855: {},
- 1103: {},
1136: {},
1213: {},
1221: {},
@@ -30,24 +26,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
1299: {},
1403: {},
1547: {},
- 1653: {},
1659: {},
1680: {},
+ 1741: {},
1759: {},
1764: {},
1835: {},
1836: {},
- 1853: {},
1901: {},
- 1955: {},
- 2018: {},
- 2107: {},
2108: {},
2110: {},
2116: {},
2119: {},
2497: {},
- 2510: {},
2514: {},
2516: {},
2518: {},
@@ -58,7 +49,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
2602: {},
2607: {},
2609: {},
- 2611: {},
+ 2614: {},
2740: {},
2764: {},
2847: {},
@@ -66,6 +57,8 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
2856: {},
2860: {},
3132: {},
+ 3177: {},
+ 3178: {},
3194: {},
3209: {},
3212: {},
@@ -85,12 +78,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
3320: {},
3326: {},
3329: {},
+ 3342: {},
3352: {},
3356: {},
+ 3399: {},
3462: {},
3549: {},
3605: {},
3695: {},
+ 3737: {},
3741: {},
3758: {},
3786: {},
@@ -98,19 +94,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
3855: {},
4007: {},
4134: {},
+ 4181: {},
4230: {},
4515: {},
4538: {},
+ 4540: {},
4609: {},
4638: {},
- 4648: {},
4657: {},
4685: {},
4713: {},
4721: {},
4725: {},
4739: {},
- 4750: {},
4760: {},
4761: {},
4764: {},
@@ -120,24 +116,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
4773: {},
4775: {},
4780: {},
- 4787: {},
4788: {},
4804: {},
4808: {},
- 4809: {},
4812: {},
4817: {},
4818: {},
4837: {},
4847: {},
- 5080: {},
5089: {},
5378: {},
5384: {},
- 5385: {},
5390: {},
5391: {},
- 5408: {},
5410: {},
5413: {},
5416: {},
@@ -145,7 +136,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
5466: {},
5483: {},
5518: {},
- 5532: {},
5578: {},
5603: {},
5607: {},
@@ -161,6 +151,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
6128: {},
6147: {},
6167: {},
+ 6181: {},
6306: {},
6327: {},
6400: {},
@@ -178,6 +169,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
6739: {},
6752: {},
6758: {},
+ 6762: {},
6772: {},
6799: {},
6805: {},
@@ -194,34 +186,36 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
6939: {},
7018: {},
7029: {},
+ 7049: {},
+ 7057: {},
7122: {},
7131: {},
- 7155: {},
7303: {},
- 7311: {},
7418: {},
7438: {},
7470: {},
- 7472: {},
7482: {},
7522: {},
7524: {},
7545: {},
7552: {},
- 7575: {},
+ 7565: {},
+ 7582: {},
+ 7590: {},
7642: {},
7679: {},
7713: {},
7727: {},
7738: {},
7794: {},
+ 7862: {},
7922: {},
+ 7979: {},
7992: {},
8014: {},
8048: {},
8053: {},
8075: {},
- 8094: {},
8151: {},
8167: {},
8193: {},
@@ -229,12 +223,10 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
8251: {},
8257: {},
8273: {},
- 8290: {},
8301: {},
8339: {},
8346: {},
8359: {},
- 8368: {},
8369: {},
8374: {},
8376: {},
@@ -249,10 +241,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
8449: {},
8452: {},
8462: {},
- 8468: {},
8473: {},
- 8511: {},
- 8542: {},
8544: {},
8551: {},
8560: {},
@@ -261,6 +250,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
8612: {},
8632: {},
8661: {},
+ 8670: {},
8680: {},
8681: {},
8697: {},
@@ -271,6 +261,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
8764: {},
8767: {},
8772: {},
+ 8778: {},
8781: {},
8818: {},
8821: {},
@@ -282,16 +273,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
8948: {},
8953: {},
8966: {},
+ 8978: {},
8990: {},
9002: {},
9008: {},
9009: {},
9031: {},
9038: {},
+ 9044: {},
9050: {},
9051: {},
9063: {},
9070: {},
+ 9085: {},
9105: {},
9119: {},
9121: {},
@@ -301,7 +295,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
9146: {},
9155: {},
9158: {},
- 9186: {},
9198: {},
9231: {},
9245: {},
@@ -310,7 +303,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
9260: {},
9269: {},
9299: {},
- 9303: {},
9304: {},
9316: {},
9318: {},
@@ -322,8 +314,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
9365: {},
9381: {},
9416: {},
- 9419: {},
- 9431: {},
9443: {},
9471: {},
9484: {},
@@ -334,34 +324,40 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
9595: {},
9605: {},
9617: {},
+ 9622: {},
9644: {},
- 9658: {},
9674: {},
9694: {},
9751: {},
+ 9770: {},
9790: {},
9808: {},
9824: {},
9829: {},
+ 9845: {},
9873: {},
9876: {},
9902: {},
9908: {},
9919: {},
9924: {},
+ 9929: {},
9930: {},
9931: {},
9934: {},
+ 9976: {},
9988: {},
+ 9997: {},
10010: {},
10013: {},
10030: {},
10036: {},
+ 10066: {},
10075: {},
+ 10076: {},
10094: {},
10099: {},
10101: {},
- 10103: {},
10118: {},
10131: {},
10139: {},
@@ -373,23 +369,20 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
10292: {},
10299: {},
10396: {},
- 10429: {},
10474: {},
+ 10569: {},
10620: {},
10796: {},
- 10834: {},
- 11014: {},
11081: {},
11139: {},
11172: {},
11232: {},
11259: {},
11260: {},
- 11269: {},
11290: {},
11315: {},
11351: {},
- 11367: {},
+ 11404: {},
11426: {},
11427: {},
11492: {},
@@ -397,13 +390,13 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
11562: {},
11594: {},
11664: {},
+ 11721: {},
11776: {},
11814: {},
11816: {},
11830: {},
11845: {},
11888: {},
- 11992: {},
12066: {},
12083: {},
12252: {},
@@ -416,7 +409,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
12338: {},
12350: {},
12353: {},
- 12361: {},
12365: {},
12389: {},
12390: {},
@@ -424,18 +416,21 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
12400: {},
12406: {},
12414: {},
+ 12426: {},
12430: {},
12436: {},
12455: {},
12479: {},
12491: {},
+ 12496: {},
12552: {},
12570: {},
12576: {},
12578: {},
- 12590: {},
- 12591: {},
12605: {},
+ 12620: {},
+ 12638: {},
+ 12660: {},
12668: {},
12709: {},
12714: {},
@@ -445,7 +440,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
12754: {},
12764: {},
12767: {},
- 12768: {},
12793: {},
12810: {},
12829: {},
@@ -460,30 +454,32 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
12969: {},
12975: {},
12978: {},
+ 12993: {},
12997: {},
13000: {},
13030: {},
13036: {},
13037: {},
- 13044: {},
13045: {},
13046: {},
- 13092: {},
+ 13070: {},
13099: {},
+ 13101: {},
13110: {},
13122: {},
13124: {},
13127: {},
13156: {},
+ 13170: {},
13188: {},
13189: {},
13194: {},
- 13213: {},
13280: {},
13285: {},
13306: {},
13335: {},
13489: {},
+ 13682: {},
13771: {},
13999: {},
14061: {},
@@ -493,27 +489,24 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
14434: {},
14522: {},
14593: {},
- 14618: {},
14638: {},
14709: {},
14754: {},
14813: {},
14868: {},
- 14956: {},
14979: {},
14988: {},
- 15128: {},
15146: {},
- 15169: {},
- 15311: {},
+ 15321: {},
15344: {},
+ 15366: {},
15377: {},
15378: {},
- 15383: {},
15389: {},
15397: {},
15399: {},
15404: {},
+ 15405: {},
15433: {},
15435: {},
15440: {},
@@ -529,13 +522,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
15557: {},
15600: {},
15614: {},
- 15623: {},
15659: {},
15704: {},
15706: {},
+ 15723: {},
15735: {},
+ 15736: {},
15751: {},
- 15765: {},
+ 15766: {},
15774: {},
15796: {},
15802: {},
@@ -551,17 +545,17 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
15958: {},
15962: {},
15964: {},
- 15965: {},
+ 15975: {},
15994: {},
16006: {},
16010: {},
16019: {},
16028: {},
+ 16030: {},
16058: {},
16086: {},
16116: {},
16117: {},
- 16125: {},
16135: {},
16178: {},
16190: {},
@@ -569,14 +563,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
16223: {},
16229: {},
16232: {},
- 16245: {},
16246: {},
16276: {},
+ 16302: {},
16322: {},
16333: {},
+ 16334: {},
16342: {},
16345: {},
- 16413: {},
+ 16367: {},
16437: {},
16509: {},
16591: {},
@@ -587,6 +582,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
16735: {},
16814: {},
16906: {},
+ 16929: {},
16960: {},
17072: {},
17079: {},
@@ -601,7 +597,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
17501: {},
17506: {},
17511: {},
- 17539: {},
+ 17529: {},
17547: {},
17552: {},
17557: {},
@@ -617,12 +613,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
17705: {},
17716: {},
17726: {},
- 17747: {},
+ 17806: {},
17809: {},
17816: {},
17828: {},
+ 17839: {},
+ 17849: {},
17853: {},
17858: {},
+ 17864: {},
17882: {},
17893: {},
17924: {},
@@ -631,6 +630,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
18001: {},
18004: {},
18013: {},
+ 18015: {},
18024: {},
18049: {},
18053: {},
@@ -639,14 +639,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
18126: {},
18144: {},
18182: {},
+ 18199: {},
18200: {},
18209: {},
18371: {},
18390: {},
18399: {},
18403: {},
+ 18412: {},
18419: {},
- 18429: {},
18734: {},
18747: {},
18809: {},
@@ -660,13 +661,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
19180: {},
19182: {},
19246: {},
- 19397: {},
+ 19318: {},
19422: {},
19429: {},
+ 19437: {},
+ 19447: {},
19624: {},
19711: {},
19863: {},
- 19889: {},
20001: {},
20055: {},
20057: {},
@@ -678,22 +680,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
20365: {},
20473: {},
20485: {},
- 20598: {},
+ 20626: {},
20634: {},
20661: {},
20676: {},
- 20719: {},
+ 20723: {},
+ 20746: {},
20771: {},
20776: {},
20804: {},
20845: {},
20846: {},
- 20860: {},
+ 20874: {},
20875: {},
20880: {},
+ 20904: {},
20910: {},
20911: {},
- 20928: {},
20960: {},
20963: {},
20978: {},
@@ -703,11 +706,10 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
21040: {},
21050: {},
21069: {},
+ 21100: {},
21107: {},
21183: {},
- 21196: {},
21211: {},
- 21217: {},
21230: {},
21232: {},
21246: {},
@@ -715,14 +717,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
21277: {},
21283: {},
21299: {},
- 21305: {},
21334: {},
21351: {},
21412: {},
21430: {},
21450: {},
+ 21466: {},
21491: {},
21497: {},
+ 21502: {},
21559: {},
21575: {},
21599: {},
@@ -737,22 +740,22 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
22069: {},
22085: {},
22313: {},
- 22363: {},
+ 22351: {},
22394: {},
- 22411: {},
22423: {},
22581: {},
22724: {},
- 22735: {},
22773: {},
22869: {},
22884: {},
22927: {},
22933: {},
22995: {},
+ 23031: {},
23114: {},
23201: {},
23243: {},
+ 23383: {},
23487: {},
23520: {},
23655: {},
@@ -765,40 +768,35 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
23750: {},
23752: {},
23764: {},
- 23856: {},
23860: {},
- 23888: {},
23889: {},
23900: {},
- 23917: {},
23923: {},
23944: {},
23955: {},
23956: {},
+ 23959: {},
23969: {},
- 24016: {},
24033: {},
24086: {},
24157: {},
24158: {},
24163: {},
24164: {},
- 24165: {},
24186: {},
24203: {},
24309: {},
24323: {},
- 24324: {},
+ 24334: {},
24337: {},
24378: {},
24389: {},
- 24390: {},
24400: {},
24432: {},
24439: {},
- 24441: {},
24444: {},
24445: {},
+ 24482: {},
24492: {},
24499: {},
24547: {},
@@ -807,11 +805,12 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
24560: {},
24589: {},
24608: {},
+ 24645: {},
24651: {},
24691: {},
24722: {},
- 24743: {},
24757: {},
+ 24792: {},
24800: {},
24812: {},
24822: {},
@@ -819,24 +818,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
24852: {},
24863: {},
24875: {},
+ 24877: {},
24889: {},
24921: {},
24940: {},
+ 24953: {},
24955: {},
- 24961: {},
25019: {},
25106: {},
25117: {},
25124: {},
- 25129: {},
25133: {},
25135: {},
25139: {},
25144: {},
25159: {},
- 25180: {},
- 25184: {},
25190: {},
+ 25198: {},
25229: {},
25248: {},
25250: {},
@@ -845,28 +843,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
25310: {},
25369: {},
25374: {},
- 25375: {},
25400: {},
25406: {},
25424: {},
25429: {},
25441: {},
- 25447: {},
25454: {},
25467: {},
25471: {},
25472: {},
25491: {},
- 25509: {},
25512: {},
25513: {},
- 25528: {},
25543: {},
25596: {},
25607: {},
25620: {},
25668: {},
- 25695: {},
26130: {},
26210: {},
26383: {},
@@ -874,12 +867,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
26611: {},
26615: {},
26617: {},
+ 26932: {},
27651: {},
27653: {},
27660: {},
27665: {},
27668: {},
27672: {},
+ 27680: {},
27694: {},
27695: {},
27696: {},
@@ -893,18 +888,16 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
27742: {},
27745: {},
27747: {},
+ 27751: {},
27757: {},
27759: {},
27773: {},
- 27774: {},
27775: {},
27781: {},
27789: {},
- 27796: {},
27800: {},
27813: {},
27831: {},
- 27833: {},
27837: {},
27839: {},
27843: {},
@@ -920,18 +913,18 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
27923: {},
27924: {},
27927: {},
- 27928: {},
27932: {},
27947: {},
27951: {},
27953: {},
- 27955: {},
27983: {},
27984: {},
+ 27988: {},
27995: {},
28005: {},
28006: {},
28007: {},
+ 28009: {},
28015: {},
28024: {},
28032: {},
@@ -946,7 +939,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
28118: {},
28126: {},
28146: {},
- 28171: {},
+ 28182: {},
28186: {},
28198: {},
28201: {},
@@ -954,12 +947,9 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
28210: {},
28220: {},
28258: {},
- 28294: {},
28343: {},
28398: {},
28403: {},
- 28432: {},
- 28438: {},
28458: {},
28469: {},
28481: {},
@@ -980,12 +970,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
28580: {},
28598: {},
28649: {},
- 28656: {},
28668: {},
- 28669: {},
28683: {},
28685: {},
28698: {},
+ 28717: {},
28725: {},
28753: {},
28760: {},
@@ -994,16 +983,16 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
28840: {},
28849: {},
28851: {},
- 28878: {},
+ 28854: {},
28884: {},
28885: {},
+ 28909: {},
28919: {},
28952: {},
28964: {},
28972: {},
29027: {},
29030: {},
- 29039: {},
29049: {},
29061: {},
29070: {},
@@ -1012,7 +1001,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
29119: {},
29124: {},
29170: {},
- 29194: {},
29208: {},
29238: {},
29244: {},
@@ -1021,16 +1009,13 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
29286: {},
29310: {},
29314: {},
- 29348: {},
29355: {},
29357: {},
29384: {},
29405: {},
29447: {},
29465: {},
- 29467: {},
29485: {},
- 29492: {},
29497: {},
29518: {},
29544: {},
@@ -1040,27 +1025,27 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
29582: {},
29584: {},
29614: {},
+ 29667: {},
29687: {},
29695: {},
29975: {},
30036: {},
30058: {},
+ 30272: {},
30526: {},
30600: {},
30619: {},
30689: {},
30722: {},
30764: {},
+ 30838: {},
30844: {},
- 30848: {},
- 30860: {},
30873: {},
- 30896: {},
- 30929: {},
30969: {},
30982: {},
30985: {},
30986: {},
+ 30987: {},
30990: {},
30992: {},
30999: {},
@@ -1083,42 +1068,38 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
31205: {},
31208: {},
31213: {},
- 31214: {},
31224: {},
31242: {},
- 31245: {},
31246: {},
+ 31250: {},
31252: {},
- 31263: {},
31272: {},
31287: {},
+ 31390: {},
31404: {},
31423: {},
31452: {},
31499: {},
+ 31510: {},
31543: {},
31549: {},
31615: {},
+ 31642: {},
31655: {},
- 31679: {},
- 31689: {},
31721: {},
31725: {},
- 31726: {},
+ 31736: {},
31856: {},
31898: {},
- 31960: {},
32020: {},
32098: {},
32398: {},
- 32437: {},
32860: {},
33363: {},
33392: {},
33567: {},
33576: {},
33582: {},
- 33588: {},
33763: {},
33765: {},
33771: {},
@@ -1126,27 +1107,27 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
33781: {},
33788: {},
33796: {},
- 33852: {},
33874: {},
33885: {},
33915: {},
33922: {},
+ 33943: {},
33983: {},
34001: {},
34058: {},
34087: {},
- 34113: {},
34120: {},
34170: {},
34187: {},
34224: {},
34244: {},
- 34245: {},
+ 34263: {},
34295: {},
34296: {},
- 34306: {},
34362: {},
- 34458: {},
+ 34410: {},
+ 34447: {},
+ 34471: {},
34525: {},
34533: {},
34547: {},
@@ -1154,10 +1135,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
34569: {},
34577: {},
34594: {},
- 34606: {},
34661: {},
- 34666: {},
- 34683: {},
34700: {},
34702: {},
34705: {},
@@ -1167,30 +1145,32 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
34754: {},
34772: {},
34779: {},
+ 34781: {},
34797: {},
34803: {},
- 34820: {},
- 34841: {},
34857: {},
34876: {},
+ 34916: {},
+ 34917: {},
34918: {},
34977: {},
34984: {},
+ 35046: {},
35047: {},
- 35063: {},
35091: {},
35104: {},
35132: {},
35141: {},
+ 35158: {},
35179: {},
35191: {},
35197: {},
35223: {},
35228: {},
35244: {},
- 35277: {},
35297: {},
35311: {},
+ 35313: {},
35320: {},
35328: {},
35346: {},
@@ -1200,7 +1180,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
35432: {},
35444: {},
35457: {},
- 35493: {},
+ 35467: {},
35518: {},
35549: {},
35566: {},
@@ -1213,18 +1193,18 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
35706: {},
35725: {},
35729: {},
+ 35732: {},
35753: {},
35790: {},
35805: {},
35807: {},
- 35816: {},
35819: {},
+ 35892: {},
35900: {},
- 35913: {},
+ 35911: {},
36040: {},
36290: {},
- 36384: {},
- 36445: {},
+ 36352: {},
36492: {},
36511: {},
36549: {},
@@ -1239,14 +1219,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
36892: {},
36902: {},
36903: {},
- 36905: {},
36907: {},
36908: {},
36909: {},
36912: {},
- 36913: {},
36914: {},
- 36916: {},
36920: {},
36923: {},
36924: {},
@@ -1259,15 +1236,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
36947: {},
36955: {},
36958: {},
- 36959: {},
36962: {},
36963: {},
- 36965: {},
- 36969: {},
36972: {},
36974: {},
36977: {},
- 36986: {},
36988: {},
36992: {},
36994: {},
@@ -1276,12 +1249,10 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
36999: {},
37002: {},
37006: {},
- 37008: {},
37009: {},
37014: {},
37020: {},
37027: {},
- 37028: {},
37030: {},
37035: {},
37037: {},
@@ -1308,15 +1279,12 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37119: {},
37123: {},
37124: {},
- 37126: {},
37129: {},
37133: {},
37136: {},
37141: {},
- 37143: {},
37148: {},
37154: {},
- 37163: {},
37164: {},
37168: {},
37173: {},
@@ -1330,20 +1298,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37204: {},
37205: {},
37208: {},
- 37211: {},
- 37215: {},
- 37219: {},
37223: {},
37228: {},
37229: {},
37233: {},
- 37254: {},
37257: {},
- 37272: {},
37282: {},
37284: {},
37287: {},
- 37292: {},
37294: {},
37303: {},
37305: {},
@@ -1351,9 +1313,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37313: {},
37315: {},
37323: {},
- 37326: {},
- 37329: {},
- 37333: {},
37334: {},
37336: {},
37337: {},
@@ -1363,32 +1322,26 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37347: {},
37349: {},
37350: {},
- 37353: {},
37358: {},
37371: {},
37376: {},
37385: {},
- 37395: {},
37406: {},
37410: {},
37414: {},
37424: {},
37425: {},
- 37427: {},
37429: {},
- 37430: {},
37440: {},
37447: {},
- 37449: {},
37451: {},
37453: {},
37457: {},
37460: {},
37461: {},
- 37462: {},
+ 37468: {},
37473: {},
- 37480: {},
- 37489: {},
+ 37487: {},
37492: {},
37503: {},
37508: {},
@@ -1406,26 +1359,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37558: {},
37559: {},
37563: {},
- 37564: {},
- 37571: {},
+ 37566: {},
37575: {},
37577: {},
37580: {},
- 37582: {},
37584: {},
37586: {},
- 37593: {},
37594: {},
- 37604: {},
37611: {},
37612: {},
37614: {},
37616: {},
- 37619: {},
37621: {},
37622: {},
- 37637: {},
- 37638: {},
37642: {},
37645: {},
37649: {},
@@ -1438,21 +1384,19 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
37693: {},
37697: {},
37705: {},
- 37711: {},
37721: {},
- 37917: {},
37963: {},
38008: {},
38009: {},
38067: {},
38077: {},
38136: {},
+ 38186: {},
38195: {},
38198: {},
38201: {},
38203: {},
- 38209: {},
- 38229: {},
+ 38227: {},
38235: {},
38247: {},
38264: {},
@@ -1464,65 +1408,63 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
38553: {},
38565: {},
38584: {},
- 38592: {},
38600: {},
38623: {},
38742: {},
- 38794: {},
+ 38800: {},
38805: {},
38819: {},
38841: {},
- 38851: {},
+ 38875: {},
38901: {},
+ 38919: {},
+ 38987: {},
38999: {},
39007: {},
39010: {},
39032: {},
39067: {},
- 39074: {},
39093: {},
- 39122: {},
39184: {},
39216: {},
39232: {},
+ 39251: {},
39273: {},
39279: {},
39280: {},
- 39308: {},
39344: {},
- 39351: {},
39354: {},
39375: {},
39386: {},
39397: {},
39401: {},
39402: {},
+ 39440: {},
39501: {},
39507: {},
- 39544: {},
39572: {},
+ 39574: {},
39603: {},
39608: {},
39611: {},
39642: {},
- 39647: {},
39650: {},
- 39686: {},
+ 39699: {},
+ 39737: {},
39766: {},
+ 39798: {},
+ 39823: {},
39824: {},
- 39826: {},
39878: {},
39891: {},
- 39927: {},
- 40021: {},
+ 39912: {},
40029: {},
- 40191: {},
- 40676: {},
40788: {},
40945: {},
40980: {},
41032: {},
41046: {},
+ 41068: {},
41088: {},
41124: {},
41164: {},
@@ -1535,9 +1477,10 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
41354: {},
41371: {},
41378: {},
+ 41435: {},
41436: {},
+ 41454: {},
41496: {},
- 41549: {},
41557: {},
41563: {},
41564: {},
@@ -1552,40 +1495,38 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
41798: {},
41820: {},
41833: {},
+ 41872: {},
41881: {},
41897: {},
41937: {},
41956: {},
41997: {},
+ 41998: {},
42003: {},
42013: {},
- 42020: {},
42082: {},
- 42083: {},
- 42099: {},
+ 42090: {},
42109: {},
- 42162: {},
42183: {},
42232: {},
- 42235: {},
- 42248: {},
42298: {},
42306: {},
42313: {},
- 42314: {},
42334: {},
42337: {},
- 42410: {},
+ 42343: {},
42437: {},
42525: {},
42532: {},
42541: {},
+ 42555: {},
42560: {},
42571: {},
42580: {},
42581: {},
42610: {},
42652: {},
+ 42673: {},
42682: {},
42689: {},
42708: {},
@@ -1596,15 +1537,16 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
42828: {},
42837: {},
42841: {},
- 42852: {},
+ 42844: {},
42863: {},
+ 42864: {},
42908: {},
42912: {},
42925: {},
- 42960: {},
42961: {},
42991: {},
43019: {},
+ 43028: {},
43060: {},
43139: {},
43197: {},
@@ -1612,8 +1554,8 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
43242: {},
43248: {},
43256: {},
+ 43289: {},
43350: {},
- 43406: {},
43447: {},
43451: {},
43452: {},
@@ -1621,63 +1563,63 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
43529: {},
43533: {},
43557: {},
- 43568: {},
43612: {},
43627: {},
43633: {},
43653: {},
+ 43656: {},
43700: {},
43708: {},
43733: {},
43754: {},
43766: {},
- 43768: {},
+ 43791: {},
+ 43811: {},
43824: {},
43870: {},
- 43922: {},
+ 43883: {},
43925: {},
43939: {},
43940: {},
+ 44021: {},
44027: {},
44034: {},
- 44066: {},
- 44075: {},
+ 44086: {},
44087: {},
44134: {},
44143: {},
- 44212: {},
- 44213: {},
44217: {},
44234: {},
44244: {},
- 44285: {},
+ 44247: {},
+ 44272: {},
44313: {},
44327: {},
44377: {},
+ 44384: {},
44391: {},
44395: {},
44477: {},
44483: {},
44489: {},
- 44515: {},
44546: {},
44558: {},
44566: {},
- 44575: {},
44631: {},
44702: {},
- 44709: {},
44725: {},
44735: {},
44869: {},
44894: {},
44901: {},
44925: {},
+ 45007: {},
45090: {},
45102: {},
45143: {},
45177: {},
45178: {},
+ 45193: {},
45224: {},
45245: {},
45267: {},
@@ -1686,10 +1628,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
45345: {},
45355: {},
45356: {},
+ 45361: {},
45410: {},
+ 45430: {},
45458: {},
45461: {},
- 45475: {},
45498: {},
45543: {},
45558: {},
@@ -1704,11 +1647,9 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
45763: {},
45766: {},
45773: {},
- 45845: {},
45879: {},
45891: {},
45899: {},
- 45903: {},
45905: {},
45916: {},
45918: {},
@@ -1717,16 +1658,17 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
45960: {},
46198: {},
46408: {},
- 46562: {},
46650: {},
46868: {},
46941: {},
+ 47132: {},
47139: {},
47159: {},
47169: {},
47232: {},
47237: {},
47253: {},
+ 47304: {},
47331: {},
47377: {},
47394: {},
@@ -1734,98 +1676,89 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
47524: {},
47588: {},
47589: {},
- 47702: {},
+ 47719: {},
47782: {},
- 47798: {},
- 47881: {},
+ 47790: {},
47883: {},
47887: {},
47898: {},
- 47942: {},
47956: {},
- 47959: {},
47962: {},
- 47975: {},
48092: {},
- 48133: {},
- 48146: {},
+ 48101: {},
48147: {},
48161: {},
48190: {},
48206: {},
- 48233: {},
+ 48239: {},
48252: {},
- 48253: {},
- 48260: {},
- 48271: {},
- 48418: {},
+ 48288: {},
+ 48431: {},
48437: {},
48480: {},
48492: {},
48503: {},
48506: {},
48629: {},
- 48642: {},
48675: {},
- 48685: {},
48695: {},
- 48716: {},
+ 48715: {},
48728: {},
48803: {},
48830: {},
48832: {},
48847: {},
- 48861: {},
48887: {},
48917: {},
48926: {},
48945: {},
- 48953: {},
48966: {},
- 49020: {},
- 49040: {},
+ 49044: {},
49056: {},
- 49070: {},
+ 49091: {},
49100: {},
+ 49101: {},
49115: {},
49117: {},
49129: {},
+ 49183: {},
49223: {},
+ 49242: {},
49273: {},
- 49455: {},
49472: {},
49528: {},
49561: {},
- 49586: {},
- 49602: {},
49628: {},
- 49666: {},
49724: {},
- 49725: {},
+ 49770: {},
+ 49798: {},
49800: {},
49808: {},
+ 49826: {},
+ 49870: {},
49889: {},
49902: {},
49914: {},
49981: {},
+ 49985: {},
50010: {},
50025: {},
50181: {},
50223: {},
50231: {},
50251: {},
- 50261: {},
50266: {},
50274: {},
+ 50294: {},
50304: {},
- 50334: {},
- 50349: {},
+ 50360: {},
50411: {},
50463: {},
50467: {},
50500: {},
50581: {},
50597: {},
+ 50606: {},
50613: {},
50616: {},
50635: {},
@@ -1835,25 +1768,27 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
50685: {},
50698: {},
50710: {},
+ 50749: {},
50767: {},
- 50770: {},
50810: {},
50821: {},
50825: {},
+ 50923: {},
50925: {},
- 50953: {},
50959: {},
50973: {},
50979: {},
51018: {},
+ 51069: {},
51110: {},
51167: {},
51175: {},
51184: {},
51207: {},
51265: {},
- 51319: {},
51336: {},
+ 51341: {},
+ 51342: {},
51346: {},
51371: {},
51375: {},
@@ -1864,42 +1799,37 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
51469: {},
51495: {},
51504: {},
- 51561: {},
51582: {},
51604: {},
- 51615: {},
51653: {},
51684: {},
51765: {},
51784: {},
+ 51809: {},
51825: {},
51852: {},
51896: {},
- 51947: {},
- 52000: {},
52075: {},
- 52116: {},
- 52157: {},
+ 52145: {},
52173: {},
+ 52195: {},
52228: {},
52232: {},
52233: {},
52238: {},
52242: {},
- 52251: {},
52253: {},
52257: {},
52260: {},
52262: {},
52263: {},
- 52286: {},
52312: {},
52323: {},
52341: {},
52361: {},
52362: {},
52363: {},
- 52381: {},
+ 52373: {},
52398: {},
52405: {},
52412: {},
@@ -1915,27 +1845,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
52783: {},
52974: {},
53006: {},
+ 53363: {},
53667: {},
53764: {},
- 53926: {},
- 54115: {},
+ 53913: {},
54198: {},
54614: {},
- 55081: {},
+ 54994: {},
55330: {},
- 55387: {},
55391: {},
55392: {},
55424: {},
55427: {},
55430: {},
- 55501: {},
55577: {},
55685: {},
55699: {},
55769: {},
- 55784: {},
- 55792: {},
55805: {},
55821: {},
55836: {},
@@ -1944,9 +1870,9 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
55872: {},
55900: {},
55915: {},
+ 55933: {},
55943: {},
55944: {},
- 55990: {},
56017: {},
56030: {},
56040: {},
@@ -1961,7 +1887,6 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
56099: {},
56120: {},
56167: {},
- 56204: {},
56207: {},
56231: {},
56262: {},
@@ -1969,32 +1894,25 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
56300: {},
56329: {},
56349: {},
- 56388: {},
- 56400: {},
- 56410: {},
56478: {},
56484: {},
56491: {},
- 56566: {},
+ 56497: {},
56568: {},
56630: {},
+ 56642: {},
56653: {},
56655: {},
56656: {},
56665: {},
- 56694: {},
56696: {},
- 56704: {},
- 56709: {},
+ 56747: {},
56803: {},
56902: {},
- 56933: {},
- 56995: {},
- 57013: {},
57016: {},
57043: {},
57070: {},
- 57101: {},
+ 57112: {},
57133: {},
57134: {},
57218: {},
@@ -2007,22 +1925,21 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
57374: {},
57388: {},
57389: {},
+ 57443: {},
57513: {},
57564: {},
57566: {},
57588: {},
- 57608: {},
+ 57606: {},
57630: {},
- 57634: {},
- 57704: {},
- 57722: {},
57728: {},
57743: {},
57760: {},
- 57761: {},
57764: {},
+ 57776: {},
57778: {},
57794: {},
+ 57826: {},
57852: {},
57858: {},
57869: {},
@@ -2030,9 +1947,12 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
58056: {},
58061: {},
58065: {},
+ 58098: {},
58118: {},
58224: {},
- 58254: {},
+ 58243: {},
+ 58264: {},
+ 58321: {},
58322: {},
58328: {},
58424: {},
@@ -2043,10 +1963,8 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
58504: {},
58507: {},
58524: {},
- 58593: {},
58610: {},
58656: {},
- 58666: {},
58682: {},
58689: {},
58715: {},
@@ -2054,18 +1972,15 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
58731: {},
58821: {},
58895: {},
- 58923: {},
58945: {},
58952: {},
- 59078: {},
- 59129: {},
+ 59108: {},
+ 59125: {},
+ 59126: {},
+ 59127: {},
59253: {},
59257: {},
- 59317: {},
- 59355: {},
59362: {},
- 59443: {},
- 59463: {},
59497: {},
59588: {},
59625: {},
@@ -2075,80 +1990,94 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
59847: {},
59861: {},
59890: {},
+ 59912: {},
59931: {},
59989: {},
60068: {},
60111: {},
- 60138: {},
60258: {},
- 60268: {},
60294: {},
60304: {},
- 60325: {},
60352: {},
- 60353: {},
60367: {},
60372: {},
+ 60377: {},
+ 60398: {},
60471: {},
60485: {},
60517: {},
60636: {},
- 60656: {},
- 60723: {},
60725: {},
+ 60754: {},
60757: {},
60781: {},
60806: {},
+ 60876: {},
+ 60966: {},
60999: {},
+ 61010: {},
61071: {},
- 61098: {},
61135: {},
61143: {},
- 61154: {},
+ 61174: {},
61189: {},
+ 61208: {},
+ 61211: {},
61272: {},
61275: {},
- 61287: {},
61307: {},
+ 61317: {},
61345: {},
61367: {},
+ 61390: {},
+ 61449: {},
61461: {},
61466: {},
61478: {},
+ 61490: {},
61512: {},
+ 61588: {},
62005: {},
62013: {},
- 62027: {},
+ 62023: {},
+ 62059: {},
+ 62099: {},
+ 62129: {},
62161: {},
62179: {},
+ 62183: {},
+ 62211: {},
62240: {},
- 62250: {},
+ 62281: {},
62282: {},
62336: {},
- 62337: {},
- 62386: {},
62563: {},
- 62627: {},
63023: {},
63199: {},
63473: {},
63526: {},
63852: {},
63859: {},
+ 63945: {},
63949: {},
63969: {},
63991: {},
63996: {},
- 64037: {},
+ 63998: {},
64043: {},
+ 64073: {},
+ 64098: {},
64105: {},
- 64126: {},
64134: {},
+ 64139: {},
64270: {},
+ 64286: {},
64300: {},
+ 64443: {},
64466: {},
131090: {},
131111: {},
+ 131149: {},
131178: {},
131207: {},
131267: {},
@@ -2161,48 +2090,40 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
131584: {},
131591: {},
131596: {},
- 131607: {},
131627: {},
- 132021: {},
+ 131769: {},
132045: {},
132061: {},
132080: {},
- 132104: {},
- 132116: {},
132148: {},
- 132160: {},
132165: {},
132167: {},
132173: {},
132199: {},
132203: {},
- 132222: {},
- 132280: {},
+ 132255: {},
132298: {},
+ 132429: {},
132447: {},
- 132449: {},
132462: {},
132468: {},
132471: {},
- 132480: {},
132513: {},
132525: {},
132618: {},
- 132652: {},
132686: {},
- 132730: {},
132825: {},
132831: {},
133012: {},
- 133192: {},
+ 133076: {},
+ 133137: {},
+ 133159: {},
+ 133206: {},
133287: {},
133334: {},
- 133380: {},
133384: {},
133385: {},
- 133414: {},
133440: {},
- 133453: {},
133480: {},
133481: {},
133524: {},
@@ -2212,30 +2133,34 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
133613: {},
133623: {},
133661: {},
+ 133752: {},
133875: {},
133894: {},
133957: {},
133982: {},
134090: {},
+ 134113: {},
134134: {},
134204: {},
134356: {},
- 134359: {},
- 134413: {},
- 134489: {},
+ 134467: {},
+ 134550: {},
134562: {},
- 134599: {},
134651: {},
134674: {},
134697: {},
134707: {},
+ 134711: {},
134714: {},
134715: {},
134732: {},
134739: {},
+ 134762: {},
+ 134774: {},
134783: {},
134806: {},
134840: {},
+ 134972: {},
134995: {},
135043: {},
135059: {},
@@ -2243,7 +2168,8 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
135126: {},
135298: {},
135300: {},
- 135333: {},
+ 135341: {},
+ 135345: {},
135371: {},
135375: {},
135376: {},
@@ -2251,7 +2177,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
135405: {},
135407: {},
135409: {},
- 135427: {},
+ 135477: {},
135478: {},
135589: {},
135600: {},
@@ -2267,36 +2193,28 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
136238: {},
136255: {},
136258: {},
- 136379: {},
136380: {},
136384: {},
+ 136400: {},
136442: {},
136454: {},
- 136474: {},
136479: {},
136480: {},
136515: {},
136525: {},
- 136530: {},
136538: {},
136557: {},
- 136763: {},
136780: {},
136787: {},
136873: {},
136897: {},
136907: {},
- 136919: {},
136950: {},
136969: {},
- 136972: {},
- 136975: {},
136994: {},
137047: {},
- 137056: {},
137080: {},
137226: {},
- 137236: {},
137409: {},
137412: {},
137424: {},
@@ -2304,100 +2222,109 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
137453: {},
137477: {},
137526: {},
+ 137549: {},
137561: {},
- 137577: {},
- 137580: {},
137824: {},
137872: {},
- 137891: {},
- 137905: {},
- 137952: {},
+ 137880: {},
+ 137895: {},
137959: {},
137967: {},
+ 137982: {},
137989: {},
138089: {},
- 138096: {},
138167: {},
138168: {},
138179: {},
138197: {},
+ 138322: {},
138346: {},
138368: {},
138384: {},
- 138388: {},
138423: {},
138500: {},
- 138506: {},
138529: {},
138590: {},
- 138629: {},
- 138634: {},
138655: {},
- 138684: {},
138754: {},
138886: {},
+ 138902: {},
138915: {},
138934: {},
138997: {},
+ 139003: {},
139009: {},
139029: {},
139043: {},
- 139218: {},
139224: {},
+ 139285: {},
139325: {},
+ 139580: {},
139609: {},
139628: {},
139651: {},
- 139719: {},
+ 139741: {},
139759: {},
- 139766: {},
139831: {},
+ 139841: {},
139879: {},
139898: {},
139922: {},
+ 139952: {},
139994: {},
140045: {},
+ 140061: {},
140072: {},
+ 140096: {},
+ 140100: {},
140220: {},
- 140265: {},
140292: {},
- 140330: {},
140401: {},
+ 140443: {},
140499: {},
140504: {},
140594: {},
140608: {},
+ 140635: {},
+ 140686: {},
140900: {},
- 140903: {},
- 140966: {},
+ 141015: {},
141024: {},
141031: {},
141047: {},
141145: {},
+ 141212: {},
141216: {},
- 141342: {},
- 141432: {},
+ 141361: {},
+ 141421: {},
+ 141607: {},
141680: {},
141681: {},
- 141691: {},
141711: {},
141767: {},
141778: {},
- 141983: {},
141995: {},
142065: {},
+ 142120: {},
+ 142295: {},
142300: {},
142352: {},
- 142580: {},
+ 142403: {},
+ 142552: {},
142647: {},
- 147045: {},
147049: {},
+ 147184: {},
149024: {},
- 149173: {},
- 149419: {},
- 149994: {},
- 150107: {},
+ 149034: {},
+ 149521: {},
+ 149645: {},
+ 149660: {},
+ 149768: {},
+ 149771: {},
+ 149866: {},
+ 150131: {},
150153: {},
+ 150359: {},
150683: {},
150692: {},
150750: {},
@@ -2408,53 +2335,53 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
196874: {},
196925: {},
196961: {},
+ 197077: {},
197207: {},
+ 197212: {},
197225: {},
- 197248: {},
197296: {},
197301: {},
197350: {},
197398: {},
197423: {},
+ 197470: {},
197540: {},
197556: {},
- 197663: {},
+ 197623: {},
+ 197637: {},
+ 197648: {},
197674: {},
+ 197704: {},
197706: {},
- 197798: {},
197830: {},
197882: {},
- 197889: {},
197897: {},
198068: {},
- 198161: {},
198252: {},
198265: {},
198279: {},
- 198394: {},
- 198433: {},
+ 198288: {},
198440: {},
- 198504: {},
+ 198441: {},
198589: {},
198605: {},
- 198631: {},
198668: {},
198735: {},
198820: {},
198961: {},
198966: {},
- 199046: {},
- 199081: {},
+ 199061: {},
199128: {},
199140: {},
+ 199274: {},
199276: {},
+ 199284: {},
+ 199468: {},
199469: {},
- 199490: {},
199493: {},
199524: {},
- 199565: {},
199620: {},
- 199636: {},
+ 199698: {},
199731: {},
199739: {},
199995: {},
@@ -2462,69 +2389,66 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
200088: {},
200134: {},
200154: {},
+ 200313: {},
200446: {},
+ 200565: {},
200590: {},
200612: {},
200640: {},
- 200683: {},
+ 200651: {},
+ 200665: {},
200697: {},
+ 200698: {},
200724: {},
200736: {},
200740: {},
+ 200742: {},
+ 200796: {},
200845: {},
200865: {},
- 200899: {},
- 200923: {},
- 201000: {},
- 201011: {},
201019: {},
- 201031: {},
201150: {},
201167: {},
- 201205: {},
201241: {},
201249: {},
- 201322: {},
201411: {},
- 201540: {},
- 201596: {},
+ 201505: {},
201603: {},
- 201746: {},
201749: {},
201767: {},
201776: {},
201814: {},
+ 201817: {},
201838: {},
201884: {},
201890: {},
- 201967: {},
+ 201898: {},
201986: {},
201997: {},
- 202065: {},
- 202085: {},
+ 202050: {},
202087: {},
202098: {},
202103: {},
202204: {},
202254: {},
- 202282: {},
202293: {},
202422: {},
202433: {},
202441: {},
+ 202448: {},
202468: {},
202498: {},
202561: {},
202613: {},
+ 202618: {},
202632: {},
202635: {},
+ 202662: {},
202699: {},
202870: {},
- 202877: {},
202921: {},
- 202931: {},
202940: {},
- 202960: {},
+ 202943: {},
202987: {},
203020: {},
203136: {},
@@ -2534,66 +2458,68 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
203409: {},
203424: {},
203448: {},
+ 203451: {},
203459: {},
- 203561: {},
203622: {},
203653: {},
203675: {},
203680: {},
203735: {},
203744: {},
- 203905: {},
+ 203811: {},
+ 203877: {},
203912: {},
- 203917: {},
203953: {},
+ 203964: {},
203971: {},
203995: {},
204106: {},
204108: {},
+ 204141: {},
204151: {},
+ 204165: {},
204170: {},
+ 204171: {},
+ 204209: {},
+ 204249: {},
204274: {},
204279: {},
204317: {},
204342: {},
204356: {},
+ 204393: {},
204403: {},
204457: {},
- 204467: {},
204548: {},
204565: {},
- 204566: {},
- 204592: {},
204595: {},
204601: {},
204649: {},
- 204666: {},
204716: {},
- 204793: {},
- 204802: {},
204804: {},
- 204873: {},
- 204918: {},
+ 204814: {},
+ 204902: {},
204957: {},
+ 204997: {},
+ 205015: {},
+ 205090: {},
205110: {},
205119: {},
205168: {},
205244: {},
205254: {},
+ 205277: {},
205278: {},
+ 205293: {},
205362: {},
205367: {},
205368: {},
- 205371: {},
- 205400: {},
205473: {},
- 205544: {},
+ 205521: {},
205547: {},
205638: {},
205647: {},
205714: {},
- 205800: {},
- 205832: {},
205889: {},
206026: {},
206065: {},
@@ -2601,80 +2527,71 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
206092: {},
206119: {},
206206: {},
+ 206238: {},
206262: {},
- 206283: {},
- 206296: {},
- 206358: {},
206375: {},
206406: {},
206471: {},
- 206561: {},
+ 206519: {},
+ 206557: {},
206610: {},
206611: {},
206641: {},
- 206663: {},
206666: {},
206774: {},
206783: {},
- 206804: {},
- 206841: {},
- 206977: {},
+ 206912: {},
+ 206920: {},
207044: {},
+ 207081: {},
207097: {},
207137: {},
- 207143: {},
- 207159: {},
+ 207154: {},
+ 207164: {},
207192: {},
- 207231: {},
- 207251: {},
- 207348: {},
+ 207355: {},
207369: {},
207375: {},
- 207408: {},
+ 207459: {},
+ 207464: {},
207502: {},
- 207568: {},
207569: {},
207589: {},
- 207604: {},
207651: {},
207713: {},
207728: {},
- 207782: {},
+ 207766: {},
207790: {},
207810: {},
207876: {},
207980: {},
- 207990: {},
207991: {},
- 208115: {},
- 208126: {},
208149: {},
208286: {},
+ 208306: {},
208320: {},
208339: {},
208448: {},
208555: {},
+ 208570: {},
208592: {},
208668: {},
- 208708: {},
+ 208670: {},
+ 208671: {},
208730: {},
208734: {},
- 208785: {},
- 208847: {},
- 208859: {},
+ 208857: {},
208905: {},
- 208909: {},
208972: {},
208997: {},
209046: {},
209049: {},
- 209193: {},
209196: {},
209240: {},
209262: {},
209277: {},
+ 209302: {},
209360: {},
- 209375: {},
209424: {},
209442: {},
209531: {},
@@ -2682,32 +2599,34 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
209835: {},
209839: {},
209854: {},
+ 209984: {},
210003: {},
210021: {},
210079: {},
- 210080: {},
210095: {},
- 210116: {},
- 210125: {},
+ 210150: {},
210218: {},
210278: {},
210315: {},
+ 210392: {},
210402: {},
- 210542: {},
+ 210616: {},
210625: {},
210644: {},
+ 210693: {},
210808: {},
+ 210964: {},
210974: {},
211028: {},
211057: {},
- 211144: {},
- 211145: {},
211147: {},
+ 211210: {},
211211: {},
+ 211322: {},
211356: {},
+ 211385: {},
211458: {},
211468: {},
- 211504: {},
211555: {},
211559: {},
211689: {},
@@ -2717,23 +2636,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
212183: {},
212238: {},
212330: {},
- 212370: {},
+ 212444: {},
212449: {},
212531: {},
212572: {},
+ 212616: {},
212637: {},
212645: {},
212655: {},
212661: {},
+ 212752: {},
212865: {},
212898: {},
212910: {},
212974: {},
- 212986: {},
212999: {},
213155: {},
- 213207: {},
- 213363: {},
+ 213373: {},
213398: {},
213402: {},
262145: {},
@@ -2742,27 +2661,21 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
262179: {},
262181: {},
262186: {},
- 262187: {},
262191: {},
262197: {},
262202: {},
262210: {},
- 262215: {},
262220: {},
- 262221: {},
262223: {},
262234: {},
262239: {},
- 262241: {},
262253: {},
262262: {},
262354: {},
+ 262378: {},
262468: {},
262481: {},
- 262493: {},
262589: {},
- 262659: {},
- 262673: {},
262753: {},
262773: {},
262916: {},
@@ -2771,16 +2684,16 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
263073: {},
263170: {},
263175: {},
- 263187: {},
- 263209: {},
+ 263189: {},
263210: {},
- 263216: {},
263222: {},
263223: {},
263224: {},
263238: {},
263242: {},
263245: {},
+ 263292: {},
+ 263327: {},
263686: {},
263689: {},
263694: {},
@@ -2790,11 +2703,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
263703: {},
263717: {},
263725: {},
- 263732: {},
263749: {},
263750: {},
263751: {},
263759: {},
+ 263760: {},
263761: {},
263762: {},
263763: {},
@@ -2802,11 +2715,12 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
263767: {},
263781: {},
263783: {},
+ 263791: {},
263792: {},
263793: {},
263805: {},
- 263809: {},
263824: {},
+ 263834: {},
263980: {},
264605: {},
264609: {},
@@ -2820,6 +2734,7 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
264663: {},
264668: {},
264685: {},
+ 264686: {},
264694: {},
264696: {},
264731: {},
@@ -2829,77 +2744,74 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
264750: {},
264756: {},
264758: {},
- 264764: {},
264770: {},
264778: {},
264779: {},
264780: {},
264783: {},
- 264793: {},
- 264796: {},
264814: {},
- 264819: {},
264821: {},
264825: {},
264837: {},
264838: {},
264844: {},
264847: {},
+ 264984: {},
265540: {},
+ 265561: {},
265594: {},
+ 265608: {},
265631: {},
265632: {},
265636: {},
- 265663: {},
265675: {},
265684: {},
+ 265686: {},
265688: {},
265691: {},
+ 265695: {},
265698: {},
- 265705: {},
265711: {},
265721: {},
265727: {},
- 265767: {},
+ 265741: {},
+ 265745: {},
265780: {},
265798: {},
265799: {},
265818: {},
265822: {},
- 265826: {},
+ 265823: {},
265855: {},
265867: {},
- 265880: {},
+ 266445: {},
266668: {},
266673: {},
- 266677: {},
- 266725: {},
- 266730: {},
266734: {},
266742: {},
266755: {},
- 266766: {},
- 266779: {},
+ 266757: {},
+ 266762: {},
266783: {},
266792: {},
- 266802: {},
266809: {},
+ 266812: {},
266814: {},
266815: {},
+ 266830: {},
+ 266831: {},
266841: {},
266853: {},
- 266858: {},
266860: {},
- 266870: {},
266880: {},
266893: {},
266894: {},
266904: {},
+ 267587: {},
267684: {},
267685: {},
267699: {},
267700: {},
- 267702: {},
267705: {},
267708: {},
267713: {},
@@ -2907,50 +2819,53 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
267761: {},
267765: {},
267795: {},
- 267797: {},
267803: {},
267809: {},
267828: {},
- 267832: {},
267837: {},
267845: {},
267846: {},
267882: {},
267883: {},
+ 267904: {},
+ 267916: {},
268323: {},
268976: {},
+ 269194: {},
269729: {},
269730: {},
269733: {},
269734: {},
- 269738: {},
269749: {},
269750: {},
269763: {},
+ 269769: {},
269780: {},
269782: {},
269783: {},
269788: {},
+ 269794: {},
269797: {},
269806: {},
269816: {},
269820: {},
269822: {},
+ 269829: {},
269831: {},
269832: {},
269838: {},
269840: {},
- 269843: {},
269853: {},
269857: {},
269862: {},
269894: {},
+ 269898: {},
269901: {},
- 269905: {},
269908: {},
+ 269909: {},
+ 269918: {},
269919: {},
269921: {},
- 269926: {},
269928: {},
269931: {},
269934: {},
@@ -2962,17 +2877,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
269955: {},
269964: {},
269965: {},
- 269974: {},
+ 269973: {},
269976: {},
269981: {},
- 269989: {},
270007: {},
- 270023: {},
270026: {},
270029: {},
270035: {},
270036: {},
- 270045: {},
270049: {},
270052: {},
270058: {},
@@ -2983,22 +2895,20 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
270108: {},
270161: {},
270814: {},
- 271689: {},
271773: {},
271791: {},
+ 271793: {},
271795: {},
- 271806: {},
- 271808: {},
271812: {},
271814: {},
271819: {},
271822: {},
271835: {},
271837: {},
+ 271846: {},
271855: {},
271868: {},
271874: {},
- 271880: {},
271898: {},
271899: {},
271907: {},
@@ -3006,22 +2916,23 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
271911: {},
271929: {},
271930: {},
+ 271931: {},
271932: {},
271935: {},
+ 271936: {},
+ 271942: {},
271965: {},
271971: {},
- 271988: {},
+ 271985: {},
271996: {},
- 272006: {},
272011: {},
272015: {},
272018: {},
272019: {},
272026: {},
272055: {},
- 272062: {},
+ 272057: {},
272073: {},
- 272075: {},
272083: {},
272102: {},
272106: {},
@@ -3031,11 +2942,14 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
272134: {},
272809: {},
272818: {},
+ 272832: {},
272836: {},
- 272848: {},
272851: {},
+ 272868: {},
272882: {},
- 272883: {},
+ 272916: {},
+ 272943: {},
+ 272946: {},
272954: {},
327687: {},
327693: {},
@@ -3044,14 +2958,11 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
327707: {},
327712: {},
327714: {},
- 327716: {},
327724: {},
327725: {},
327738: {},
- 327742: {},
327747: {},
327750: {},
- 327752: {},
327756: {},
327760: {},
327765: {},
@@ -3063,33 +2974,25 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
327786: {},
327794: {},
327795: {},
- 327798: {},
327799: {},
327802: {},
- 327804: {},
- 327809: {},
- 327814: {},
327819: {},
- 327820: {},
327828: {},
327829: {},
327830: {},
- 327849: {},
- 327864: {},
327871: {},
- 327872: {},
+ 327879: {},
327885: {},
327900: {},
327901: {},
327903: {},
327931: {},
- 327932: {},
327934: {},
- 327947: {},
327972: {},
327975: {},
327990: {},
327991: {},
+ 327992: {},
327996: {},
328015: {},
328061: {},
@@ -3098,14 +3001,12 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
328079: {},
328088: {},
328111: {},
- 328114: {},
- 328118: {},
328136: {},
328140: {},
328144: {},
- 328154: {},
328169: {},
328182: {},
+ 328187: {},
328191: {},
328196: {},
328198: {},
@@ -3117,106 +3018,105 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
328250: {},
328253: {},
328258: {},
- 328271: {},
- 328284: {},
+ 328259: {},
328286: {},
328297: {},
328304: {},
328309: {},
+ 328310: {},
328319: {},
328331: {},
328341: {},
- 328344: {},
+ 328358: {},
328411: {},
- 328429: {},
+ 328432: {},
+ 328436: {},
328442: {},
- 328453: {},
328469: {},
328471: {},
+ 328473: {},
328475: {},
328479: {},
328480: {},
328488: {},
328490: {},
- 328491: {},
328494: {},
- 328509: {},
328510: {},
+ 328514: {},
328535: {},
328539: {},
328546: {},
328549: {},
- 328566: {},
- 328567: {},
- 328570: {},
+ 328576: {},
328578: {},
328581: {},
328586: {},
328590: {},
328594: {},
- 328600: {},
328605: {},
328610: {},
328611: {},
+ 328614: {},
328619: {},
+ 328636: {},
328638: {},
328652: {},
- 328676: {},
+ 328679: {},
328697: {},
+ 328702: {},
328708: {},
328717: {},
328727: {},
+ 328733: {},
328734: {},
328753: {},
328755: {},
328770: {},
- 328785: {},
+ 328777: {},
+ 328808: {},
328817: {},
328844: {},
328856: {},
- 328857: {},
328858: {},
- 328880: {},
328887: {},
- 328892: {},
- 328899: {},
- 328919: {},
+ 328895: {},
328939: {},
328943: {},
328954: {},
- 328959: {},
328961: {},
- 328965: {},
328975: {},
328977: {},
328983: {},
328987: {},
328988: {},
+ 328989: {},
328993: {},
328997: {},
329014: {},
- 329021: {},
329027: {},
329029: {},
329044: {},
329048: {},
- 329074: {},
329078: {},
329082: {},
329094: {},
329101: {},
329103: {},
+ 329110: {},
329129: {},
329135: {},
- 329155: {},
+ 329140: {},
329167: {},
+ 329174: {},
+ 329179: {},
329183: {},
+ 329205: {},
329211: {},
- 329255: {},
+ 329219: {},
329288: {},
+ 393275: {},
393629: {},
- 393678: {},
- 393682: {},
+ 393894: {},
394311: {},
394381: {},
395561: {},
@@ -3224,261 +3124,258 @@ var DefaultTopASNs = map[agd.ASN]struct{}{
395965: {},
396082: {},
396304: {},
- 396356: {},
396357: {},
- 396982: {},
- 397399: {},
+ 396420: {},
+ 397470: {},
397545: {},
+ 397563: {},
397961: {},
398228: {},
+ 398326: {},
398721: {},
398901: {},
399382: {},
399686: {},
399724: {},
400266: {},
- 400354: {},
400618: {},
}
// DefaultCountryTopASNs is a mapping of a country to their top ASNs.
-var DefaultCountryTopASNs = map[agd.Country]agd.ASN{
- agd.CountryAD: 6752,
- agd.CountryAE: 5384,
- agd.CountryAF: 55330,
- agd.CountryAG: 11594,
- agd.CountryAI: 11139,
- agd.CountryAL: 50616,
- agd.CountryAM: 12297,
- agd.CountryAO: 37119,
- agd.CountryAR: 7303,
- agd.CountryAS: 9751,
- agd.CountryAT: 8412,
- agd.CountryAU: 1221,
- agd.CountryAW: 11816,
- agd.CountryAX: 3238,
- agd.CountryAZ: 34170,
- agd.CountryBA: 9146,
- agd.CountryBB: 14813,
- agd.CountryBD: 24389,
- agd.CountryBE: 5432,
- agd.CountryBF: 37577,
- agd.CountryBG: 8866,
- agd.CountryBH: 51375,
- agd.CountryBI: 327799,
- agd.CountryBJ: 37136,
- agd.CountryBM: 32020,
- agd.CountryBN: 10094,
- agd.CountryBO: 6568,
- agd.CountryBQ: 27694,
- agd.CountryBR: 28573,
- agd.CountryBS: 15146,
- agd.CountryBT: 18024,
- agd.CountryBW: 14988,
- agd.CountryBY: 25106,
- agd.CountryBZ: 10269,
- agd.CountryCA: 812,
- agd.CountryCC: 198605,
- agd.CountryCD: 37020,
- agd.CountryCF: 328079,
- agd.CountryCG: 36924,
- agd.CountryCH: 3303,
- agd.CountryCI: 29571,
- agd.CountryCK: 10131,
- agd.CountryCL: 7418,
- agd.CountryCM: 30992,
- agd.CountryCN: 4134,
- agd.CountryCO: 10620,
- agd.CountryCR: 52263,
- agd.CountryCU: 27725,
- agd.CountryCV: 37517,
- agd.CountryCW: 52233,
- agd.CountryCY: 6866,
- agd.CountryCZ: 5610,
- agd.CountryDE: 3320,
- agd.CountryDJ: 30990,
- agd.CountryDK: 3292,
- agd.CountryDM: 40945,
- agd.CountryDO: 6400,
- agd.CountryDZ: 36947,
- agd.CountryEC: 27947,
- agd.CountryEE: 44477,
- agd.CountryEG: 8452,
- agd.CountryER: 24757,
- agd.CountryES: 3352,
- agd.CountryET: 24757,
- agd.CountryFI: 51765,
- agd.CountryFJ: 38442,
- agd.CountryFK: 204649,
- agd.CountryFM: 139759,
- agd.CountryFO: 15389,
- agd.CountryFR: 3215,
- agd.CountryGA: 16058,
- agd.CountryGB: 2856,
- agd.CountryGD: 46650,
- agd.CountryGE: 16010,
- agd.CountryGF: 3215,
- agd.CountryGG: 8680,
- agd.CountryGH: 30986,
- agd.CountryGI: 8301,
- agd.CountryGL: 8818,
- agd.CountryGM: 37309,
- agd.CountryGN: 37461,
- agd.CountryGP: 3215,
- agd.CountryGQ: 37337,
- agd.CountryGR: 6799,
- agd.CountryGT: 14754,
- agd.CountryGU: 7131,
- agd.CountryGW: 37559,
- agd.CountryGY: 19863,
- agd.CountryHK: 45102,
- agd.CountryHN: 14754,
- agd.CountryHR: 5391,
- agd.CountryHT: 27653,
- agd.CountryHU: 5483,
- agd.CountryID: 7713,
- agd.CountryIE: 15502,
- agd.CountryIL: 1680,
- agd.CountryIM: 13122,
- agd.CountryIN: 55836,
- agd.CountryIO: 17458,
- agd.CountryIQ: 203214,
- agd.CountryIR: 197207,
- agd.CountryIS: 44925,
- agd.CountryIT: 1267,
- agd.CountryJE: 8680,
- agd.CountryJM: 30689,
- agd.CountryJO: 8376,
- agd.CountryJP: 2516,
- agd.CountryKE: 33771,
- agd.CountryKG: 47237,
- agd.CountryKH: 38623,
- agd.CountryKI: 134783,
- agd.CountryKM: 36939,
- agd.CountryKN: 36290,
- agd.CountryKR: 4766,
- agd.CountryKW: 29357,
- agd.CountryKY: 6639,
- agd.CountryKZ: 206026,
- agd.CountryLA: 9873,
- agd.CountryLB: 42003,
- agd.CountryLC: 15344,
- agd.CountryLI: 9009,
- agd.CountryLK: 18001,
- agd.CountryLR: 37094,
- agd.CountryLS: 33567,
- agd.CountryLT: 8764,
- agd.CountryLU: 53667,
- agd.CountryLV: 24921,
- agd.CountryLY: 21003,
- agd.CountryMA: 36903,
- agd.CountryMC: 6758,
- agd.CountryMD: 8926,
- agd.CountryME: 43940,
- agd.CountryMF: 33392,
- agd.CountryMG: 37054,
- agd.CountryMH: 24439,
- agd.CountryMK: 6821,
- agd.CountryML: 30985,
- agd.CountryMM: 136255,
- agd.CountryMN: 17882,
- agd.CountryMO: 4609,
- agd.CountryMP: 7131,
- agd.CountryMQ: 3215,
- agd.CountryMR: 29544,
- agd.CountryMS: 396304,
- agd.CountryMT: 12709,
- agd.CountryMU: 23889,
- agd.CountryMV: 55944,
- agd.CountryMW: 37440,
- agd.CountryMX: 8151,
- agd.CountryMY: 4788,
- agd.CountryMZ: 37342,
- agd.CountryNA: 36996,
- agd.CountryNC: 18200,
- agd.CountryNE: 37531,
- agd.CountryNF: 198605,
- agd.CountryNG: 29465,
- agd.CountryNI: 14754,
- agd.CountryNL: 1136,
- agd.CountryNO: 2119,
- agd.CountryNP: 17501,
- agd.CountryNR: 140504,
- agd.CountryNU: 198605,
- agd.CountryNZ: 9790,
- agd.CountryOM: 28885,
- agd.CountryPA: 18809,
- agd.CountryPE: 12252,
- agd.CountryPF: 9471,
- agd.CountryPG: 139898,
- agd.CountryPH: 9299,
- agd.CountryPK: 45669,
- agd.CountryPL: 43447,
- agd.CountryPM: 3695,
- agd.CountryPR: 14638,
- agd.CountryPS: 12975,
- agd.CountryPT: 3243,
- agd.CountryPW: 17893,
- agd.CountryPY: 23201,
- agd.CountryQA: 42298,
- agd.CountryRE: 37002,
- agd.CountryRO: 8708,
- agd.CountryRS: 8400,
- agd.CountryRU: 8359,
- agd.CountryRW: 36924,
- agd.CountrySA: 39891,
- agd.CountrySB: 45891,
- agd.CountrySC: 36958,
- agd.CountrySD: 15706,
- agd.CountrySE: 60068,
- agd.CountrySG: 4773,
- agd.CountrySH: 33763,
- agd.CountrySI: 5603,
- agd.CountrySK: 6855,
- agd.CountrySL: 37164,
- agd.CountrySM: 15433,
- agd.CountrySN: 8346,
- agd.CountrySO: 37371,
- agd.CountrySR: 27775,
- agd.CountrySS: 328755,
- agd.CountryST: 328191,
- agd.CountrySV: 14754,
- agd.CountrySX: 27734,
- agd.CountrySY: 29256,
- agd.CountrySZ: 328169,
- agd.CountryTC: 22933,
- agd.CountryTD: 327802,
- agd.CountryTF: 52000,
- agd.CountryTG: 36924,
- agd.CountryTH: 131445,
- agd.CountryTJ: 43197,
- agd.CountryTK: 4648,
- agd.CountryTL: 133606,
- agd.CountryTM: 198605,
- agd.CountryTN: 37705,
- agd.CountryTO: 38201,
- agd.CountryTR: 47331,
- agd.CountryTT: 27800,
- agd.CountryTV: 23917,
- agd.CountryTW: 3462,
- agd.CountryTZ: 36908,
- agd.CountryUA: 15895,
- agd.CountryUG: 37075,
- agd.CountryUS: 7922,
- agd.CountryUY: 6057,
- agd.CountryUZ: 8193,
- agd.CountryVC: 46408,
- agd.CountryVE: 8048,
- agd.CountryVG: 396357,
- agd.CountryVI: 14434,
- agd.CountryVN: 7552,
- agd.CountryVU: 45355,
- agd.CountryWF: 45879,
- agd.CountryWS: 17993,
- agd.CountryXK: 21246,
- agd.CountryYE: 30873,
- agd.CountryYT: 3215,
- agd.CountryZA: 37457,
- agd.CountryZM: 37287,
- agd.CountryZW: 37204,
+var DefaultCountryTopASNs = map[Country]ASN{
+ CountryAD: 6752,
+ CountryAE: 5384,
+ CountryAF: 55330,
+ CountryAG: 11594,
+ CountryAI: 2740,
+ CountryAL: 21183,
+ CountryAM: 12297,
+ CountryAO: 37119,
+ CountryAQ: 13335,
+ CountryAR: 7303,
+ CountryAS: 9751,
+ CountryAT: 8412,
+ CountryAU: 1221,
+ CountryAW: 11816,
+ CountryAX: 3238,
+ CountryAZ: 28787,
+ CountryBA: 9146,
+ CountryBB: 14813,
+ CountryBD: 24389,
+ CountryBE: 5432,
+ CountryBF: 37577,
+ CountryBG: 8866,
+ CountryBH: 5416,
+ CountryBI: 327799,
+ CountryBJ: 37424,
+ CountryBM: 32020,
+ CountryBN: 10094,
+ CountryBO: 6568,
+ CountryBQ: 27745,
+ CountryBR: 28573,
+ CountryBS: 15146,
+ CountryBT: 18024,
+ CountryBW: 14988,
+ CountryBY: 6697,
+ CountryBZ: 10269,
+ CountryCA: 812,
+ CountryCD: 37020,
+ CountryCF: 37460,
+ CountryCG: 36924,
+ CountryCH: 3303,
+ CountryCI: 29571,
+ CountryCK: 10131,
+ CountryCL: 7418,
+ CountryCM: 36912,
+ CountryCN: 4134,
+ CountryCO: 10620,
+ CountryCR: 11830,
+ CountryCU: 27725,
+ CountryCV: 37517,
+ CountryCW: 52233,
+ CountryCY: 6866,
+ CountryCZ: 5610,
+ CountryDE: 3320,
+ CountryDJ: 30990,
+ CountryDK: 3292,
+ CountryDM: 40945,
+ CountryDO: 6400,
+ CountryDZ: 36947,
+ CountryEC: 27947,
+ CountryEE: 44477,
+ CountryEG: 8452,
+ CountryER: 24757,
+ CountryES: 3352,
+ CountryET: 24757,
+ CountryFI: 51765,
+ CountryFJ: 38442,
+ CountryFK: 204649,
+ CountryFM: 139759,
+ CountryFO: 15389,
+ CountryFR: 3215,
+ CountryGA: 36924,
+ CountryGB: 2856,
+ CountryGD: 46650,
+ CountryGE: 16010,
+ CountryGF: 3215,
+ CountryGG: 8680,
+ CountryGH: 30986,
+ CountryGI: 8301,
+ CountryGL: 8818,
+ CountryGM: 37309,
+ CountryGN: 37461,
+ CountryGP: 3215,
+ CountryGQ: 37529,
+ CountryGR: 6799,
+ CountryGT: 14754,
+ CountryGU: 9246,
+ CountryGW: 37559,
+ CountryGY: 19863,
+ CountryHK: 4760,
+ CountryHN: 14754,
+ CountryHR: 5391,
+ CountryHT: 27653,
+ CountryHU: 5483,
+ CountryID: 7713,
+ CountryIE: 15502,
+ CountryIL: 1680,
+ CountryIM: 13122,
+ CountryIN: 55836,
+ CountryIO: 17458,
+ CountryIQ: 203214,
+ CountryIR: 44244,
+ CountryIS: 44735,
+ CountryIT: 1267,
+ CountryJE: 8680,
+ CountryJM: 30689,
+ CountryJO: 8376,
+ CountryJP: 2516,
+ CountryKE: 33771,
+ CountryKG: 50223,
+ CountryKH: 38623,
+ CountryKI: 134783,
+ CountryKM: 36939,
+ CountryKN: 11139,
+ CountryKP: 198605,
+ CountryKR: 4766,
+ CountryKW: 29357,
+ CountryKY: 16705,
+ CountryKZ: 206026,
+ CountryLA: 131267,
+ CountryLB: 42003,
+ CountryLC: 15344,
+ CountryLI: 56653,
+ CountryLK: 18001,
+ CountryLR: 37094,
+ CountryLS: 33567,
+ CountryLT: 8764,
+ CountryLU: 6661,
+ CountryLV: 24921,
+ CountryLY: 21003,
+ CountryMA: 36903,
+ CountryMC: 6758,
+ CountryMD: 8926,
+ CountryME: 43940,
+ CountryMF: 33392,
+ CountryMG: 37054,
+ CountryMH: 24439,
+ CountryMK: 6821,
+ CountryML: 30985,
+ CountryMM: 136255,
+ CountryMN: 17882,
+ CountryMO: 4609,
+ CountryMP: 7131,
+ CountryMQ: 3215,
+ CountryMR: 29544,
+ CountryMS: 396304,
+ CountryMT: 12709,
+ CountryMU: 23889,
+ CountryMV: 7642,
+ CountryMW: 37440,
+ CountryMX: 8151,
+ CountryMY: 4788,
+ CountryMZ: 37342,
+ CountryNA: 36996,
+ CountryNC: 18200,
+ CountryNE: 37531,
+ CountryNG: 29465,
+ CountryNI: 14754,
+ CountryNL: 1136,
+ CountryNO: 29695,
+ CountryNP: 17501,
+ CountryNR: 140504,
+ CountryNZ: 9790,
+ CountryOM: 28885,
+ CountryPA: 11556,
+ CountryPE: 12252,
+ CountryPF: 9471,
+ CountryPG: 139898,
+ CountryPH: 9299,
+ CountryPK: 45669,
+ CountryPL: 5617,
+ CountryPM: 3695,
+ CountryPR: 14638,
+ CountryPS: 12975,
+ CountryPT: 3243,
+ CountryPW: 17893,
+ CountryPY: 23201,
+ CountryQA: 42298,
+ CountryRE: 3215,
+ CountryRO: 8708,
+ CountryRS: 8400,
+ CountryRU: 8359,
+ CountryRW: 36890,
+ CountrySA: 39891,
+ CountrySB: 45891,
+ CountrySC: 36958,
+ CountrySD: 15706,
+ CountrySE: 1257,
+ CountrySG: 4773,
+ CountrySH: 33763,
+ CountrySI: 3212,
+ CountrySK: 6855,
+ CountrySL: 36988,
+ CountrySM: 15433,
+ CountrySN: 8346,
+ CountrySO: 37371,
+ CountrySR: 27775,
+ CountrySS: 37594,
+ CountryST: 328191,
+ CountrySV: 14754,
+ CountrySX: 27781,
+ CountrySY: 29256,
+ CountrySZ: 328169,
+ CountryTC: 394311,
+ CountryTD: 327802,
+ CountryTG: 36924,
+ CountryTH: 131445,
+ CountryTJ: 43197,
+ CountryTL: 58731,
+ CountryTM: 20661,
+ CountryTN: 37693,
+ CountryTO: 38201,
+ CountryTR: 47331,
+ CountryTT: 27800,
+ CountryTW: 3462,
+ CountryTZ: 36908,
+ CountryUA: 15895,
+ CountryUG: 37075,
+ CountryUS: 7922,
+ CountryUY: 6057,
+ CountryUZ: 8193,
+ CountryVA: 8978,
+ CountryVC: 46408,
+ CountryVE: 8048,
+ CountryVG: 396357,
+ CountryVI: 14434,
+ CountryVN: 7552,
+ CountryVU: 9249,
+ CountryWF: 45879,
+ CountryWS: 38800,
+ CountryXK: 21246,
+ CountryYE: 30873,
+ CountryYT: 3215,
+ CountryZA: 37457,
+ CountryZM: 37287,
+ CountryZW: 37204,
}
diff --git a/internal/geoip/asntops_generate.go b/internal/geoip/asntops_generate.go
index 32d2c29..a821035 100644
--- a/internal/geoip/asntops_generate.go
+++ b/internal/geoip/asntops_generate.go
@@ -9,8 +9,8 @@ import (
"text/template"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
)
@@ -33,11 +33,11 @@ func main() {
check(err)
defer log.OnCloserError(out, log.ERROR)
- defaultCountryTopASNs := map[agd.Country][]agd.ASN{}
+ defaultCountryTopASNs := map[geoip.Country][]geoip.ASN{}
err = json.NewDecoder(resp.Body).Decode(&defaultCountryTopASNs)
check(err)
- defaultTopASNs := map[agd.ASN]struct{}{}
+ defaultTopASNs := map[geoip.ASN]struct{}{}
for _, asns := range defaultCountryTopASNs {
for _, asn := range asns {
if asn != 0 {
@@ -47,8 +47,8 @@ func main() {
}
type templateData struct {
- DefaultTopASNs map[agd.ASN]struct{}
- DefaultCountryTopASNs map[agd.Country][]agd.ASN
+ DefaultTopASNs map[geoip.ASN]struct{}
+ DefaultCountryTopASNs map[geoip.Country][]geoip.ASN
}
tmplData := &templateData{
@@ -72,20 +72,18 @@ const tmplStr = `// Code generated by go run ./asntops_generate.go; DO NOT EDIT.
package geoip
-import "github.com/AdguardTeam/AdGuardDNS/internal/agd"
-
// DefaultTopASNs contains all specially handled ASNs.
-var DefaultTopASNs = map[agd.ASN]struct{}{
+var DefaultTopASNs = map[ASN]struct{}{
{{- range $asn, $_ := .DefaultTopASNs }}
{{ printf "%-7s {}," ( printf "%d:" $asn ) }}
{{- end }}
}
// DefaultCountryTopASNs is a mapping of a country to their top ASNs.
-var DefaultCountryTopASNs = map[agd.Country]agd.ASN{
+var DefaultCountryTopASNs = map[Country]ASN{
{{- range $ctry, $ASNs := .DefaultCountryTopASNs }}
{{- if gt (len $ASNs) 0 }}
- agd.Country{{ $ctry }}: {{ index $ASNs 0 }},
+ Country{{ $ctry }}: {{ index $ASNs 0 }},
{{- else }}
{{- continue }}
{{- end }}
diff --git a/internal/agd/country.go b/internal/geoip/country.go
similarity index 99%
rename from internal/agd/country.go
rename to internal/geoip/country.go
index 8e0a52c..d4a3369 100644
--- a/internal/agd/country.go
+++ b/internal/geoip/country.go
@@ -1,6 +1,6 @@
// Code generated by go run ./country_generate.go; DO NOT EDIT.
-package agd
+package geoip
import (
"encoding"
diff --git a/internal/agd/country_generate.go b/internal/geoip/country_generate.go
similarity index 98%
rename from internal/agd/country_generate.go
rename to internal/geoip/country_generate.go
index a958bb4..e522656 100644
--- a/internal/agd/country_generate.go
+++ b/internal/geoip/country_generate.go
@@ -6,6 +6,7 @@ import (
"encoding/csv"
"net/http"
"os"
+ "slices"
"strings"
"text/template"
"time"
@@ -13,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
- "golang.org/x/exp/slices"
)
func main() {
@@ -60,7 +60,7 @@ const csvURL = `https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-
// tmplStr is the template of the generated Go code.
const tmplStr = `// Code generated by go run ./country_generate.go; DO NOT EDIT.
-package agd
+package geoip
import (
"encoding"
diff --git a/internal/geoip/error.go b/internal/geoip/error.go
new file mode 100644
index 0000000..5b42b4b
--- /dev/null
+++ b/internal/geoip/error.go
@@ -0,0 +1,27 @@
+package geoip
+
+import "fmt"
+
+// NotACountryError is returned from NewCountry when the string doesn't
+// represent a valid country.
+type NotACountryError struct {
+ // Code is the code presented to NewCountry.
+ Code string
+}
+
+// Error implements the error interface for *NotACountryError.
+func (err *NotACountryError) Error() (msg string) {
+ return fmt.Sprintf("%q is not a valid iso 3166-1 alpha-2 code", err.Code)
+}
+
+// NotAContinentError is returned from NewContinent when the string doesn't
+// represent a valid continent.
+type NotAContinentError struct {
+ // Code is the code presented to NewContinent.
+ Code string
+}
+
+// Error implements the error interface for *NotAContinentError.
+func (err *NotAContinentError) Error() (msg string) {
+ return fmt.Sprintf("%q is not a valid continent code", err.Code)
+}
diff --git a/internal/geoip/file.go b/internal/geoip/file.go
index 7eb74c5..e9fd29d 100644
--- a/internal/geoip/file.go
+++ b/internal/geoip/file.go
@@ -8,7 +8,6 @@ import (
"os"
"sync"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
@@ -23,10 +22,10 @@ type FileConfig struct {
// AllTopASNs contains all subnets from CountryTopASNs. While scanning the
// statistics data file this list is used as a dictionary to check if the
// current ASN included in CountryTopASNs.
- AllTopASNs map[agd.ASN]struct{}
+ AllTopASNs map[ASN]struct{}
// CountryTopASNs is a mapping of a country to their top ASNs.
- CountryTopASNs map[agd.Country]agd.ASN
+ CountryTopASNs map[Country]ASN
// ASNPath is the path to the GeoIP database of ASNs.
ASNPath string
@@ -45,8 +44,8 @@ type FileConfig struct {
// File is a file implementation of [geoip.Interface].
type File struct {
- allTopASNs map[agd.ASN]struct{}
- countryTopASNs map[agd.Country]agd.ASN
+ allTopASNs map[ASN]struct{}
+ countryTopASNs map[Country]ASN
// mu protects asn, country, country subnet maps, and caches against
// simultaneous access during a refresh.
@@ -59,11 +58,12 @@ type File struct {
// removing these.
//
// See AGDNS-710.
+ // TODO(a.garipov): Switch to locationSubnets instead?
ipv4CountrySubnets countrySubnets
ipv6CountrySubnets countrySubnets
- ipv4TopASNSubnets asnSubnets
- ipv6TopASNSubnets asnSubnets
+ ipv4LocationSubnets locationSubnets
+ ipv6LocationSubnets locationSubnets
ipCache gcache.Cache
hostCache gcache.Cache
@@ -76,10 +76,37 @@ type File struct {
}
// countrySubnets is a country-to-subnet mapping.
-type countrySubnets map[agd.Country]netip.Prefix
+type countrySubnets map[Country]netip.Prefix
-// asnSubnets is an ASN-to-subnet mapping.
-type asnSubnets map[agd.ASN]netip.Prefix
+// locationSubnets is a locationKey-to-subnet mapping.
+type locationSubnets map[locationKey]netip.Prefix
+
+// locationKey represents a key for locationSubnets mapping.
+type locationKey struct {
+ country Country
+ topSubdivision string
+ asn ASN
+}
+
+// newLocationKey returns a key for locationKey-to-subnet mapping. The location
+// with determined subdivision is used only for certain countries with the
+// purpose to limit the total amount of items in the mapping.
+//
+// See AGDNS-1622.
+func newLocationKey(asn ASN, ctry Country, subdiv string) (l locationKey) {
+ switch ctry {
+ case CountryRU, CountryUS, CountryCN, CountryIN:
+ return locationKey{
+ asn: asn,
+ country: ctry,
+ topSubdivision: subdiv,
+ }
+ default:
+ return locationKey{
+ asn: asn,
+ }
+ }
+}
// NewFile returns a new GeoIP database that reads information from a file.
func NewFile(c *FileConfig) (f *File, err error) {
@@ -130,7 +157,8 @@ func ipToCacheKey(ip netip.Addr) (k any) {
var _ Interface = (*File)(nil)
// SubnetByLocation implements the Interface interface for *File. fam must be
-// either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
+// either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6]. l must not be
+// nil.
//
// The process of the subnet selection is as follows:
//
@@ -147,40 +175,39 @@ var _ Interface = (*File)(nil)
// subnet is returned. If the information about the most used ASNs is not
// available, the first subnet from the country that is broad enough (see
// resetCountrySubnets) is chosen.
-func (f *File) SubnetByLocation(
- c agd.Country,
- asn agd.ASN,
- fam netutil.AddrFamily,
-) (n netip.Prefix, err error) {
- var topASNSubnets asnSubnets
+func (f *File) SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Prefix, err error) {
var ctrySubnets countrySubnets
+ var locSubnets locationSubnets
f.mu.RLock()
defer f.mu.RUnlock()
switch fam {
case netutil.AddrFamilyIPv4:
- topASNSubnets = f.ipv4TopASNSubnets
ctrySubnets = f.ipv4CountrySubnets
+ locSubnets = f.ipv4LocationSubnets
case netutil.AddrFamilyIPv6:
- topASNSubnets = f.ipv6TopASNSubnets
ctrySubnets = f.ipv6CountrySubnets
+ locSubnets = f.ipv6LocationSubnets
default:
panic(fmt.Errorf("geoip: unsupported addr fam %s", fam))
}
+ locKey := newLocationKey(l.ASN, l.Country, l.TopSubdivision)
+
var ok bool
- if n, ok = topASNSubnets[asn]; ok {
+ if n, ok = locSubnets[locKey]; ok {
+ // First lookup in location map.
return n, nil
- } else if asn, ok = f.countryTopASNs[c]; ok {
+ } else if l.ASN, ok = f.countryTopASNs[l.Country]; ok {
// Technically, if there is an entry in countryTopASNs then that entry
// also always exists in topASNSubnets, but let's be defensive about it.
- if n, ok = topASNSubnets[asn]; ok {
+ if n, ok = locSubnets[newLocationKey(l.ASN, CountryNone, "")]; ok {
return n, nil
}
}
- if n, ok = ctrySubnets[c]; ok {
+ if n, ok = ctrySubnets[l.Country]; ok {
return n, nil
}
@@ -189,7 +216,7 @@ func (f *File) SubnetByLocation(
// 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 *agd.Location, err error) {
+func (f *File) Data(host string, ip netip.Addr) (l *Location, err error) {
if ip == (netip.Addr{}) {
return f.dataByHost(host), nil
} else if ip.Is4In6() {
@@ -204,7 +231,7 @@ func (f *File) Data(host string, ip netip.Addr) (l *agd.Location, err error) {
if err == nil {
metrics.GeoIPCacheLookupsHits.Inc()
- return locVal.(*agd.Location), nil
+ return locVal.(*Location), nil
} else if !errors.Is(err, gcache.KeyNotFoundError) {
// Shouldn't happen, since we don't set a serialization function.
panic(fmt.Errorf("getting from ip cache: %w", err))
@@ -220,7 +247,7 @@ func (f *File) Data(host string, ip netip.Addr) (l *agd.Location, err error) {
return nil, fmt.Errorf("looking up asn: %w", err)
}
- l = &agd.Location{
+ l = &Location{
ASN: asn,
}
@@ -236,7 +263,7 @@ func (f *File) Data(host string, ip netip.Addr) (l *agd.Location, err error) {
}
// dataByHost returns GeoIP data that has been cached previously.
-func (f *File) dataByHost(host string) (l *agd.Location) {
+func (f *File) dataByHost(host string) (l *Location) {
locVal, err := f.hostCache.Get(host)
if err != nil {
if errors.Is(err, gcache.KeyNotFoundError) {
@@ -251,7 +278,7 @@ func (f *File) dataByHost(host string) (l *agd.Location) {
metrics.GeoIPHostCacheLookupsHits.Inc()
- return locVal.(*agd.Location)
+ return locVal.(*Location)
}
// asnResult is used to retrieve autonomous system number data from a GeoIP
@@ -262,7 +289,7 @@ type asnResult struct {
// lookupASN looks up and returns the autonomous system number part of the GeoIP
// data for ip.
-func (f *File) lookupASN(ip netip.Addr) (asn agd.ASN, err error) {
+func (f *File) lookupASN(ip netip.Addr) (asn ASN, err error) {
// TODO(a.garipov): Remove AsSlice if oschwald/maxminddb-golang#88 is done.
var res asnResult
err = f.asn.Lookup(ip.AsSlice(), &res)
@@ -270,7 +297,7 @@ func (f *File) lookupASN(ip netip.Addr) (asn agd.ASN, err error) {
return 0, fmt.Errorf("looking up asn: %w", err)
}
- return agd.ASN(res.ASN), nil
+ return ASN(res.ASN), nil
}
// countryResult is used to retrieve the continent and country data from a GeoIP
@@ -289,7 +316,7 @@ type countryResult struct {
// setCtry looks up and sets the country, continent and the subdivision parts
// of the GeoIP data for ip into loc. loc must not be nil.
-func (f *File) setCtry(loc *agd.Location, ip netip.Addr) (err error) {
+func (f *File) setCtry(loc *Location, ip netip.Addr) (err error) {
// TODO(a.garipov): Remove AsSlice if oschwald/maxminddb-golang#88 is done.
var res countryResult
err = f.country.Lookup(ip.AsSlice(), &res)
@@ -297,12 +324,12 @@ func (f *File) setCtry(loc *agd.Location, ip netip.Addr) (err error) {
return fmt.Errorf("looking up country: %w", err)
}
- loc.Country, err = agd.NewCountry(res.Country.ISOCode)
+ loc.Country, err = NewCountry(res.Country.ISOCode)
if err != nil {
return fmt.Errorf("converting country: %w", err)
}
- loc.Continent, err = agd.NewContinent(res.Continent.Code)
+ loc.Continent, err = NewContinent(res.Continent.Code)
if err != nil {
return fmt.Errorf("converting continent: %w", err)
}
@@ -315,7 +342,7 @@ func (f *File) setCtry(loc *agd.Location, ip netip.Addr) (err error) {
}
// setCaches sets the GeoIP data into the caches.
-func (f *File) setCaches(host string, ipCacheKey any, l *agd.Location) {
+func (f *File) setCaches(host string, ipCacheKey any, l *Location) {
err := f.ipCache.Set(ipCacheKey, l)
if err != nil {
// Shouldn't happen, since we don't set a serialization function.
@@ -333,15 +360,9 @@ func (f *File) setCaches(host string, ipCacheKey any, l *agd.Location) {
}
}
-// type check
-var _ agd.Refresher = (*File)(nil)
-
-// Refresh implements the agd.Refresher interface for *File. It reopens the
-// GeoIP database files.
+// Refresh implements the [agdservice.Refresher] interface for *File. It
+// reopens the GeoIP database files.
func (f *File) Refresh(_ context.Context) (err error) {
- var wg sync.WaitGroup
- wg.Add(2)
-
asn, err := geoIPFromFile(f.asnPath)
if err != nil {
metrics.GeoIPUpdateStatus.WithLabelValues(f.asnPath).Set(0)
@@ -356,61 +377,19 @@ func (f *File) Refresh(_ context.Context) (err error) {
return fmt.Errorf("reading country geoip: %w", err)
}
- var asnErr, ctryErr error
-
- go func() {
- defer wg.Done()
-
- var ipv4, ipv6 asnSubnets
- ipv4, ipv6, asnErr = f.resetTopASNSubnets(asn)
-
- if asnErr != nil {
- metrics.GeoIPUpdateStatus.WithLabelValues(f.asnPath).Set(0)
-
- asnErr = fmt.Errorf("resetting geoip: top asn subnet data: %w", asnErr)
- }
-
- f.mu.Lock()
- defer f.mu.Unlock()
-
- f.ipv4TopASNSubnets, f.ipv6TopASNSubnets = ipv4, ipv6
- }()
-
- go func() {
- defer wg.Done()
-
- var ipv4, ipv6 countrySubnets
- ipv4, ipv6, ctryErr = resetCountrySubnets(country)
-
- if ctryErr != nil {
- metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0)
-
- ctryErr = fmt.Errorf("resetting geoip: country subnet data: %w", ctryErr)
- }
-
- f.mu.Lock()
- defer f.mu.Unlock()
-
- f.ipv4CountrySubnets, f.ipv6CountrySubnets = ipv4, ipv6
- }()
-
- wg.Wait()
-
- if asnErr != nil {
- return asnErr
+ err = f.resetSubnetMappings(asn, country)
+ if err != nil {
+ return fmt.Errorf("resetting geoip: %w", err)
}
- if ctryErr != nil {
- return ctryErr
- }
-
- f.mu.Lock()
- defer f.mu.Unlock()
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()
+
f.asn, f.country = asn, country
hostCacheBuilder := gcache.New(f.hostCacheSize)
@@ -424,6 +403,54 @@ func (f *File) Refresh(_ context.Context) (err error) {
return nil
}
+// resetSubnetMappings refreshes mapping from GeoIP data.
+func (f *File) resetSubnetMappings(asn, country *maxminddb.Reader) (err error) {
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ var locErr, ctryErr error
+
+ go func() {
+ defer wg.Done()
+
+ var ipv4, ipv6 locationSubnets
+ ipv4, ipv6, locErr = f.resetLocationSubnets(asn, country)
+
+ if locErr != nil {
+ metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0)
+
+ locErr = fmt.Errorf("location subnet data: %w", locErr)
+ }
+
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ f.ipv4LocationSubnets, f.ipv6LocationSubnets = ipv4, ipv6
+ }()
+
+ go func() {
+ defer wg.Done()
+
+ var ipv4, ipv6 countrySubnets
+ ipv4, ipv6, ctryErr = resetCountrySubnets(country)
+
+ if ctryErr != nil {
+ metrics.GeoIPUpdateStatus.WithLabelValues(f.countryPath).Set(0)
+
+ ctryErr = fmt.Errorf("country subnet data: %w", ctryErr)
+ }
+
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ f.ipv4CountrySubnets, f.ipv6CountrySubnets = ipv4, ipv6
+ }()
+
+ wg.Wait()
+
+ return errors.Annotate(errors.Join(ctryErr, locErr), "refreshing: %w")
+}
+
// geoIPFromFile reads the entire content of the file at fn and returns an
// initialized and checked reader.
func geoIPFromFile(fn string) (r *maxminddb.Reader, err error) {
diff --git a/internal/geoip/file_test.go b/internal/geoip/file_test.go
index fe0c677..9c6d7d7 100644
--- a/internal/geoip/file_test.go
+++ b/internal/geoip/file_test.go
@@ -4,13 +4,16 @@ import (
"net/netip"
"testing"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/netutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+// type check
+var _ agdservice.Refresher = (*geoip.File)(nil)
+
func TestFile_Data_cityDB(t *testing.T) {
conf := &geoip.FileConfig{
ASNPath: asnPath,
@@ -107,37 +110,43 @@ func TestFile_SubnetByLocation(t *testing.T) {
testCases := []struct {
name string
- country agd.Country
+ subdiv string
+ country geoip.Country
want netip.Prefix
- asn agd.ASN
+ asn geoip.ASN
fam netutil.AddrFamily
}{{
name: "by_asn",
country: testIPv4SubnetCtry,
+ subdiv: "",
asn: countryTopASNs[testIPv4SubnetCtry],
fam: netutil.AddrFamilyIPv4,
want: testIPv4CountrySubnet,
}, {
name: "from_top_countries_v4",
country: testIPv4SubnetCtry,
+ subdiv: "",
asn: 0,
fam: netutil.AddrFamilyIPv4,
want: testIPv4CountrySubnet,
}, {
name: "from_top_countries_v6",
country: testIPv6SubnetCtry,
+ subdiv: "",
asn: 0,
fam: netutil.AddrFamilyIPv6,
want: testIPv6CountrySubnet,
}, {
name: "from_countries_dict",
- country: agd.CountryBT,
+ country: geoip.CountryBT,
+ subdiv: "",
asn: 0,
fam: netutil.AddrFamilyIPv4,
want: netip.MustParsePrefix("67.43.156.0/24"),
}, {
name: "not_found",
- country: agd.CountryFR,
+ country: geoip.CountryFR,
+ subdiv: "",
asn: 0,
fam: netutil.AddrFamilyIPv4,
want: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
@@ -145,7 +154,12 @@ func TestFile_SubnetByLocation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- ctrySubnet, err := g.SubnetByLocation(tc.country, tc.asn, tc.fam)
+ ctrySubnet, err := g.SubnetByLocation(&geoip.Location{
+ Country: tc.country,
+ Continent: "",
+ TopSubdivision: tc.subdiv,
+ ASN: tc.asn,
+ }, tc.fam)
require.NoError(t, err)
assert.Equal(t, tc.want, ctrySubnet)
@@ -153,9 +167,12 @@ func TestFile_SubnetByLocation(t *testing.T) {
}
}
-var locSink *agd.Location
-
-var errSink error
+// Sinks for benchmarks.
+var (
+ errSink error
+ fileSink *geoip.File
+ locSink *geoip.Location
+)
func BenchmarkFile_Data(b *testing.B) {
conf := &geoip.FileConfig{
@@ -208,8 +225,6 @@ func BenchmarkFile_Data(b *testing.B) {
})
}
-var fileSink *geoip.File
-
func BenchmarkNewFile(b *testing.B) {
conf := &geoip.FileConfig{
ASNPath: asnPath,
diff --git a/internal/geoip/filescanner.go b/internal/geoip/filescanner.go
index 219837a..53ca035 100644
--- a/internal/geoip/filescanner.go
+++ b/internal/geoip/filescanner.go
@@ -4,7 +4,6 @@ import (
"fmt"
"net/netip"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/oschwald/maxminddb-golang"
@@ -36,71 +35,6 @@ const (
desiredIPv6SubnetLength = 56
)
-// resetTopASNSubnets resets the IPv4 and IPv6 top ASN subnet maps. For each
-// ASN in the set of top ASNs, as defined by allTopASNs, the subnet of a desired
-// length is chosen (see desiredIPv4SubnetLength and desiredIPv6SubnetLength).
-//
-// If an ASN only has a subnet that is broader than the desired length, that
-// subnet is replaced with one of the desired length with the newly-significant
-// bits set to zero.
-//
-// TODO(a.garipov): Consider merging with resetCountrySubnets.
-func (f *File) resetTopASNSubnets(r *maxminddb.Reader) (ipv4, ipv6 asnSubnets, err error) {
- ipv4, ipv6 = asnSubnets{}, asnSubnets{}
-
- nets := r.Networks(maxminddb.SkipAliasedNetworks)
- for nets.Next() {
- var asn agd.ASN
- var subnet netip.Prefix
- asn, subnet, err = subnetASNData(nets)
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return nil, nil, err
- } else if _, ok := f.allTopASNs[asn]; !ok {
- continue
- }
-
- if subnet.Addr().Is4() {
- replaceSubnet(ipv4, asn, subnet, desiredIPv4SubnetLength)
- } else {
- replaceSubnet(ipv6, asn, subnet, desiredIPv6SubnetLength)
- }
-
- }
-
- err = nets.Err()
- if err != nil {
- return nil, nil, fmt.Errorf("reading: %w", err)
- }
-
- applyTopASNSubnetHacks(ipv4, netutil.AddrFamilyIPv4)
- applyTopASNSubnetHacks(ipv6, netutil.AddrFamilyIPv6)
-
- log.Debug("geoip: got ipv4 top asn subnets %v", ipv4)
- log.Debug("geoip: got ipv6 top asn subnets %v", ipv6)
-
- return ipv4, ipv6, nil
-}
-
-// subnetASNData returns the ASN and subnet of the network at which nets
-// currently points.
-func subnetASNData(nets *maxminddb.Networks) (asn agd.ASN, subnet netip.Prefix, err error) {
- var res asnResult
- n, err := nets.Network(&res)
- if err != nil {
- return 0, netip.Prefix{}, fmt.Errorf("getting subnet and asn: %w", err)
- }
-
- // Assume that there are no actual IPv6-mapped IPv4 addresses in the GeoIP
- // database.
- subnet, err = netutil.IPNetToPrefixNoMapped(n)
- if err != nil {
- return 0, netip.Prefix{}, fmt.Errorf("converting subnet: %w", err)
- }
-
- return agd.ASN(res.ASN), subnet, nil
-}
-
// replaceSubnet adds subnet to subnets, possibly replacing the previous one,
// depending on presence and characteristics of the subnet already present in
// subnets for the given key.
@@ -140,34 +74,47 @@ func dist(a, b int) (d int) {
return d
}
-// applyTopASNSubnetHacks modifies the data in subnets based on the previous
-// experience and user reports. It also make sure that all items in subnets
-// have the desired length for their protocol. subnets must not be nil. fam
-// must be either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
-func applyTopASNSubnetHacks(subnets asnSubnets, fam netutil.AddrFamily) {
- var desiredLength int
- switch fam {
- case netutil.AddrFamilyIPv4:
- // We've got complaints from Moscow Megafon users that they cannot use
- // the YouTube app on Android and iOS when we use a different subnet.
- // It appears that the IPs for domain "youtubei.googleapis.com" are
- // indeed not available in their network unless this network is used in
- // the ECS option.
- subnets[25159] = netip.MustParsePrefix("178.176.72.0/24")
- desiredLength = desiredIPv4SubnetLength
- case netutil.AddrFamilyIPv6:
- // TODO(a.garipov): Add more if we find them.
+// resetLocationSubnets resets the IPv4 and IPv6 location subnet maps. For each
+// country with its subdivision, the subnet of a desired length is chosen (see
+// desiredIPv4SubnetLength and desiredIPv6SubnetLength).
+//
+// TODO(a.garipov): Consider merging with resetCountrySubnets.
+func (f *File) resetLocationSubnets(asn, country *maxminddb.Reader) (ipv4, ipv6 locationSubnets, err error) {
+ ipv4, ipv6 = locationSubnets{}, locationSubnets{}
- desiredLength = desiredIPv6SubnetLength
- default:
- panic(fmt.Errorf("geoip: unsupported addr fam %s", fam))
- }
+ nets := asn.Networks(maxminddb.SkipAliasedNetworks)
+ for nets.Next() {
+ var key locationKey
+ var subnet netip.Prefix
+ key, subnet, err = subnetLocationData(nets, country)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, nil, err
+ }
- for asn, n := range subnets {
- if n.Bits() < desiredLength {
- subnets[asn] = netip.PrefixFrom(n.Addr(), desiredLength)
+ if _, ok := f.allTopASNs[key.asn]; !ok {
+ continue
+ }
+
+ if subnet.Addr().Is4() {
+ replaceSubnet(ipv4, key, subnet, desiredIPv4SubnetLength)
+ } else {
+ replaceSubnet(ipv6, key, subnet, desiredIPv6SubnetLength)
}
}
+
+ err = nets.Err()
+ if err != nil {
+ return nil, nil, fmt.Errorf("reading: %w", err)
+ }
+
+ applyLocationSubnetHacks(ipv4, netutil.AddrFamilyIPv4)
+ applyLocationSubnetHacks(ipv6, netutil.AddrFamilyIPv6)
+
+ log.Debug("geoip: got ipv4 location subnets %v", ipv4)
+ log.Debug("geoip: got ipv6 location subnets %v", ipv6)
+
+ return ipv4, ipv6, nil
}
// resetCountrySubnets resets the IPv4 and IPv6 country subnet maps. For each
@@ -184,13 +131,13 @@ func resetCountrySubnets(r *maxminddb.Reader) (ipv4, ipv6 countrySubnets, err er
nets := r.Networks(maxminddb.SkipAliasedNetworks)
for nets.Next() {
- var c agd.Country
+ var c Country
var subnet netip.Prefix
c, subnet, err = subnetCountryData(nets)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, nil, err
- } else if c == agd.CountryNone {
+ } else if c == CountryNone {
continue
}
@@ -241,31 +188,102 @@ func applyCountrySubnetHacks(subnets countrySubnets, fam netutil.AddrFamily) {
}
}
+// applyLocationSubnetHacks modifies the data in subnets based on the previous
+// experience and user reports. It also make sure that all items in subnets
+// have the desired length for their protocol. subnets must not be nil. fam
+// must be either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
+func applyLocationSubnetHacks(subnets locationSubnets, fam netutil.AddrFamily) {
+ var desiredLength int
+ switch fam {
+ case netutil.AddrFamilyIPv4:
+ // TODO(a.garipov): Add more if we find them.
+
+ // We've got complaints from Moscow Megafon users that they cannot use
+ // the YouTube app on Android and iOS when we use a different subnet.
+ // It appears that the IPs for domain "youtubei.googleapis.com" are
+ // indeed not available in their network unless this network is used in
+ // the ECS option.
+ subnets[newLocationKey(25159, CountryNone, "")] = netip.MustParsePrefix("178.176.72.0/24")
+
+ desiredLength = desiredIPv4SubnetLength
+ case netutil.AddrFamilyIPv6:
+ // TODO(a.garipov): Add more if we find them.
+
+ desiredLength = desiredIPv6SubnetLength
+ default:
+ panic(fmt.Errorf("geoip: unsupported addr fam %s", fam))
+ }
+
+ for c, n := range subnets {
+ if n.Bits() < desiredLength {
+ subnets[c] = netip.PrefixFrom(n.Addr(), desiredLength)
+ }
+ }
+}
+
// subnetCountryData returns the country and subnet of the network at which nets
// currently points.
-func subnetCountryData(nets *maxminddb.Networks) (c agd.Country, subnet netip.Prefix, err error) {
+func subnetCountryData(nets *maxminddb.Networks) (c Country, subnet netip.Prefix, err error) {
var res countryResult
n, err := nets.Network(&res)
if err != nil {
- return agd.CountryNone, netip.Prefix{}, fmt.Errorf("getting subnet and country: %w", err)
+ return CountryNone, netip.Prefix{}, fmt.Errorf("getting subnet and country: %w", err)
}
// Assume that there are no actual IPv6-mapped IPv4 addresses in the GeoIP
// database.
subnet, err = netutil.IPNetToPrefixNoMapped(n)
if err != nil {
- return agd.CountryNone, netip.Prefix{}, fmt.Errorf("converting subnet: %w", err)
+ return CountryNone, netip.Prefix{}, fmt.Errorf("converting subnet: %w", err)
}
ctryStr := res.Country.ISOCode
if ctryStr == "" {
- return agd.CountryNone, netip.Prefix{}, nil
+ return CountryNone, netip.Prefix{}, nil
}
- c, err = agd.NewCountry(ctryStr)
+ c, err = NewCountry(ctryStr)
if err != nil {
- return agd.CountryNone, netip.Prefix{}, fmt.Errorf("converting country: %w", err)
+ return CountryNone, netip.Prefix{}, fmt.Errorf("converting country: %w", err)
}
return c, subnet, nil
}
+
+// subnetLocationData returns the location key and subnet of the network at
+// which nets currently points.
+func subnetLocationData(
+ nets *maxminddb.Networks,
+ countryReader *maxminddb.Reader,
+) (l locationKey, subnet netip.Prefix, err error) {
+ var res asnResult
+ n, err := nets.Network(&res)
+ if err != nil {
+ return l, netip.Prefix{}, fmt.Errorf("getting subnet and location: %w", err)
+ }
+
+ // Assume that there are no actual IPv6-mapped IPv4 addresses in the GeoIP
+ // database.
+ subnet, err = netutil.IPNetToPrefixNoMapped(n)
+ if err != nil {
+ return l, netip.Prefix{}, fmt.Errorf("converting subnet: %w", err)
+ }
+
+ var ctryRes countryResult
+ err = countryReader.Lookup(n.IP, &ctryRes)
+ if err != nil {
+ return l, netip.Prefix{}, fmt.Errorf("looking up country: %w", err)
+ }
+
+ ctry, err := NewCountry(ctryRes.Country.ISOCode)
+ if err != nil {
+ return l, netip.Prefix{}, fmt.Errorf("converting country: %w", err)
+ }
+
+ var subdiv string
+ if len(ctryRes.Subdivisions) > 0 {
+ subdiv = ctryRes.Subdivisions[0].ISOCode
+ }
+
+ return newLocationKey(ASN(res.ASN), ctry, subdiv), subnet, nil
+}
diff --git a/internal/geoip/geoip.go b/internal/geoip/geoip.go
index 764d04f..5c76d34 100644
--- a/internal/geoip/geoip.go
+++ b/internal/geoip/geoip.go
@@ -4,19 +4,18 @@ package geoip
import (
"net/netip"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/netutil"
)
// Interface is the interface for the GeoIP database that stores the geographic
// data about an IP address.
type Interface interface {
- // SubnetByLocation returns the default subnet for country c and ASN a, if
- // there is one. If there isn't, n is an unspecified subnet. fam must be
- // either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
- SubnetByLocation(c agd.Country, a agd.ASN, fam netutil.AddrFamily) (n netip.Prefix, err error)
+ // SubnetByLocation returns the default subnet for location, if there is
+ // one. If there isn't, n is an unspecified subnet. fam must be either
+ // [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
+ SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Prefix, err error)
// Data returns the GeoIP data for ip. It may use host to get cached GeoIP
// data if ip is netip.Addr{}.
- Data(host string, ip netip.Addr) (l *agd.Location, err error)
+ Data(host string, ip netip.Addr) (l *Location, err error)
}
diff --git a/internal/geoip/geoip_test.go b/internal/geoip/geoip_test.go
index 88740b4..09aab3c 100644
--- a/internal/geoip/geoip_test.go
+++ b/internal/geoip/geoip_test.go
@@ -4,7 +4,7 @@ import (
"net/netip"
"testing"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/testutil"
)
@@ -14,7 +14,7 @@ func TestMain(m *testing.M) {
// Paths to test data.
const (
- asnPath = "./testdata/GeoLite2-ASN-Test.mmdb"
+ asnPath = "./testdata/GeoIP2-ISP-Test.mmdb"
cityPath = "./testdata/GeoIP2-City-Test.mmdb"
countryPath = "./testdata/GeoIP2-Country-Test.mmdb"
)
@@ -27,31 +27,31 @@ const (
// Test ASN data.
var (
- countryTopASNs = map[agd.Country]agd.ASN{
- agd.CountryAU: 1221,
- agd.CountryJP: 2516,
- agd.CountryUS: 7922,
+ countryTopASNs = map[geoip.Country]geoip.ASN{
+ geoip.CountryAU: 1221,
+ geoip.CountryJP: 2516,
+ geoip.CountryUS: 7922,
}
- allTopASNs = map[agd.ASN]struct{}{
- countryTopASNs[agd.CountryAU]: {},
- countryTopASNs[agd.CountryJP]: {},
- countryTopASNs[agd.CountryUS]: {},
+ allTopASNs = map[geoip.ASN]struct{}{
+ countryTopASNs[geoip.CountryAU]: {},
+ countryTopASNs[geoip.CountryJP]: {},
+ countryTopASNs[geoip.CountryUS]: {},
}
)
// Test queries data. See [ASN], [city], and [country] testing datum.
//
-// [ASN]: https://github.com/maxmind/MaxMind-DB/blob/2bf1713b3b5adcb022cf4bb77eb0689beaadcfef/source-data/GeoLite2-ASN-Test.json
-// [city]: https://github.com/maxmind/MaxMind-DB/blob/2bf1713b3b5adcb022cf4bb77eb0689beaadcfef/source-data/GeoIP2-City-Test.json
-// [country]: https://github.com/maxmind/MaxMind-DB/blob/2bf1713b3b5adcb022cf4bb77eb0689beaadcfef/source-data/GeoIP2-Country-Test.json
+// [ASN]: https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/source-data/GeoIP2-ISP-Test.json
+// [city]: https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/source-data/GeoIP2-City-Test.json
+// [country]: https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/source-data/GeoIP2-Country-Test.json
const (
- testASN agd.ASN = 1221
- testCtry agd.Country = agd.CountryUS
- testCont agd.Continent = agd.ContinentNA
- testSubdiv string = "WA"
+ testASN geoip.ASN = 1221
+ testCtry = geoip.CountryUS
+ testCont = geoip.ContinentNA
+ testSubdiv string = "WA"
- testIPv4SubnetCtry = agd.CountryUS
- testIPv6SubnetCtry = agd.CountryJP
+ testIPv4SubnetCtry = geoip.CountryUS
+ testIPv6SubnetCtry = geoip.CountryJP
)
// testIPWithASN has ASN set to 1221 in the test database.
diff --git a/internal/agd/location.go b/internal/geoip/location.go
similarity index 99%
rename from internal/agd/location.go
rename to internal/geoip/location.go
index a41780b..d2cf905 100644
--- a/internal/agd/location.go
+++ b/internal/geoip/location.go
@@ -1,4 +1,4 @@
-package agd
+package geoip
// Location Types And Constants
diff --git a/internal/geoip/testdata/GeoIP2-City-Test.mmdb b/internal/geoip/testdata/GeoIP2-City-Test.mmdb
index 43ab5ed..0a166a0 100644
Binary files a/internal/geoip/testdata/GeoIP2-City-Test.mmdb and b/internal/geoip/testdata/GeoIP2-City-Test.mmdb differ
diff --git a/internal/geoip/testdata/GeoIP2-Country-Test.mmdb b/internal/geoip/testdata/GeoIP2-Country-Test.mmdb
index 9d61099..30d179f 100644
Binary files a/internal/geoip/testdata/GeoIP2-Country-Test.mmdb and b/internal/geoip/testdata/GeoIP2-Country-Test.mmdb differ
diff --git a/internal/geoip/testdata/GeoIP2-ISP-Test.mmdb b/internal/geoip/testdata/GeoIP2-ISP-Test.mmdb
new file mode 100644
index 0000000..0ad2dce
Binary files /dev/null and b/internal/geoip/testdata/GeoIP2-ISP-Test.mmdb differ
diff --git a/internal/geoip/testdata/GeoLite2-ASN-Test.mmdb b/internal/geoip/testdata/GeoLite2-ASN-Test.mmdb
deleted file mode 100644
index fd4a733..0000000
Binary files a/internal/geoip/testdata/GeoLite2-ASN-Test.mmdb and /dev/null differ
diff --git a/internal/metrics/access.go b/internal/metrics/access.go
index d3438e0..73e3df2 100644
--- a/internal/metrics/access.go
+++ b/internal/metrics/access.go
@@ -5,10 +5,8 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
)
-// AccessBlockedForSubnetTotal is a counter with the total count of request
-// blocked for client's subnet by access manager.
-//
-// TODO(d.kolyshev): Consider adding rule label.
+// AccessBlockedForSubnetTotal is a counter with the total count of requests
+// blocked for client's subnet by global access manager.
var AccessBlockedForSubnetTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "blocked_subnet_total",
Namespace: namespace,
@@ -16,11 +14,29 @@ var AccessBlockedForSubnetTotal = promauto.NewCounter(prometheus.CounterOpts{
Help: "Total count of blocked subnet requests.",
})
-// AccessBlockedForHostTotal is a counter with the total count of request
-// blocked for request's host by access manager.
+// AccessBlockedForHostTotal is a counter with the total count of requests
+// blocked for request's host by global access manager.
var AccessBlockedForHostTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "blocked_host_total",
Namespace: namespace,
Subsystem: subsystemAccess,
Help: "Total count of blocked host requests.",
})
+
+// AccessBlockedForProfileTotal is a counter with the total count of requests
+// blocked for all profiles by access manager.
+var AccessBlockedForProfileTotal = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "profile_blocked_total",
+ Namespace: namespace,
+ Subsystem: subsystemAccess,
+ Help: "Total count of blocked profile requests.",
+})
+
+// AccessProfileInitDuration is a histogram with the duration of a profile
+// access internal engine initialization.
+var AccessProfileInitDuration = promauto.NewHistogram(prometheus.HistogramOpts{
+ Name: "profile_init_engine_duration_seconds",
+ Namespace: namespace,
+ Subsystem: subsystemAccess,
+ Help: "Time elapsed on profile access engine initialization.",
+})
diff --git a/internal/metrics/backend.go b/internal/metrics/backend.go
index 90a3e73..2d70f8a 100644
--- a/internal/metrics/backend.go
+++ b/internal/metrics/backend.go
@@ -98,5 +98,28 @@ var GRPCAvgProfileDecDuration = promauto.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Help: "The average duration of decoding one profile during a call to the backend, " +
"in seconds.",
- Buckets: []float64{0.000_001, 0.000_010, 0.000_100, 0.001},
+ Buckets: []float64{0.000_001, 0.000_01, 0.000_1, 0.001},
})
+
+var (
+ // profilesSyncTimeouts is a gauge with the total number of timeout errors
+ // occurred during profiles sync, either full or partial.
+ profilesSyncTimeouts = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "profiles_sync_timeouts_total",
+ Namespace: namespace,
+ Subsystem: subsystemBackend,
+ Help: "The total number of timeout errors during profiles sync.",
+ }, []string{"is_full_sync"})
+
+ // ProfilesSyncFullTimeouts is a gauge with the total number of timeout
+ // errors occurred during full profiles sync.
+ ProfilesSyncFullTimeouts = profilesSyncTimeouts.With(prometheus.Labels{
+ "is_full_sync": "1",
+ })
+
+ // ProfilesSyncPartTimeouts is a gauge with the total number of timeout
+ // errors occurred during partial profiles sync.
+ ProfilesSyncPartTimeouts = profilesSyncTimeouts.With(prometheus.Labels{
+ "is_full_sync": "0",
+ })
+)
diff --git a/internal/metrics/dnscheck.go b/internal/metrics/dnscheck.go
index 90fcab8..ddd2938 100644
--- a/internal/metrics/dnscheck.go
+++ b/internal/metrics/dnscheck.go
@@ -5,11 +5,21 @@ import (
"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".
+// 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"})
+
+// 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"})
diff --git a/internal/metrics/dnsmsg.go b/internal/metrics/dnsmsg.go
new file mode 100644
index 0000000..f4a3020
--- /dev/null
+++ b/internal/metrics/dnsmsg.go
@@ -0,0 +1,45 @@
+package metrics
+
+import (
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+// Metrics related to DNS-message handling.
+var (
+ // fullClones is a counter with the total number of cloned messages using
+ // our custom cloner. "full" is either "1" (cloned entirely using the
+ // cloner) or "0" (cloned using miekg/dns.Copy).
+ fullClones = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "total_full_clones",
+ Subsystem: subsystemDNSMsg,
+ Namespace: namespace,
+ Help: "Total number of (not) full clones using the cloner. " +
+ "full=1 means that a message was cloned fully using the cloner.",
+ }, []string{"full"})
+
+ // dnsMsgFullClones is a counter with the total number of ECS cache full
+ // clones.
+ dnsMsgFullClones = fullClones.With(prometheus.Labels{
+ "full": "1",
+ })
+
+ // dnsMsgPartialClones is a counter with the total number of ECS cache
+ // partial clones.
+ dnsMsgPartialClones = fullClones.With(prometheus.Labels{
+ "full": "0",
+ })
+)
+
+// ClonerStat is the Prometheus-based implementation of the [dnsmsg.ClonerStat]
+// interface.
+type ClonerStat struct{}
+
+// type check
+var _ dnsmsg.ClonerStat = ClonerStat{}
+
+// OnClone implements the [dnsmsg.ClonerStat] interface for ClonerStat.
+func (ClonerStat) OnClone(isFull bool) {
+ IncrementCond(isFull, dnsMsgFullClones, dnsMsgPartialClones)
+}
diff --git a/internal/metrics/dnssvc.go b/internal/metrics/dnssvc.go
index 1acf3c7..6afecd6 100644
--- a/internal/metrics/dnssvc.go
+++ b/internal/metrics/dnssvc.go
@@ -96,3 +96,12 @@ var DNSSvcFilteringDuration = promauto.NewHistogram(prometheus.HistogramOpts{
1,
},
})
+
+// DNSSvcUnknownDedicatedTotal is the counter of queries that have been dropped,
+// because the local-address data was not recognized.
+var DNSSvcUnknownDedicatedTotal = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "unknown_dedicated",
+ Namespace: namespace,
+ Subsystem: subsystemDNSSvc,
+ Help: "The number of dropped queries for unrecognized dedicated addresses.",
+})
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
index e49a00a..9e86d28 100644
--- a/internal/metrics/metrics.go
+++ b/internal/metrics/metrics.go
@@ -23,14 +23,15 @@ const (
subsystemConsul = "consul"
subsystemDNSCheck = "dnscheck"
subsystemDNSDB = "dnsdb"
+ subsystemDNSMsg = "dnsmsg"
subsystemDNSSvc = "dnssvc"
subsystemECSCache = "ecscache"
subsystemFilter = "filter"
subsystemGeoIP = "geoip"
subsystemQueryLog = "querylog"
+ subsystemResearch = "research"
subsystemRuleStat = "rulestat"
subsystemTLS = "tls"
- subsystemResearch = "research"
subsystemWebSvc = "websvc"
)
@@ -76,6 +77,16 @@ func BoolString(cond bool) (s string) {
return "0"
}
+// IncrementCond increments trueCounter if cond is true and falseCounter
+// otherwise.
+func IncrementCond(cond bool, trueCounter, falseCounter prometheus.Counter) {
+ if cond {
+ trueCounter.Inc()
+ } else {
+ falseCounter.Inc()
+ }
+}
+
// SetAdditionalInfo adds a gauge with extra info labels. If info is nil,
// SetAdditionalInfo does nothing.
func SetAdditionalInfo(info map[string]string) {
diff --git a/internal/metrics/research.go b/internal/metrics/research.go
index 9c83949..3697216 100644
--- a/internal/metrics/research.go
+++ b/internal/metrics/research.go
@@ -1,7 +1,6 @@
package metrics
import (
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
@@ -55,70 +54,69 @@ var ResearchResponseECH = promauto.NewCounter(prometheus.CounterOpts{
Help: `The number of DNS responses with a ECH config.`,
})
+// ResearchData contains data for research metrics.
+type ResearchData struct {
+ OriginalResponse *dns.Msg
+ FilterID string
+ Country string
+ TopSubdivision string
+ Host string
+ QType uint16
+ Blocked bool
+}
+
// ReportResearch reports metrics to prometheus that we may need to conduct
// researches. If researchLogs is true, this method may also write additional
// INFO-level logs.
-func ReportResearch(
- ri *agd.RequestInfo,
- origResp *dns.Msg,
- filterID agd.FilterListID,
- blocked bool,
- researchLogs bool,
-) {
- filteringEnabled := ri.FilteringGroup != nil &&
- ri.FilteringGroup.RuleListsEnabled &&
- len(ri.FilteringGroup.RuleListIDs) > 0
+func ReportResearch(data *ResearchData, researchLogs bool) {
+ ctry := data.Country
- // The current research metrics only count queries that come to public DNS
- // servers where filtering is enabled.
- if !filteringEnabled || ri.Profile != nil {
- return
+ var subdiv string
+ if model.LabelValue(data.TopSubdivision).IsValid() {
+ subdiv = data.TopSubdivision
}
- var ctry, subdiv string
- if l := ri.Location; l != nil {
- // Ignore AdGuard ASN specifically in order to avoid counting queries
- // that come from the monitoring. This part is ugly, but since these
- // metrics are a one-time deal, this is acceptable.
- //
- // TODO(ameshkov): Think of a better way later if we need to do that
- // again.
- if l.ASN == 212772 {
- return
- }
-
- ctry = string(l.Country)
- if model.LabelValue(l.TopSubdivision).IsValid() {
- subdiv = l.TopSubdivision
- }
- }
-
- if blocked {
- reportResearchBlocked(string(filterID), ctry, subdiv)
+ if data.Blocked {
+ reportResearchBlocked(data.FilterID, ctry, subdiv)
}
reportResearchRequest(ctry, subdiv)
- reportResearchECH(ri, origResp, researchLogs)
+
+ if data.QType == dns.TypeHTTPS {
+ reportResearchECH(data.Host, data.OriginalResponse, researchLogs)
+ }
}
// reportResearchECH checks if the response has ECH config and if it does,
// reports to metrics and writes to log.
-func reportResearchECH(ri *agd.RequestInfo, origResp *dns.Msg, researchLogs bool) {
- if origResp == nil || ri.QType != dns.TypeHTTPS {
+func reportResearchECH(host string, origResp *dns.Msg, researchLogs bool) {
+ if origResp == nil {
return
}
for _, rr := range origResp.Answer {
- if svcb, ok := rr.(*dns.HTTPS); ok {
- for _, v := range svcb.Value {
- if v.Key() == dns.SVCB_ECHCONFIG {
- ResearchResponseECH.Inc()
+ svcb, ok := rr.(*dns.HTTPS)
+ if !ok {
+ continue
+ }
- if researchLogs {
- log.Info("research: ech-enabled: %s", ri.Host)
- }
- }
- }
+ reportECHConfig(svcb.SVCB, researchLogs, host)
+ }
+}
+
+// reportECHConfig iterates over SVCB records, finds records with ECH
+// configuration, reports to metrics, and if researchLogs is enabled writes to
+// log.
+func reportECHConfig(svcb dns.SVCB, researchLogs bool, host string) {
+ for _, v := range svcb.Value {
+ if v.Key() != dns.SVCB_ECHCONFIG {
+ continue
+ }
+
+ ResearchResponseECH.Inc()
+
+ if researchLogs {
+ log.Info("research: ech-enabled: %q", host)
}
}
}
diff --git a/internal/metrics/tls.go b/internal/metrics/tls.go
index 26bc018..9134d09 100644
--- a/internal/metrics/tls.go
+++ b/internal/metrics/tls.go
@@ -209,14 +209,23 @@ func matchSrvCerts(sni string, srvCerts []tls.Certificate) (match string) {
continue
}
- for _, n := range leaf.DNSNames {
- if n == sni {
- return sni
- }
-
- if strings.HasPrefix(n, "*.") && netutil.IsImmediateSubdomain(sni, n[len("*."):]) {
- return n
- }
+ if match = matchSNI(sni, leaf.DNSNames); match != "" {
+ return match
+ }
+ }
+
+ return ""
+}
+
+// matchSNI finds match for sni in dnsNames.
+func matchSNI(sni string, dnsNames []string) (match string) {
+ for _, n := range dnsNames {
+ if n == sni {
+ return sni
+ }
+
+ if strings.HasPrefix(n, "*.") && netutil.IsImmediateSubdomain(sni, n[len("*."):]) {
+ return n
}
}
diff --git a/internal/metrics/tls_test.go b/internal/metrics/tls_test.go
index 9fd28f7..d366cc1 100644
--- a/internal/metrics/tls_test.go
+++ b/internal/metrics/tls_test.go
@@ -114,28 +114,32 @@ func assertLabelValue(
) (ok bool) {
t.Helper()
-outerLoop:
for _, family := range metricFamilies {
if family.GetName() != "dns_tls_handshake_total" {
continue
}
- for _, m := range family.GetMetric() {
- for _, p := range m.GetLabel() {
- if p.GetName() != "server_name" || wantLabel != p.GetValue() {
- continue
- }
-
- ok = true
-
- break outerLoop
- }
+ if ok = findLabel(family.GetMetric(), wantLabel); ok {
+ break
}
}
return assert.Truef(t, ok, "%s not found in server name labels", wantLabel)
}
+// findLabel is a helper function to find label in metrics.
+func findLabel(ms []*io_prometheus_client.Metric, label string) (ok bool) {
+ for _, m := range ms {
+ for _, p := range m.GetLabel() {
+ if p.GetName() == "server_name" && label == p.GetValue() {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
func TestTLSMetricsBeforeHandshake(t *testing.T) {
f := metrics.TLSMetricsBeforeHandshake("srv-name")
diff --git a/internal/profiledb/internal/filecachepb/filecache.pb.go b/internal/profiledb/internal/filecachepb/filecache.pb.go
index 72521ee..45097d3 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.31.0
-// protoc v4.23.4
+// protoc v4.25.1
// source: filecache.proto
package filecachepb
@@ -121,6 +121,8 @@ type Profile struct {
BlockPrivateRelay bool `protobuf:"varint,17,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"`
BlockFirefoxCanary bool `protobuf:"varint,18,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"`
SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,19,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"`
+ IpLogEnabled bool `protobuf:"varint,20,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"`
+ Access *AccessSettings `protobuf:"bytes,21,opt,name=access,proto3" json:"access,omitempty"`
}
func (x *Profile) Reset() {
@@ -296,6 +298,20 @@ func (x *Profile) GetSafeBrowsing() *SafeBrowsingSettings {
return nil
}
+func (x *Profile) GetIpLogEnabled() bool {
+ if x != nil {
+ return x.IpLogEnabled
+ }
+ return false
+}
+
+func (x *Profile) GetAccess() *AccessSettings {
+ if x != nil {
+ return x.Access
+ }
+ return nil
+}
+
type isProfile_BlockingMode interface {
isProfile_BlockingMode()
}
@@ -880,6 +896,140 @@ func (x *Device) GetFilteringEnabled() bool {
return false
}
+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"`
+}
+
+func (x *AccessSettings) Reset() {
+ *x = AccessSettings{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_filecache_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AccessSettings) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AccessSettings) ProtoMessage() {}
+
+func (x *AccessSettings) ProtoReflect() protoreflect.Message {
+ mi := &file_filecache_proto_msgTypes[11]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AccessSettings.ProtoReflect.Descriptor instead.
+func (*AccessSettings) Descriptor() ([]byte, []int) {
+ return file_filecache_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *AccessSettings) GetAllowlistCidr() []*CidrRange {
+ if x != nil {
+ return x.AllowlistCidr
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistCidr() []*CidrRange {
+ if x != nil {
+ return x.BlocklistCidr
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetAllowlistAsn() []uint32 {
+ if x != nil {
+ return x.AllowlistAsn
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistAsn() []uint32 {
+ if x != nil {
+ return x.BlocklistAsn
+ }
+ return nil
+}
+
+func (x *AccessSettings) GetBlocklistDomainRules() []string {
+ if x != nil {
+ return x.BlocklistDomainRules
+ }
+ return nil
+}
+
+type CidrRange struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ 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"`
+}
+
+func (x *CidrRange) Reset() {
+ *x = CidrRange{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_filecache_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CidrRange) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CidrRange) ProtoMessage() {}
+
+func (x *CidrRange) ProtoReflect() protoreflect.Message {
+ mi := &file_filecache_proto_msgTypes[12]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CidrRange.ProtoReflect.Descriptor instead.
+func (*CidrRange) Descriptor() ([]byte, []int) {
+ return file_filecache_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *CidrRange) GetAddress() []byte {
+ if x != nil {
+ return x.Address
+ }
+ return nil
+}
+
+func (x *CidrRange) GetPrefix() uint32 {
+ if x != nil {
+ return x.Prefix
+ }
+ return 0
+}
+
var File_filecache_proto protoreflect.FileDescriptor
var file_filecache_proto_rawDesc = []byte{
@@ -900,7 +1050,7 @@ var file_filecache_proto_rawDesc = []byte{
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64,
0x62, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01,
- 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xcc, 0x08, 0x0a, 0x07,
+ 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa5, 0x09, 0x0a, 0x07,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72,
@@ -968,83 +1118,109 @@ var file_filecache_proto_rawDesc = []byte{
0x73, 0x69, 0x6e, 0x67, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73,
0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0c, 0x73, 0x61, 0x66,
- 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f,
- 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa5, 0x02, 0x0a, 0x1a, 0x50,
- 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f,
- 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x73, 0x63, 0x68,
- 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72,
- 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c,
- 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75,
- 0x6c, 0x65, 0x52, 0x08, 0x73, 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, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x75, 0x6c, 0x74,
- 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x75,
- 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x61,
- 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
- 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72,
- 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x5f, 0x73, 0x61,
- 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
- 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72,
- 0x63, 0x68, 0x22, 0xad, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73,
- 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 0x36, 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64,
- 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e,
- 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a,
- 0x1e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x65, 0x77, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x67,
- 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18,
- 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x65, 0x77, 0x6c,
- 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69,
- 0x6e, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50,
- 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
- 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x25,
- 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72,
+ 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x70, 0x5f,
+ 0x6c, 0x6f, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28,
+ 0x08, 0x52, 0x0c, 0x69, 0x70, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12,
+ 0x31, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x41, 0x63, 0x63, 0x65,
+ 0x73, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65,
+ 0x73, 0x73, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d,
+ 0x6f, 0x64, 0x65, 0x22, 0xa5, 0x02, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c,
+ 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
+ 0x67, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62,
+ 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74,
+ 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 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, 0x1f, 0x0a, 0x0b, 0x62, 0x6c,
+ 0x6f, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67,
+ 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72,
+ 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
+ 0x6c, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79,
+ 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72,
+ 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62,
+ 0x65, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0xad, 0x01, 0x0a, 0x14,
+ 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74,
+ 0x69, 0x6e, 0x67, 0x73, 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, 0x36,
+ 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75,
+ 0x73, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
+ 0x15, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44,
+ 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
+ 0x6e, 0x65, 0x77, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64,
+ 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b,
+ 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x65, 0x77, 0x6c, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
+ 0x65, 0x72, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x1a,
+ 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69,
+ 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69,
+ 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
+ 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x25, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62,
+ 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x6d, 0x6f, 0x6e, 0x12, 0x25,
+ 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65,
- 0x52, 0x03, 0x6d, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01,
+ 0x52, 0x03, 0x74, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44,
- 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x03,
- 0x77, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66,
+ 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x77, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x03,
+ 0x74, 0x68, 0x75, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03,
- 0x77, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x03, 0x74, 0x68, 0x75, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
+ 0x74, 0x68, 0x75, 0x12, 0x25, 0x0a, 0x03, 0x66, 0x72, 0x69, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79,
- 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x25, 0x0a, 0x03, 0x66, 0x72,
- 0x69, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
- 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x66, 0x72,
- 0x69, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
+ 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x61,
+ 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
+ 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x61,
+ 0x74, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61,
- 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18,
- 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64,
- 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75, 0x6e, 0x22,
- 0x32, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 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, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12,
- 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 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, 0xb5, 0x01, 0x0a, 0x06, 0x44, 0x65, 0x76,
- 0x69, 0x63, 0x65, 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, 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, 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, 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,
- 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,
+ 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75, 0x6e, 0x22, 0x32, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52,
+ 0x61, 0x6e, 0x67, 0x65, 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, 0x01,
+ 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36,
+ 0x18, 0x02, 0x20, 0x01, 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, 0xb5, 0x01, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 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, 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, 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, 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, 0x8a, 0x02, 0x0a, 0x0e, 0x41, 0x63,
+ 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 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, 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, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6c,
+ 0x69, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x61,
+ 0x6c, 0x6c, 0x6f, 0x77, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x62,
+ 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x6e, 0x18, 0x04, 0x20, 0x03,
+ 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x6e,
+ 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, 0x05, 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, 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 (
@@ -1059,7 +1235,7 @@ func file_filecache_proto_rawDescGZIP() []byte {
return file_filecache_proto_rawDescData
}
-var file_filecache_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
+var file_filecache_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_filecache_proto_goTypes = []interface{}{
(*FileCache)(nil), // 0: profiledb.FileCache
(*Profile)(nil), // 1: profiledb.Profile
@@ -1072,11 +1248,13 @@ var file_filecache_proto_goTypes = []interface{}{
(*BlockingModeNullIP)(nil), // 8: profiledb.BlockingModeNullIP
(*BlockingModeREFUSED)(nil), // 9: profiledb.BlockingModeREFUSED
(*Device)(nil), // 10: profiledb.Device
- (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
- (*durationpb.Duration)(nil), // 12: google.protobuf.Duration
+ (*AccessSettings)(nil), // 11: profiledb.AccessSettings
+ (*CidrRange)(nil), // 12: profiledb.CidrRange
+ (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
+ (*durationpb.Duration)(nil), // 14: google.protobuf.Duration
}
var file_filecache_proto_depIdxs = []int32{
- 11, // 0: profiledb.FileCache.sync_time:type_name -> google.protobuf.Timestamp
+ 13, // 0: profiledb.FileCache.sync_time:type_name -> google.protobuf.Timestamp
1, // 1: profiledb.FileCache.profiles:type_name -> profiledb.Profile
10, // 2: profiledb.FileCache.devices:type_name -> profiledb.Device
2, // 3: profiledb.Profile.parental:type_name -> profiledb.ParentalProtectionSettings
@@ -1084,22 +1262,25 @@ var file_filecache_proto_depIdxs = []int32{
7, // 5: profiledb.Profile.blocking_mode_nxdomain:type_name -> profiledb.BlockingModeNXDOMAIN
8, // 6: profiledb.Profile.blocking_mode_null_ip:type_name -> profiledb.BlockingModeNullIP
9, // 7: profiledb.Profile.blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED
- 11, // 8: profiledb.Profile.update_time:type_name -> google.protobuf.Timestamp
- 12, // 9: profiledb.Profile.filtered_response_ttl:type_name -> google.protobuf.Duration
+ 13, // 8: profiledb.Profile.update_time:type_name -> google.protobuf.Timestamp
+ 14, // 9: profiledb.Profile.filtered_response_ttl:type_name -> google.protobuf.Duration
3, // 10: profiledb.Profile.safe_browsing:type_name -> profiledb.SafeBrowsingSettings
- 4, // 11: profiledb.ParentalProtectionSettings.schedule:type_name -> profiledb.ParentalProtectionSchedule
- 5, // 12: profiledb.ParentalProtectionSchedule.mon:type_name -> profiledb.DayRange
- 5, // 13: profiledb.ParentalProtectionSchedule.tue:type_name -> profiledb.DayRange
- 5, // 14: profiledb.ParentalProtectionSchedule.wed:type_name -> profiledb.DayRange
- 5, // 15: profiledb.ParentalProtectionSchedule.thu:type_name -> profiledb.DayRange
- 5, // 16: profiledb.ParentalProtectionSchedule.fri:type_name -> profiledb.DayRange
- 5, // 17: profiledb.ParentalProtectionSchedule.sat:type_name -> profiledb.DayRange
- 5, // 18: profiledb.ParentalProtectionSchedule.sun:type_name -> profiledb.DayRange
- 19, // [19:19] is the sub-list for method output_type
- 19, // [19:19] is the sub-list for method input_type
- 19, // [19:19] is the sub-list for extension type_name
- 19, // [19:19] is the sub-list for extension extendee
- 0, // [0:19] is the sub-list for field type_name
+ 11, // 11: profiledb.Profile.access:type_name -> profiledb.AccessSettings
+ 4, // 12: profiledb.ParentalProtectionSettings.schedule:type_name -> profiledb.ParentalProtectionSchedule
+ 5, // 13: profiledb.ParentalProtectionSchedule.mon:type_name -> profiledb.DayRange
+ 5, // 14: profiledb.ParentalProtectionSchedule.tue:type_name -> profiledb.DayRange
+ 5, // 15: profiledb.ParentalProtectionSchedule.wed:type_name -> profiledb.DayRange
+ 5, // 16: profiledb.ParentalProtectionSchedule.thu:type_name -> profiledb.DayRange
+ 5, // 17: profiledb.ParentalProtectionSchedule.fri:type_name -> profiledb.DayRange
+ 5, // 18: profiledb.ParentalProtectionSchedule.sat:type_name -> profiledb.DayRange
+ 5, // 19: profiledb.ParentalProtectionSchedule.sun:type_name -> profiledb.DayRange
+ 12, // 20: profiledb.AccessSettings.allowlist_cidr:type_name -> profiledb.CidrRange
+ 12, // 21: profiledb.AccessSettings.blocklist_cidr:type_name -> profiledb.CidrRange
+ 22, // [22:22] is the sub-list for method output_type
+ 22, // [22:22] is the sub-list for method input_type
+ 22, // [22:22] is the sub-list for extension type_name
+ 22, // [22:22] is the sub-list for extension extendee
+ 0, // [0:22] is the sub-list for field type_name
}
func init() { file_filecache_proto_init() }
@@ -1240,6 +1421,30 @@ func file_filecache_proto_init() {
return nil
}
}
+ file_filecache_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AccessSettings); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_filecache_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CidrRange); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
file_filecache_proto_msgTypes[1].OneofWrappers = []interface{}{
(*Profile_BlockingModeCustomIp)(nil),
@@ -1253,7 +1458,7 @@ func file_filecache_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_filecache_proto_rawDesc,
NumEnums: 0,
- NumMessages: 11,
+ NumMessages: 13,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/internal/profiledb/internal/filecachepb/filecache.proto b/internal/profiledb/internal/filecachepb/filecache.proto
index 06679e0..0fdac2a 100644
--- a/internal/profiledb/internal/filecachepb/filecache.proto
+++ b/internal/profiledb/internal/filecachepb/filecache.proto
@@ -36,6 +36,8 @@ message Profile {
bool block_private_relay = 17;
bool block_firefox_canary = 18;
SafeBrowsingSettings safe_browsing = 19;
+ bool ip_log_enabled = 20;
+ AccessSettings access = 21;
}
message ParentalProtectionSettings {
@@ -87,3 +89,16 @@ message Device {
repeated bytes dedicated_ips = 4;
bool filtering_enabled = 5;
}
+
+message AccessSettings {
+ repeated CidrRange allowlist_cidr = 1;
+ repeated CidrRange blocklist_cidr = 2;
+ repeated uint32 allowlist_asn = 3;
+ repeated uint32 blocklist_asn = 4;
+ repeated string blocklist_domain_rules = 5;
+}
+
+message CidrRange {
+ bytes address = 1;
+ uint32 prefix = 2;
+}
diff --git a/internal/profiledb/internal/filecachepb/filecachepb.go b/internal/profiledb/internal/filecachepb/filecachepb.go
index 2a49747..f24cf12 100644
--- a/internal/profiledb/internal/filecachepb/filecachepb.go
+++ b/internal/profiledb/internal/filecachepb/filecachepb.go
@@ -6,10 +6,12 @@ import (
"net/netip"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -89,11 +91,13 @@ func (x *Profile) toInternal() (prof *agd.Profile, err error) {
FilteredResponseTTL: x.FilteredResponseTtl.AsDuration(),
FilteringEnabled: x.FilteringEnabled,
SafeBrowsing: x.SafeBrowsing.toInternal(),
+ Access: x.Access.toInternal(),
RuleListsEnabled: x.RuleListsEnabled,
QueryLogEnabled: x.QueryLogEnabled,
Deleted: x.Deleted,
BlockPrivateRelay: x.BlockPrivateRelay,
BlockFirefoxCanary: x.BlockFirefoxCanary,
+ IPLogEnabled: x.IpLogEnabled,
}, nil
}
@@ -151,35 +155,31 @@ func (x *ParentalProtectionSchedule) toInternal() (s *agd.ParentalProtectionSche
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
// internal one.
-func blockingModeToInternal(
- pbBlockingMode isProfile_BlockingMode,
-) (m dnsmsg.BlockingModeCodec, err error) {
- switch pbm := pbBlockingMode.(type) {
+func blockingModeToInternal(pbm isProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
+ switch pbm := pbm.(type) {
case *Profile_BlockingModeCustomIp:
custom := &dnsmsg.BlockingModeCustomIP{}
err = custom.IPv4.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv4)
if err != nil {
- return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv4: %w", err)
+ return nil, fmt.Errorf("bad custom ipv4: %w", err)
}
err = custom.IPv6.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv6)
if err != nil {
- return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv6: %w", err)
+ return nil, fmt.Errorf("bad custom ipv6: %w", err)
}
- m.Mode = custom
+ return custom, nil
case *Profile_BlockingModeNxdomain:
- m.Mode = &dnsmsg.BlockingModeNXDOMAIN{}
+ return &dnsmsg.BlockingModeNXDOMAIN{}, nil
case *Profile_BlockingModeNullIp:
- m.Mode = &dnsmsg.BlockingModeNullIP{}
+ return &dnsmsg.BlockingModeNullIP{}, nil
case *Profile_BlockingModeRefused:
- m.Mode = &dnsmsg.BlockingModeREFUSED{}
+ return &dnsmsg.BlockingModeREFUSED{}, nil
default:
// Consider unhandled type-switch cases programmer errors.
- panic(fmt.Errorf("bad pb blocking mode %T(%[1]v)", m))
+ return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
}
-
- return m, nil
}
// devicesToInternal converts protobuf device structures into internal ones.
@@ -237,6 +237,48 @@ func (x *SafeBrowsingSettings) toInternal() (s *agd.SafeBrowsingSettings) {
}
}
+// toInternal converts protobuf access settings to an internal structure. If x
+// is nil, toInternal returns [access.EmptyProfile].
+func (x *AccessSettings) toInternal() (a access.Profile) {
+ if x == nil {
+ return access.EmptyProfile{}
+ }
+
+ return access.NewDefaultProfile(&access.ProfileConfig{
+ AllowedNets: cidrRangeToInternal(x.AllowlistCidr),
+ BlockedNets: cidrRangeToInternal(x.BlocklistCidr),
+ AllowedASN: asnToInternal(x.AllowlistAsn),
+ BlockedASN: asnToInternal(x.BlocklistAsn),
+ BlocklistDomainRules: x.BlocklistDomainRules,
+ })
+}
+
+// cidrRangeToInternal is a helper that converts a slice of CidrRange to the
+// slice of [netip.Prefix].
+func cidrRangeToInternal(cidrs []*CidrRange) (out []netip.Prefix) {
+ for _, c := range cidrs {
+ addr, ok := netip.AddrFromSlice(c.Address)
+ if !ok {
+ // Should never happen.
+ panic(fmt.Errorf("bad address: %v", c.Address))
+ }
+
+ out = append(out, netip.PrefixFrom(addr, int(c.Prefix)))
+ }
+
+ return out
+}
+
+// asnToInternal is a helper that converts a slice of ASNs to the slice of
+// [geoip.ASN].
+func asnToInternal(asns []uint32) (out []geoip.ASN) {
+ for _, asn := range asns {
+ out = append(out, geoip.ASN(asn))
+ }
+
+ return out
+}
+
// profilesToProtobuf converts a slice of profiles to protobuf structures.
func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) {
pbProfiles = make([]*Profile, 0, len(profiles))
@@ -244,6 +286,7 @@ func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) {
pbProfiles = append(pbProfiles, &Profile{
Parental: parentalToProtobuf(p.Parental),
BlockingMode: blockingModeToProtobuf(p.BlockingMode),
+ Access: accessToProtobuf(p.Access.Config()),
ProfileId: string(p.ID),
UpdateTime: timestamppb.New(p.UpdateTime),
DeviceIds: unsafelyConvertStrSlice[agd.DeviceID, string](p.DeviceIDs),
@@ -261,12 +304,50 @@ func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) {
Deleted: p.Deleted,
BlockPrivateRelay: p.BlockPrivateRelay,
BlockFirefoxCanary: p.BlockFirefoxCanary,
+ IpLogEnabled: p.IPLogEnabled,
})
}
return pbProfiles
}
+// accessToProtobuf converts access settings to protobuf structure.
+func accessToProtobuf(c *access.ProfileConfig) (ac *AccessSettings) {
+ if c == nil {
+ return nil
+ }
+
+ var allowedASNs []uint32
+ for _, asn := range c.AllowedASN {
+ allowedASNs = append(allowedASNs, uint32(asn))
+ }
+
+ var blockedASNs []uint32
+ for _, asn := range c.BlockedASN {
+ blockedASNs = append(blockedASNs, uint32(asn))
+ }
+
+ return &AccessSettings{
+ AllowlistCidr: accessNetsToProtobuf(c.AllowedNets),
+ BlocklistCidr: accessNetsToProtobuf(c.BlockedNets),
+ AllowlistAsn: allowedASNs,
+ BlocklistAsn: blockedASNs,
+ BlocklistDomainRules: c.BlocklistDomainRules,
+ }
+}
+
+// accessNetsToProtobuf converts slice of [netip.Prefix] to protobuf structure.
+func accessNetsToProtobuf(nets []netip.Prefix) (cidrs []*CidrRange) {
+ for _, n := range nets {
+ cidrs = append(cidrs, &CidrRange{
+ Address: n.Addr().AsSlice(),
+ Prefix: uint32(n.Bits()),
+ })
+ }
+
+ return cidrs
+}
+
// parentalToProtobuf converts parental settings to protobuf structure.
func parentalToProtobuf(s *agd.ParentalProtectionSettings) (pbSetts *ParentalProtectionSettings) {
if s == nil {
@@ -325,8 +406,8 @@ func scheduleToProtobuf(s *agd.ParentalProtectionSchedule) (pbSched *ParentalPro
}
// blockingModeToProtobuf converts a blocking-mode sum-type to a protobuf one.
-func blockingModeToProtobuf(m dnsmsg.BlockingModeCodec) (pbBlockingMode isProfile_BlockingMode) {
- switch m := m.Mode.(type) {
+func blockingModeToProtobuf(m dnsmsg.BlockingMode) (pbBlockingMode isProfile_BlockingMode) {
+ switch m := m.(type) {
case *dnsmsg.BlockingModeCustomIP:
return &Profile_BlockingModeCustomIp{
BlockingModeCustomIp: &BlockingModeCustomIP{
diff --git a/internal/profiledb/internal/filecachepb/storage.go b/internal/profiledb/internal/filecachepb/storage.go
index 3d60e0c..6972c95 100644
--- a/internal/profiledb/internal/filecachepb/storage.go
+++ b/internal/profiledb/internal/filecachepb/storage.go
@@ -75,11 +75,6 @@ func (s *Storage) Store(c *internal.FileCache) (err error) {
return fmt.Errorf("encoding protobuf: %w", err)
}
- err = renameio.WriteFile(s.path, b, 0o600)
- if err != nil {
- // Don't wrap the error, because it's informative enough as is.
- return err
- }
-
- return nil
+ // Don't wrap the error, because it's informative enough as is.
+ return renameio.WriteFile(s.path, b, 0o600)
}
diff --git a/internal/profiledb/internal/internal.go b/internal/profiledb/internal/internal.go
index fc8c0b5..da6491c 100644
--- a/internal/profiledb/internal/internal.go
+++ b/internal/profiledb/internal/internal.go
@@ -12,7 +12,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 = 7
+const FileCacheVersion = 9
// 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 deab918..c238417 100644
--- a/internal/profiledb/internal/profiledbtest/profiledbtest.go
+++ b/internal/profiledb/internal/profiledbtest/profiledbtest.go
@@ -6,9 +6,11 @@ import (
"testing"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/stretchr/testify/require"
)
@@ -51,11 +53,9 @@ func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) {
},
Enabled: true,
},
- BlockingMode: dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- },
- ID: ProfileID,
- DeviceIDs: []agd.DeviceID{dev.ID},
+ BlockingMode: &dnsmsg.BlockingModeNullIP{},
+ ID: ProfileID,
+ DeviceIDs: []agd.DeviceID{dev.ID},
RuleListIDs: []agd.FilterListID{
"adguard_dns_filter",
},
@@ -69,9 +69,17 @@ func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) {
BlockDangerousDomains: true,
BlockNewlyRegisteredDomains: false,
},
+ Access: access.NewDefaultProfile(&access.ProfileConfig{
+ AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
+ BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
+ AllowedASN: []geoip.ASN{1},
+ BlockedASN: []geoip.ASN{2},
+ BlocklistDomainRules: []string{"block.test"},
+ }),
RuleListsEnabled: true,
QueryLogEnabled: true,
BlockPrivateRelay: true,
BlockFirefoxCanary: true,
+ IPLogEnabled: true,
}, dev
}
diff --git a/internal/profiledb/profiledb.go b/internal/profiledb/profiledb.go
index 2fb1bf1..8557852 100644
--- a/internal/profiledb/profiledb.go
+++ b/internal/profiledb/profiledb.go
@@ -6,17 +6,17 @@ import (
"fmt"
"net/netip"
"path/filepath"
+ "slices"
"sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
)
// Interface is the local database of user profiles and devices.
@@ -39,6 +39,27 @@ type Interface interface {
ProfileByLinkedIP(ctx context.Context, ip netip.Addr) (p *agd.Profile, d *agd.Device, err error)
}
+// Config represents the profile database configuration.
+type Config struct {
+ // Storage returns the data for this profile DB.
+ Storage Storage
+
+ // CacheFilePath is the path to the profile cache file. If cacheFilePath is
+ // the string "none", filesystem cache is disabled.
+ CacheFilePath string
+
+ // FullSyncIvl is the interval between two full synchronizations with the
+ // storage.
+ FullSyncIvl time.Duration
+
+ // FullSyncRetryIvl is the interval between two retries of full
+ // synchronizations with the storage.
+ FullSyncRetryIvl time.Duration
+
+ // InitialTimeout is the timeout for initial refresh.
+ InitialTimeout time.Duration
+}
+
// Default is the default in-memory implementation of the [Interface] interface
// that can refresh itself from the provided storage.
type Default struct {
@@ -46,8 +67,8 @@ type Default struct {
// linkedIPToDeviceID, and dedicatedIPToDeviceID maps.
mapsMu *sync.RWMutex
- // refreshMu protects syncTime and lastFullSync. These are only used within
- // Refresh, so this is also basically a refresh serializer.
+ // refreshMu serializes Refresh calls and access to all values used inside
+ // of it.
refreshMu *sync.Mutex
// cache is the filesystem-cache storage used by this profile database.
@@ -77,45 +98,52 @@ type Default struct {
// requests to the storage, unless it's a full synchronization.
syncTime time.Time
- // lastFullSync is the time of the last full synchronization.
+ // lastFullSync is the time of the last successful full synchronization.
lastFullSync time.Time
+ // lastFullSyncError is the time of the last unsuccessful attempt at a full
+ // synchronization. If the last full synchronization was successful, this
+ // field is time.Time{}.
+ lastFullSyncError time.Time
+
// fullSyncIvl is the interval between two full synchronizations with the
// storage.
fullSyncIvl time.Duration
+
+ // fullSyncRetryIvl is the interval between two retries of full
+ // synchronizations with the storage.
+ fullSyncRetryIvl time.Duration
}
// New returns a new default in-memory profile database with a filesystem cache.
-// The initial refresh is performed immediately with the constant timeout of 1
-// minute, beyond which an empty profiledb is returned. If cacheFilePath is the
-// string "none", filesystem cache is disabled. db is never nil.
-func New(
- s Storage,
- fullSyncIvl time.Duration,
- cacheFilePath string,
-) (db *Default, err error) {
+// The initial refresh is performed immediately with the given timeout, beyond
+// which an empty profiledb is returned. If cacheFilePath is the string "none",
+// filesystem cache is disabled. db is never nil.
+func New(conf *Config) (db *Default, err error) {
var cacheStorage internal.FileCacheStorage
- if cacheFilePath == "none" {
+ if conf.CacheFilePath == "none" {
cacheStorage = internal.EmptyFileCacheStorage{}
- } else if ext := filepath.Ext(cacheFilePath); ext == ".pb" {
- cacheStorage = filecachepb.New(cacheFilePath)
+ } else if ext := filepath.Ext(conf.CacheFilePath); ext == ".pb" {
+ cacheStorage = filecachepb.New(conf.CacheFilePath)
} else {
- return nil, fmt.Errorf("file %q is not protobuf", cacheFilePath)
+ return nil, fmt.Errorf("file %q is not protobuf", conf.CacheFilePath)
}
db = &Default{
mapsMu: &sync.RWMutex{},
refreshMu: &sync.Mutex{},
cache: cacheStorage,
- storage: s,
+ storage: conf.Storage,
syncTime: time.Time{},
lastFullSync: time.Time{},
+ lastFullSyncError: time.Time{},
profiles: make(map[agd.ProfileID]*agd.Profile),
devices: make(map[agd.DeviceID]*agd.Device),
deviceIDToProfileID: make(map[agd.DeviceID]agd.ProfileID),
linkedIPToDeviceID: make(map[netip.Addr]agd.DeviceID),
dedicatedIPToDeviceID: make(map[netip.Addr]agd.DeviceID),
- fullSyncIvl: fullSyncIvl,
+ fullSyncIvl: conf.FullSyncIvl,
+ fullSyncRetryIvl: conf.FullSyncRetryIvl,
}
err = db.loadFileCache()
@@ -123,11 +151,7 @@ func New(
log.Error("profiledb: fs cache: loading: %s", err)
}
- // initialTimeout defines the maximum duration of the first attempt to load
- // the profiledb.
- const initialTimeout = 1 * time.Minute
-
- ctx, cancel := context.WithTimeout(context.Background(), initialTimeout)
+ ctx, cancel := context.WithTimeout(context.Background(), conf.InitialTimeout)
defer cancel()
log.Info("profiledb: initial refresh")
@@ -149,14 +173,16 @@ func New(
}
// type check
-var _ agd.Refresher = (*Default)(nil)
+var _ agdservice.Refresher = (*Default)(nil)
-// Refresh implements the [Refresher] interface for *Default. It updates the
-// internal maps and the synchronization time using the data it receives from
-// the storage.
+// Refresh implements the [agdservice.Refresher] interface for *Default. It
+// updates the internal maps and the synchronization time using the data it
+// receives from the storage.
+//
+// TODO(a.garipov): Consider splitting the full refresh logic into a separate
+// method.
func (db *Default) Refresh(ctx context.Context) (err error) {
- sinceLastFullSync := time.Since(db.lastFullSync)
- isFullSync := sinceLastFullSync >= db.fullSyncIvl
+ sinceLastAttempt, isFullSync := db.needsFullSync()
var totalProfiles, totalDevices int
startTime := time.Now()
@@ -181,18 +207,10 @@ func (db *Default) Refresh(ctx context.Context) (err error) {
db.refreshMu.Lock()
defer db.refreshMu.Unlock()
- syncTime := db.syncTime
- if isFullSync {
- log.Info("profiledb: full sync, %s since %s", sinceLastFullSync, db.lastFullSync)
-
- syncTime = time.Time{}
- }
-
- resp, err := db.storage.Profiles(ctx, &StorageRequest{
- SyncTime: syncTime,
- })
+ resp, err := db.fetchProfiles(ctx, sinceLastAttempt, isFullSync)
if err != nil {
- return fmt.Errorf("updating profiles: %w", err)
+ // Don't wrap the error, because it's informative enough as is.
+ return err
}
profiles := resp.Profiles
@@ -209,6 +227,7 @@ func (db *Default) Refresh(ctx context.Context) (err error) {
db.syncTime = resp.SyncTime
if isFullSync {
db.lastFullSync = time.Now()
+ db.lastFullSyncError = time.Time{}
err = db.cache.Store(&internal.FileCache{
SyncTime: resp.SyncTime,
@@ -227,6 +246,61 @@ func (db *Default) Refresh(ctx context.Context) (err error) {
return nil
}
+// fetchProfiles fetches the profiles and devices from the storage. It returns
+// the response and the error, if any. If isFullSync is true, the last full
+// synchronization error time is updated on error. It must only be called under
+// the refreshMu lock.
+func (db *Default) fetchProfiles(
+ ctx context.Context,
+ sinceLastAttempt time.Duration,
+ isFullSync bool,
+) (sr *StorageResponse, err error) {
+ syncTime := db.syncTime
+ if isFullSync {
+ log.Info("profiledb: full sync, %s since last attempt", sinceLastAttempt)
+
+ syncTime = time.Time{}
+ }
+
+ sr, err = db.storage.Profiles(ctx, &StorageRequest{
+ SyncTime: syncTime,
+ })
+ if err == nil {
+ return sr, nil
+ }
+
+ if isFullSync {
+ db.lastFullSyncError = time.Now()
+ }
+
+ if errors.Is(err, context.DeadlineExceeded) {
+ metrics.IncrementCond(
+ isFullSync,
+ metrics.ProfilesSyncFullTimeouts,
+ metrics.ProfilesSyncPartTimeouts,
+ )
+ }
+
+ return nil, fmt.Errorf("updating profiles: %w", err)
+}
+
+// needsFullSync determines if a full synchronization is necessary. If the last
+// full synchronization was successful, it returns true if it's time for a new
+// one. Otherwise, it returns true if it's time for a retry.
+func (db *Default) needsFullSync() (sinceFull time.Duration, isFull bool) {
+ lastFull := db.lastFullSync
+ sinceFull = time.Since(lastFull)
+ if db.lastFullSyncError.IsZero() {
+ return sinceFull, sinceFull >= db.fullSyncIvl
+ }
+
+ log.Info("profiledb: warning: %s since last successful full sync at %s", sinceFull, lastFull)
+
+ sinceLastError := time.Since(db.lastFullSyncError)
+
+ return sinceLastError, sinceLastError >= db.fullSyncRetryIvl
+}
+
// loadFileCache loads the profiles data from the filesystem cache.
func (db *Default) loadFileCache() (err error) {
const logPrefix = "profiledb: cache"
@@ -278,11 +352,11 @@ func (db *Default) setProfiles(profiles []*agd.Profile, devices []*agd.Device, i
defer db.mapsMu.Unlock()
if isFullSync {
- maps.Clear(db.profiles)
- maps.Clear(db.devices)
- maps.Clear(db.deviceIDToProfileID)
- maps.Clear(db.linkedIPToDeviceID)
- maps.Clear(db.dedicatedIPToDeviceID)
+ clear(db.profiles)
+ clear(db.devices)
+ clear(db.deviceIDToProfileID)
+ clear(db.linkedIPToDeviceID)
+ clear(db.dedicatedIPToDeviceID)
}
for _, p := range profiles {
diff --git a/internal/profiledb/profiledb_test.go b/internal/profiledb/profiledb_test.go
index af78807..5a3162c 100644
--- a/internal/profiledb/profiledb_test.go
+++ b/internal/profiledb/profiledb_test.go
@@ -54,11 +54,9 @@ func newDefaultProfileDB(tb testing.TB, devices <-chan []*agd.Device) (db *profi
return &profiledb.StorageResponse{
Profiles: []*agd.Profile{{
- BlockingMode: dnsmsg.BlockingModeCodec{
- Mode: &dnsmsg.BlockingModeNullIP{},
- },
- ID: profiledbtest.ProfileID,
- DeviceIDs: devIDs,
+ BlockingMode: &dnsmsg.BlockingModeNullIP{},
+ ID: profiledbtest.ProfileID,
+ DeviceIDs: devIDs,
}},
Devices: devices,
}, nil
@@ -68,7 +66,13 @@ func newDefaultProfileDB(tb testing.TB, devices <-chan []*agd.Device) (db *profi
OnProfiles: onProfiles,
}
- db, err := profiledb.New(ps, 1*time.Minute, "none")
+ db, err := profiledb.New(&profiledb.Config{
+ Storage: ps,
+ FullSyncIvl: 1 * time.Minute,
+ FullSyncRetryIvl: 1 * time.Minute,
+ InitialTimeout: testTimeout,
+ CacheFilePath: "none",
+ })
require.NoError(tb, err)
return db
@@ -303,7 +307,13 @@ func TestDefaultProfileDB_fileCache_success(t *testing.T) {
})
require.NoError(t, err)
- db, err := profiledb.New(ps, 1*time.Minute, cacheFilePath)
+ db, err := profiledb.New(&profiledb.Config{
+ Storage: ps,
+ FullSyncIvl: 1 * time.Minute,
+ FullSyncRetryIvl: 1 * time.Minute,
+ InitialTimeout: testTimeout,
+ CacheFilePath: cacheFilePath,
+ })
require.NoError(t, err)
require.NotNil(t, db)
@@ -335,7 +345,13 @@ func TestDefaultProfileDB_fileCache_badVersion(t *testing.T) {
})
require.NoError(t, err)
- db, err := profiledb.New(ps, 1*time.Minute, cacheFilePath)
+ db, err := profiledb.New(&profiledb.Config{
+ Storage: ps,
+ FullSyncIvl: 1 * time.Minute,
+ FullSyncRetryIvl: 1 * time.Minute,
+ InitialTimeout: testTimeout,
+ CacheFilePath: cacheFilePath,
+ })
assert.NoError(t, err)
assert.NotNil(t, db)
assert.True(t, storageCalled)
diff --git a/internal/querylog/entry.go b/internal/querylog/entry.go
index 36dcfd9..2b1be77 100644
--- a/internal/querylog/entry.go
+++ b/internal/querylog/entry.go
@@ -2,24 +2,27 @@ package querylog
import (
"fmt"
+ "net/netip"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
)
-// Query Log Entry
-
// Entry is a single query log entry.
type Entry struct {
+ // RemoteIP is the remote IP address of the client.
+ RemoteIP netip.Addr
+
// RequestResult is the result of filtering the DNS request.
RequestResult filter.Result
// ResponseResult is the result of filtering the DNS response.
ResponseResult filter.Result
- // Time is the time of receiving the request in milliseconds.
+ // Time is the time of receiving the request.
Time time.Time
// ProfileID is the detected profile ID, if any.
@@ -29,11 +32,11 @@ type Entry struct {
DeviceID agd.DeviceID
// ClientCountry is the detected country of the client's IP address, if any.
- ClientCountry agd.Country
+ ClientCountry geoip.Country
// ResponseCountry is the detected country of the first IP in the response
// sent to the client, if any.
- ResponseCountry agd.Country
+ ResponseCountry geoip.Country
// DomainFQDN is the fully-qualified name of the requested resource.
DomainFQDN string
@@ -43,7 +46,7 @@ type Entry struct {
// ClientASN is the detected autonomous system number of the client's IP
// address, if any.
- ClientASN agd.ASN
+ ClientASN geoip.ASN
// Elapsed is the time passed since the beginning of the request processing
// in milliseconds.
@@ -131,6 +134,14 @@ func toResultCode(r filter.Result, resp bool) (c resultCode) {
// jsonlEntry is a single JSONL query log jsonlEntry / line.
type jsonlEntry struct {
+ // RemoteIP is the remote IP address of the client. This field is optional.
+ //
+ // The short name "ip" stands for "IP".
+ //
+ // TODO(d.kolyshev): Do not use pointer when `omitempty` is supported for
+ // zero structs. See https://github.com/golang/go/issues/11939.
+ RemoteIP *netip.Addr `json:"ip,omitempty"`
+
// RequestID is the ID of the request.
//
// The short name "u" stands for "unique".
@@ -149,13 +160,13 @@ type jsonlEntry struct {
// ClientCountry is the detected country of the client's IP address, if any.
//
// The short name "c" stands for "client country".
- ClientCountry agd.Country `json:"c,omitempty"`
+ ClientCountry geoip.Country `json:"c,omitempty"`
// ResponseCountry is the detected country of the first IP in the response
// sent to the client, if any.
//
// The short name "d" stands for "direction" or "destination".
- ResponseCountry agd.Country `json:"d,omitempty"`
+ ResponseCountry geoip.Country `json:"d,omitempty"`
// DomainFQDN is the requested resource name.
//
@@ -184,7 +195,7 @@ type jsonlEntry struct {
// address, if any.
//
// The short name "a" stands for "ASN".
- ClientASN agd.ASN `json:"a,omitempty"`
+ ClientASN geoip.ASN `json:"a,omitempty"`
// Elapsed is the time passed since the beginning of the request processing
// in milliseconds.
diff --git a/internal/querylog/fs.go b/internal/querylog/fs.go
index f9ee5b7..03dd0a4 100644
--- a/internal/querylog/fs.go
+++ b/internal/querylog/fs.go
@@ -5,15 +5,16 @@ import (
"context"
"encoding/json"
"fmt"
+ "net/netip"
"os"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optlog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mathutil"
+ "github.com/AdguardTeam/golibs/syncutil"
)
// FileSystemConfig is the configuration of the file system query log.
@@ -27,7 +28,7 @@ type FileSystemConfig struct {
func NewFileSystem(c *FileSystemConfig) (l *FileSystem) {
return &FileSystem{
path: c.Path,
- bufferPool: agdsync.NewTypedPool(func() (v *entryBuffer) {
+ bufferPool: syncutil.NewPool(func() (v *entryBuffer) {
return &entryBuffer{
ent: &jsonlEntry{},
buf: &bytes.Buffer{},
@@ -47,7 +48,7 @@ type entryBuffer struct {
type FileSystem struct {
// bufferPool is a pool with [*entryBuffer] instances used to avoid extra
// allocations when serializing query log items to JSON and writing them.
- bufferPool *agdsync.TypedPool[entryBuffer]
+ bufferPool *syncutil.Pool[entryBuffer]
// path is the path to the query log file.
path string
@@ -73,6 +74,11 @@ func (l *FileSystem) Write(_ context.Context, e *Entry) (err error) {
defer l.bufferPool.Put(entBuf)
entBuf.buf.Reset()
+ var remoteIP *netip.Addr
+ if e.RemoteIP != (netip.Addr{}) {
+ remoteIP = &e.RemoteIP
+ }
+
c, id, r := resultData(e.RequestResult, e.ResponseResult)
*entBuf.ent = jsonlEntry{
RequestID: e.RequestID.String(),
@@ -91,6 +97,7 @@ func (l *FileSystem) Write(_ context.Context, e *Entry) (err error) {
Protocol: e.Protocol,
ResultCode: c,
ResponseCode: e.ResponseCode,
+ RemoteIP: remoteIP,
}
var f *os.File
diff --git a/internal/querylog/fs_test.go b/internal/querylog/fs_test.go
index 28c0621..6528cce 100644
--- a/internal/querylog/fs_test.go
+++ b/internal/querylog/fs_test.go
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@@ -64,7 +64,7 @@ func TestFileSystem_Write(t *testing.T) {
t.Run("nxdomain", func(t *testing.T) {
e = testEntry()
e.RequestResult, e.ResponseResult = nil, nil
- e.ResponseCountry = agd.CountryNone
+ e.ResponseCountry = geoip.CountryNone
e.ResponseCode = dns.RcodeNameError
err = l.Write(ctx, e)
diff --git a/internal/querylog/querylog_test.go b/internal/querylog/querylog_test.go
index f6ec01e..d7e1795 100644
--- a/internal/querylog/querylog_test.go
+++ b/internal/querylog/querylog_test.go
@@ -6,6 +6,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
@@ -30,8 +31,8 @@ func testEntry() (e *querylog.Entry) {
RequestID: testRequestID,
ProfileID: "prof1234",
DeviceID: "dev1234",
- ClientCountry: agd.CountryRU,
- ResponseCountry: agd.CountryUS,
+ ClientCountry: geoip.CountryRU,
+ ResponseCountry: geoip.CountryUS,
DomainFQDN: "example.com.",
Protocol: agd.ProtoDNS,
ClientASN: 1234,
diff --git a/internal/rulestat/http.go b/internal/rulestat/http.go
index 1201156..2a333a1 100644
--- a/internal/rulestat/http.go
+++ b/internal/rulestat/http.go
@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"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/netutil"
@@ -99,10 +100,11 @@ func (s *HTTP) Collect(_ context.Context, id agd.FilterListID, text agd.FilterRu
}
// type check
-var _ agd.Refresher = (*HTTP)(nil)
+var _ agdservice.Refresher = (*HTTP)(nil)
-// Refresh implements the agd.Refresher interface for *HTTP. It uploads the
-// collected statistics to s.u and starts collecting a new set of statistics.
+// Refresh implements the [agdservice.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) {
err = s.refresh(ctx)
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 72fe3a7..3c698ff 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -1,36 +1,38 @@
module github.com/AdguardTeam/AdGuardDNS/internal/tools
-go 1.20
+go 1.21.5
require (
github.com/fzipp/gocyclo v0.6.0
github.com/golangci/misspell v0.4.1
- github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601
+ github.com/gordonklaus/ineffassign v0.1.0
github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1
- github.com/securego/gosec/v2 v2.17.0
- github.com/uudashr/gocognit v1.0.7
- golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846
+ github.com/securego/gosec/v2 v2.18.2
+ // TODO(a.garipov): Return to latest once the release is tagged
+ // correctly. See uudashr/gocognit#31.
+ github.com/uudashr/gocognit v1.1.2
+ golang.org/x/tools v0.16.0
golang.org/x/vuln v1.0.1
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.31.0
- honnef.co/go/tools v0.4.5
+ honnef.co/go/tools v0.4.6
mvdan.cc/gofumpt v0.5.0
- mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0
+ mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
- github.com/google/go-cmp v0.5.9 // indirect
- github.com/google/uuid v1.3.1 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/uuid v1.4.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect
- golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/sync v0.3.0 // indirect
- golang.org/x/sys v0.11.0 // indirect
+ golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e // indirect
+ golang.org/x/mod v0.14.0 // indirect
+ golang.org/x/sync v0.5.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 0cafb37..3865de6 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -3,43 +3,57 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4=
github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
+github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g=
github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI=
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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/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-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
-github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
-github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+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/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
-github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8=
-github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
+github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
+github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
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/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fxdJg=
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
-github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
-github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
+github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
+github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
+github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI=
-github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I=
+github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo=
-github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI=
+github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k=
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -51,26 +65,27 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63 h1:XkcpbHJE31bhdecT6qfUGtB7MCIKA8Vb9uGOyX/T364=
-golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e h1:Iel2aGgaO80fSb1N54L7SE6XMeVvYy6caKt8u/5LvR8=
+golang.org/x/exp/typeparams v0.0.0-20231127185646-65229373498e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -81,22 +96,23 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
-golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
-golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
-golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
+golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
+golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -112,9 +128,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.4.5 h1:YGD4H+SuIOOqsyoLOpZDWcieM28W47/zRO7f+9V3nvo=
-honnef.co/go/tools v0.4.5/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k=
+honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8=
+honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0=
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
-mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 h1:NAENkqZ+Xofhqs4R4Af+i3HpZj1M23SFn/lHfRh1D4E=
-mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0/go.mod h1:flQN1deud3vIpPdF88533Lpp/MvzGLgPIPjB1kgBf4I=
+mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb h1:xiF91GJnDSbyPdiZB5d52N2VpZfGhjM4Ji75cjzuooQ=
+mvdan.cc/unparam v0.0.0-20230917202934-3ee2d22f45fb/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI=
diff --git a/internal/websvc/handler_test.go b/internal/websvc/handler_test.go
index 35cca21..c5f9ed7 100644
--- a/internal/websvc/handler_test.go
+++ b/internal/websvc/handler_test.go
@@ -1,7 +1,6 @@
package websvc_test
import (
- "context"
"net/http"
"net/http/httptest"
"net/url"
@@ -9,6 +8,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/testutil"
@@ -50,12 +50,12 @@ func TestService_ServeHTTP(t *testing.T) {
var err error
require.NotPanics(t, func() {
- err = svc.Start()
+ err = svc.Start(agdtest.ContextWithTimeout(t, testTimeout))
})
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
- return svc.Shutdown(context.Background())
+ return svc.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
})
// DNSCheck path.
diff --git a/internal/websvc/linkip.go b/internal/websvc/linkip.go
index 66f34a1..02bdeac 100644
--- a/internal/websvc/linkip.go
+++ b/internal/websvc/linkip.go
@@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"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"
@@ -23,14 +24,14 @@ import (
// linkedIPProxy proxies selected requests to a remote address.
type linkedIPProxy struct {
httpProxy *httputil.ReverseProxy
- errColl agd.ErrorCollector
+ errColl errcoll.Interface
logPrefix string
}
// linkedIPHandler returns a linked IP proxy handler.
func linkedIPHandler(
apiURL *url.URL,
- errColl agd.ErrorCollector,
+ errColl errcoll.Interface,
name string,
timeout time.Duration,
) (h http.Handler) {
@@ -81,7 +82,7 @@ func linkedIPHandler(
ctx := r.Context()
reqID, _ := agd.RequestIDFromContext(ctx)
m, p := r.Method, r.URL.Path
- agd.Collectf(ctx, errColl, "%s: proxying %s %s: req %s: %w", logPrefix, m, p, reqID, err)
+ errcoll.Collectf(ctx, errColl, "%s: proxying %s %s: req %s: %w", logPrefix, m, p, reqID, err)
}
return &linkedIPProxy{
diff --git a/internal/websvc/websvc.go b/internal/websvc/websvc.go
index 583648c..1d1edbc 100644
--- a/internal/websvc/websvc.go
+++ b/internal/websvc/websvc.go
@@ -10,7 +10,8 @@ import (
"net/url"
"time"
- "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
+ "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
)
@@ -38,7 +39,7 @@ type Config struct {
DNSCheck http.Handler
// ErrColl is used to collect linked IP proxy errors and other errors.
- ErrColl agd.ErrorCollector
+ 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.
@@ -200,11 +201,11 @@ func blockPageServers(
}
// type check
-var _ agd.Service = (*Service)(nil)
+var _ agdservice.Interface = (*Service)(nil)
-// Start implements the agd.Service interface for *Service. svc may be nil. It
-// panics if one of the servers could not start.
-func (svc *Service) Start() (err error) {
+// Start implements the [agdservice.Interface] interface for *Service. svc may
+// be nil. It panics if one of the servers could not start.
+func (svc *Service) Start(ctx context.Context) (err error) {
if svc == nil {
return nil
}
@@ -260,7 +261,8 @@ func mustStartServer(srv *http.Server) {
}
}
-// Shutdown implements the agd.Service interface for *Service. svc may be nil.
+// Shutdown implements the [agdservice.Interface] interface for *Service. svc
+// may be nil.
func (svc *Service) Shutdown(ctx context.Context) (err error) {
if svc == nil {
return nil
@@ -280,7 +282,7 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
srvs: svc.adultBlocking,
}, {
name: "non-doh",
- srvs: svc.adultBlocking,
+ srvs: svc.nonDoH,
}}
for _, g := range serverGroups {
diff --git a/internal/websvc/websvc_test.go b/internal/websvc/websvc_test.go
index 1b8309b..f14f6ab 100644
--- a/internal/websvc/websvc_test.go
+++ b/internal/websvc/websvc_test.go
@@ -1,7 +1,6 @@
package websvc_test
import (
- "context"
"io"
"net/http"
"net/netip"
@@ -10,6 +9,7 @@ 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/testutil"
@@ -154,11 +154,11 @@ func startService(t *testing.T, c *websvc.Config) {
var err error
require.NotPanics(t, func() {
- err = svc.Start()
+ err = svc.Start(agdtest.ContextWithTimeout(t, testTimeout))
})
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
- return svc.Shutdown(context.Background())
+ return svc.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
})
}
diff --git a/scripts/backend/main.go b/scripts/backend/main.go
new file mode 100644
index 0000000..b3449ac
--- /dev/null
+++ b/scripts/backend/main.go
@@ -0,0 +1,162 @@
+// backend contains mock GRPC server for BILLSTAT_URL and PROFILES_URL
+// endpoints.
+package main
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "net/netip"
+ "strconv"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/log"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/protobuf/types/known/durationpb"
+ "google.golang.org/protobuf/types/known/emptypb"
+)
+
+func main() {
+ const listenAddr = "localhost:6062"
+ l, err := net.Listen("tcp", listenAddr)
+ if err != nil {
+ log.Fatalf("getting listener: %s", err)
+ }
+
+ grpcSrv := grpc.NewServer()
+ srv := &mockDNSServiceServer{}
+ backendpb.RegisterDNSServiceServer(grpcSrv, srv)
+
+ log.Info("staring serving on %s", listenAddr)
+ err = grpcSrv.Serve(l)
+ if err != nil {
+ log.Fatalf("serving grpc: %s", err)
+ }
+}
+
+// mockDNSServiceServer is the mock [backendpb.DNSServiceServer].
+type mockDNSServiceServer struct {
+ backendpb.UnimplementedDNSServiceServer
+}
+
+// type check
+var _ backendpb.DNSServiceServer = (*mockDNSServiceServer)(nil)
+
+// GetDNSProfiles implements the [backendpb.DNSServiceServer] interface for
+// *mockDNSServiceServer
+func (s *mockDNSServiceServer) GetDNSProfiles(
+ req *backendpb.DNSProfilesRequest,
+ srv backendpb.DNSService_GetDNSProfilesServer,
+) (err error) {
+ log.Info("getting dns profiles: sync time: %s", req.SyncTime.AsTime())
+
+ t := time.Now()
+ syncTime := strconv.FormatInt(t.UnixMilli(), 10)
+ trailerMD := metadata.MD{
+ "sync_time": []string{syncTime},
+ }
+
+ srv.SetTrailer(trailerMD)
+ err = srv.Send(mockDNSProfile())
+ if err != nil {
+ log.Info("sending dns profile: %s", err)
+ }
+
+ return nil
+}
+
+// SaveDevicesBillingStat implements the [backendpb.DNSServiceServer] interface
+// for *mockDNSServiceServer
+func (s *mockDNSServiceServer) SaveDevicesBillingStat(
+ srv backendpb.DNSService_SaveDevicesBillingStatServer,
+) (err error) {
+ for {
+ bs, err := srv.Recv()
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return srv.SendAndClose(&emptypb.Empty{})
+ } else {
+ return fmt.Errorf("receiving billing stat: %w", err)
+ }
+ }
+
+ log.Info("saving billing stat: device: %q", bs.DeviceId)
+ }
+}
+
+// mockDNSProfile returns a mock instance of [*backendpb.DNSProfile].
+func mockDNSProfile() (dp *backendpb.DNSProfile) {
+ dayRange := &backendpb.DayRange{
+ Start: durationpb.New(0),
+ End: durationpb.New(59 * time.Minute),
+ }
+
+ devices := []*backendpb.DeviceSettings{{
+ Id: "118ffe93",
+ Name: "118ffe93-name",
+ FilteringEnabled: false,
+ LinkedIp: []byte{1, 1, 1, 1},
+ DedicatedIps: [][]byte{{1, 1, 1, 2}},
+ }}
+
+ return &backendpb.DNSProfile{
+ DnsId: "mock1234",
+ FilteringEnabled: true,
+ QueryLogEnabled: true,
+ Deleted: false,
+ SafeBrowsing: &backendpb.SafeBrowsingSettings{
+ Enabled: true,
+ BlockDangerousDomains: true,
+ BlockNrd: false,
+ },
+ Parental: &backendpb.ParentalSettings{
+ Enabled: false,
+ BlockAdult: false,
+ GeneralSafeSearch: false,
+ YoutubeSafeSearch: false,
+ BlockedServices: []string{"youtube"},
+ Schedule: &backendpb.ScheduleSettings{
+ Tmz: "GMT",
+ WeeklyRange: &backendpb.WeeklyRange{
+ Sun: nil,
+ Mon: dayRange,
+ Tue: dayRange,
+ Wed: dayRange,
+ Thu: dayRange,
+ Fri: dayRange,
+ Sat: nil,
+ },
+ },
+ },
+ Access: &backendpb.AccessSettings{
+ AllowlistCidr: []*backendpb.CidrRange{{
+ Address: netip.MustParseAddr("1.1.1.0").AsSlice(),
+ Prefix: 24,
+ }},
+ BlocklistCidr: []*backendpb.CidrRange{{
+ Address: netip.MustParseAddr("2.2.2.0").AsSlice(),
+ Prefix: 24,
+ }},
+ AllowlistAsn: []uint32{1},
+ BlocklistAsn: []uint32{2},
+ BlocklistDomainRules: []string{"block.test"},
+ },
+ RuleLists: &backendpb.RuleListsSettings{
+ Enabled: true,
+ Ids: []string{"1"},
+ },
+ Devices: devices,
+ CustomRules: []string{"||example.org^"},
+ FilteredResponseTtl: durationpb.New(10 * time.Second),
+ BlockPrivateRelay: true,
+ BlockFirefoxCanary: true,
+ BlockingMode: &backendpb.DNSProfile_BlockingModeCustomIp{
+ BlockingModeCustomIp: &backendpb.BlockingModeCustomIP{
+ Ipv4: []byte{1, 2, 3, 4},
+ },
+ },
+ }
+}
diff --git a/scripts/make/go-fuzz.sh b/scripts/make/go-fuzz.sh
new file mode 100644
index 0000000..792c5af
--- /dev/null
+++ b/scripts/make/go-fuzz.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+verbose="${VERBOSE:-0}"
+readonly verbose
+
+# Verbosity levels:
+# 0 = Don't print anything except for errors.
+# 1 = Print commands, but not nested commands.
+# 2 = Print everything.
+if [ "$verbose" -gt '1' ]
+then
+ set -x
+ v_flags='-v=1'
+ x_flags='-x=1'
+elif [ "$verbose" -gt '0' ]
+then
+ set -x
+ v_flags='-v=1'
+ x_flags='-x=0'
+else
+ set +x
+ v_flags='-v=0'
+ x_flags='-x=0'
+fi
+readonly v_flags x_flags
+
+set -e -f -u
+
+if [ "${RACE:-1}" -eq '0' ]
+then
+ race_flags='--race=0'
+else
+ race_flags='--race=1'
+fi
+readonly race_flags
+
+go="${GO:-go}"
+
+count_flags='--count=1'
+shuffle_flags='--shuffle=on'
+timeout_flags="${TIMEOUT_FLAGS:---timeout=30s}"
+fuzztime_flags="${FUZZTIME_FLAGS:---fuzztime=20s}"
+
+readonly go count_flags shuffle_flags timeout_flags fuzztime_flags
+
+# TODO(a.garipov): File an issue about using --fuzz with multiple packages.
+"$go" test\
+ "$count_flags"\
+ "$shuffle_flags"\
+ "$race_flags"\
+ "$timeout_flags"\
+ "$x_flags"\
+ "$v_flags"\
+ "$fuzztime_flags"\
+ --fuzz="FuzzCloner_Clone"\
+ ./internal/dnsmsg/;
diff --git a/scripts/make/go-gen.sh b/scripts/make/go-gen.sh
index 39079ee..6955d44 100644
--- a/scripts/make/go-gen.sh
+++ b/scripts/make/go-gen.sh
@@ -24,22 +24,36 @@ go="${GO:-go}"
readonly go
(
- cd ./internal/agd/
- "$go" run ./country_generate.go
+ cd ./internal/geoip/
+ "$go" run ./country_generate.go
)
(
- cd ./internal/geoip/
- "$go" run ./asntops_generate.go
+ cd ./internal/geoip/
+ "$go" run ./asntops_generate.go
)
(
- cd ./internal/profiledb/internal/filecachepb/
+ cd ./internal/ecscache/
+ "$go" run ./ecsblocklist_generate.go
+ # Force format code, because it's not possible to make an accurate template
+ # for a long list of strings with different lengths.
+ gofumpt -l -w ./ecsblocklist.go
+)
+
+(
+ cd ./internal/profiledb/internal/filecachepb/
protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto
)
(
- cd ./internal/backendpb/
- protoc --go_opt=paths=source_relative --go_out=.\
- --go-grpc_opt=paths=source_relative --go-grpc_out=. ./backend.proto
+ cd ./internal/backendpb/
+ protoc\
+ --go-grpc_opt=Mbackend.proto=./backendpb\
+ --go-grpc_opt=paths=source_relative\
+ --go-grpc_out=.\
+ --go_opt=Mbackend.proto=./backendpb\
+ --go_opt=paths=source_relative\
+ --go_out=.\
+ ./backend.proto
)
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index 3d99053..330252a 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -30,33 +30,6 @@ set -f -u
-# Warnings
-
-go_version="$( "${GO:-go}" version )"
-readonly go_version
-
-go_min_version='go1.20.7'
-go_version_msg="
-warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
-if you have the version installed, please set the GO environment variable.
-for example:
-
- export GO='${go_min_version}'
-"
-readonly go_min_version go_version_msg
-
-case "$go_version"
-in
-('go version'*"$go_min_version"*)
- # Go on.
- ;;
-(*)
- echo "$go_version_msg" 1>&2
- ;;
-esac
-
-
-
# Simple analyzers
# blocklist_imports is a simple check against unwanted packages. The following
@@ -74,11 +47,12 @@ esac
#
# See https://github.com/golang/go/issues/45200.
#
-# * Package sort is replaced by golang.org/x/exp/slices.
+# * Package sort is replaced by package slices.
#
# * Package unsafe is… unsafe.
#
-# * Package golang.org/x/net/context has been moved into stdlib.
+# * Packages golang.org/x/exp/slices and golang.org/x/net/context have been
+# moved into stdlib.
#
# Currently, the only standard exception are files generated from protobuf
# schemas, which use package reflect. If your project needs more exceptions,
@@ -89,8 +63,8 @@ esac
# * internal/profiledb/internal/filecachepb/unsafe.go: a “safe” unsafe helper
# to prevent excessive allocations.
#
-# TODO(a.garipov): Add deprecated packages golang.org/x/exp/maps and
-# golang.org/x/exp/slices once all projects switch to Go 1.21.
+# TODO(a.garipov): Add deprecated package golang.org/x/exp/maps and once all
+# projects switch to Go 1.22 or later.
blocklist_imports() {
git grep\
-e '[[:space:]]"errors"$'\
@@ -99,6 +73,7 @@ blocklist_imports() {
-e '[[:space:]]"reflect"$'\
-e '[[:space:]]"sort"$'\
-e '[[:space:]]"unsafe"$'\
+ -e '[[:space:]]"golang.org/x/exp/slices"$'\
-e '[[:space:]]"golang.org/x/net/context"$'\
-n\
-- '*.go'\
@@ -175,65 +150,11 @@ run_linter "$GO" vet ./... "$dnssrvmod"
run_linter govulncheck ./... "$dnssrvmod"
-# NOTE: For AdGuard DNS, ignore the generated protobuf file.
+# NOTE: For AdGuard DNS, ignore the generated protobuf files.
run_linter gocyclo --ignore '\.pb\.go$' --over 10 .
-# TODO(a.garipov): Enable 10 for all.
-#
-# TODO(a.garipov): Redo once https://github.com/uudashr/gocognit/issues/22 is
-# fixed.
-gocognit_paths="\
-./internal/metrics 19
-./internal/filter/internal/composite/ 15
-./internal/ecscache 15
-./internal/backend/ 14
-./internal/dnsserver/ 14
-./internal/dnsmsg/ 11
-./internal/filter/hashprefix/ 11
-./internal/agd/ 10
-./internal/agdhttp/ 10
-./internal/agdio/ 10
-./internal/agdnet/ 10
-./internal/agdprotobuf/ 10
-./internal/agdtest/ 10
-./internal/agdtime/ 10
-./internal/billstat/ 10
-./internal/bindtodevice/ 10
-./internal/cmd/ 10
-./internal/connlimiter/ 10
-./internal/consul/ 10
-./internal/debugsvc/ 10
-./internal/dnscheck/ 10
-./internal/dnsdb/ 10
-./internal/dnsserver/cache/ 10
-./internal/dnsserver/dnsservertest/ 10
-./internal/dnsserver/forward/ 10
-./internal/dnsserver/netext/ 10
-./internal/dnsserver/pool/ 10
-./internal/dnsserver/prometheus/ 10
-./internal/dnsserver/querylog/ 10
-./internal/dnsserver/ratelimit/ 10
-./internal/dnssvc/ 10
-./internal/errcoll/ 10
-./internal/filter/internal/custom/ 10
-./internal/filter/internal/filtertest/ 10
-./internal/filter/internal/resultcache/ 10
-./internal/filter/internal/rulelist/ 10
-./internal/filter/internal/safesearch/ 10
-./internal/filter/internal/serviceblock/ 10
-./internal/geoip/ 10
-./internal/optlog/ 10
-./internal/profiledb/internal/profiledbtest/ 10
-./internal/querylog/ 10
-./internal/rulestat/ 10
-./internal/tools/ 10
-./internal/websvc/ 10"
-readonly gocognit_paths
-
-echo "$gocognit_paths" | while read -r path max
-do
- run_linter gocognit --over="$max" "$path"
-done
+# NOTE: For AdGuard DNS, ignore the generated protobuf files.
+run_linter gocognit --ignore '\.pb\.go$' --over='10' .
run_linter ineffassign ./... "$dnssrvmod"
diff --git a/scripts/make/helper.sh b/scripts/make/helper.sh
index 6d7fe77..9c4ff88 100644
--- a/scripts/make/helper.sh
+++ b/scripts/make/helper.sh
@@ -8,7 +8,7 @@
# This comment is used to simplify checking local copies of the script. Bump
# this number every time a remarkable change is made to this script.
#
-# AdGuard-Project-Version: 2
+# AdGuard-Project-Version: 3
@@ -47,7 +47,7 @@ trap not_found EXIT
run_linter() (
set +e
- if [ "$VERBOSE" -lt '2' ]
+ if [ "${VERBOSE:-0}" -lt '2' ]
then
set +x
fi
diff --git a/scripts/make/txt-lint.sh b/scripts/make/txt-lint.sh
index 35d0394..e878c81 100644
--- a/scripts/make/txt-lint.sh
+++ b/scripts/make/txt-lint.sh
@@ -3,7 +3,7 @@
# This comment is used to simplify checking local copies of the script. Bump
# this number every time a remarkable change is made to this script.
#
-# AdGuard-Project-Version: 4
+# AdGuard-Project-Version: 5
verbose="${VERBOSE:-0}"
readonly verbose
@@ -31,7 +31,7 @@ set -f -u
# trailing_newlines is a simple check that makes sure that all plain-text files
# have a trailing newlines to make sure that all tools work correctly with them.
-trailing_newlines() {
+trailing_newlines() (
nl="$( printf "\n" )"
readonly nl
@@ -40,12 +40,13 @@ trailing_newlines() {
':!*.mmdb'\
| while read -r f
do
- if [ "$( tail -c -1 "$f" )" != "$nl" ]
+ final_byte="$( tail -c -1 "$f" )"
+ if [ "$final_byte" != "$nl" ]
then
printf '%s: must have a trailing newline\n' "$f"
fi
done
-}
+)
# trailing_whitespace is a simple check that makes sure that there are no
# trailing whitespace in plain-text files.
diff --git a/staticcheck.conf b/staticcheck.conf
index 7f25543..3e804aa 100644
--- a/staticcheck.conf
+++ b/staticcheck.conf
@@ -1,3 +1,8 @@
+# This comment is used to simplify checking local copies of the staticcheck
+# configuration. Bump this number every time a significant change is made to
+# this file.
+#
+# AdGuard-Project-Version: 1
checks = ["all"]
initialisms = [
# See https://github.com/dominikh/go-tools/blob/master/config/config.go.
@@ -13,11 +18,14 @@ initialisms = [
, "EDNS"
, "MX"
, "QUIC"
+, "RA"
, "RRSIG"
, "SDNS"
+, "SLAAC"
, "SOA"
, "SVCB"
, "TLD"
+, "WHOIS"
]
dot_import_whitelist = []
http_status_code_whitelist = []