Sync v2.5.1

This commit is contained in:
Andrey Meshkov 2024-01-04 19:22:32 +03:00
parent cfb4caf935
commit 150f2d733f
291 changed files with 20054 additions and 7857 deletions

View File

@ -11,9 +11,245 @@ 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
* 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:
@ -31,9 +267,10 @@ 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
* 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
* 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
```

View File

@ -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

View File

@ -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'
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

View File

@ -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`.
* <a href="#ratelimit-back_off_period" id="ratelimit-back_off_period" name="ratelimit-back_off_period">`back_off_period`</a>:
* <a href="#ratelimit-backoff_period" id="ratelimit-backoff_period" name="ratelimit-backoff_period">`backoff_period`</a>:
The time during which to count the number of requests that a client has sent
over the RPS.
**Example:** `10m`.
* <a href="#ratelimit-back_off_duration" id="ratelimit-back_off_duration" name="ratelimit-back_off_duration">`back_off_duration`</a>:
* <a href="#ratelimit-backoff_duration" id="ratelimit-backoff_duration" name="ratelimit-backoff_duration">`backoff_duration`</a>:
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.
* <a href="#ratelimit-back_off_count" id="ratelimit-back_off_count" name="ratelimit-back_off_count">`back_off_count`</a>:
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.
* <a href="#ratelimit-backoff_count" id="ratelimit-backoff_count" name="ratelimit-backoff_count">`backoff_count`</a>:
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`.
### <a href="#ratelimit-connection_limit" id="ratelimit-connection_limit" name="ratelimit-connection_limit">Stream connection limit</a>
@ -218,6 +219,35 @@ The `connection_limit` object has the following properties:
See also [notes on these parameters](#recommended-connection_limit).
### <a href="#ratelimit-quic" id="ratelimit-quic" name="ratelimit-quic">QUIC rate limiting</a>
The `quic` object has the following properties:
* <a href="#ratelimit-quic-enabled" id="ratelimit-quic-enabled" name="ratelimit-quic-enabled">`enabled`</a>:
Whether or not the QUIC connections rate limiting should be enforced.
**Example:** `true`.
* <a href="#ratelimit-quic-max_streams_per_peer" id="ratelimit-quic-max_streams_per_peer" name="ratelimit-quic-max_streams_per_peer">`max_streams_per_peer`</a>:
The maximum number of concurrent streams that a peer is allowed to open.
**Example:** `1000`.
### <a href="#ratelimit-tcp" id="ratelimit-tcp" name="ratelimit-tcp">TCP rate limiting</a>
The `tcp` object has the following properties:
* <a href="#ratelimit-tcp-enabled" id="ratelimit-tcp-enabled" name="ratelimit-tcp-enabled">`enabled`</a>:
Whether or not the TCP rate limiting should be enforced.
**Example:** `true`.
* <a href="#ratelimit-tcp-max_pipeline_count" id="ratelimit-tcp-max_pipeline_count" name="ratelimit-tcp-max_pipeline_count">`max_pipeline_count`</a>:
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:
* <a href="#upstream-server" id="upstream-server" name="upstream-server">`server`</a>:
The URL of the main upstream server, in the `[scheme://]ip:port` format.
* <a href="#upstream-servers" id="upstream-servers" name="upstream-servers">`servers`</a>:
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).
* <a href="#upstream-timeout" id="upstream-timeout" name="upstream-timeout">`timeout`</a>:
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
```
* <a href="#upstream-fallback" id="upstream-fallback" name="upstream-fallback">`fallback`</a>:
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']`.
* <a href="#upstream-fallback-servers" id="upstream-fallback-servers" name="upstream-fallback-servers">`servers`</a>:
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.
## <a href="#dns" id="dns" name="dns">DNS</a>
The `dns` object has the following properties:
* <a href="#dns-read_timeout" id="dns-read_timeout" name="dns-read_timeout">`read_timeout`</a>:
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`.
* <a href="#dns-tcp_idle_timeout" id="dns-tcp_idle_timeout" name="dns-tcp_idle_timeout">`tcp_idle_timeout`</a>:
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`.
* <a href="#dns-write_timeout" id="dns-write_timeout" name="dns-write_timeout">`write_timeout`</a>:
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`.
* <a href="#dns-handle_timeout" id="dns-handle_timeout" name="dns-handle_timeout">`handle_timeout`</a>:
The timeout for the entire handling of a single query, as a human-readable
duration.
**Example:** `1s`.
* <a href="#dns-max_udp_response_size" id="dns-max_udp_response_size" name="dns-max_udp_response_size">`max_udp_response_size`</a>:
The maximum size of DNS response over UDP protocol.
**Example:** `1024B`.
## <a href="#dnsdb" id="dnsdb" name="dnsdb">DNSDB</a>
The `DNSDB` object has the following properties:
The `dnsdb` object has the following properties:
* <a href="#dnsdb-enabled" id="dnsdb-enabled" name="dnsdb-enabled">`enabled`</a>:
If true, the DNSDB memory buffer is enabled.
@ -382,6 +468,13 @@ The `backend` object has the following properties:
**Example:** `24h`.
* <a href="#backend-full_refresh_retry_interval" id="backend-full_refresh_retry_interval" name="backend-full_refresh_retry_interval">`full_refresh_retry_interval`</a>:
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`.
* <a href="#backend-bill_stat_interval" id="backend-bill_stat_interval" name="backend-bill_stat_interval">`bill_stat_interval`</a>:
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:
* <a href="#filters-refresh_interval" id="filters-refresh_interval" name="filters-refresh_interval">`refresh_interval`</a>:
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`.

View File

@ -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`.
</p>
<ul>
<li>
<code>../internal/agd/country_generate.go</code>;
<code>../internal/geoip/country_generate.go</code>;
</li>
<li>
<code>../internal/geoip/asntops_generate.go</code>;
</li>
<li>
<code>../internal/ecscache/ecsblockilist_generate.go</code>;
</li>
<li>
<code>../internal/profiledb/internal/filecachepb/filecache.pb.go</code>.
</li>
@ -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' \

View File

@ -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.
## <a href="#BILLSTAT_URL" id="BILLSTAT_URL" name="BILLSTAT_URL">`BILLSTAT_URL`</a>
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
## <a href="#PROFILES_URL" id="PROFILES_URL" name="PROFILES_URL">`PROFILES_URL`</a>
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

View File

@ -29,26 +29,8 @@ document should set the `Server` header in their replies.
## <a href="#backend-billstat" id="backend-billstat" name="backend-billstat">Backend Billing Statistics</a>
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:
## <a href="#backend-profiles" id="backend-profiles" name="backend-profiles">Backend Profiles Service</a>
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

View File

@ -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.
* <a href="#properties-ip" id="properties-ip" name="properties-ip">`ip`</a>:
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.

52
go.mod
View File

@ -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
)

146
go.sum
View File

@ -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=

View File

@ -1,4 +1,4 @@
go 1.20
go 1.21.5
use (
.

View File

@ -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=

View File

@ -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()
}

View File

@ -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)
})

77
internal/access/engine.go Normal file
View File

@ -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)
}

View File

@ -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()
}

137
internal/access/profile.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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.

View File

@ -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
}

152
internal/agd/server_test.go Normal file
View File

@ -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())
}

View File

@ -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 }

View File

@ -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)
}

View File

@ -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",

View File

@ -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,
}
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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")
)

View File

@ -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 }

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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{}

View File

@ -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

View File

@ -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
rand *rand.Rand
refr Refresher
errColl ErrorCollector
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:
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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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
}

View File

@ -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))
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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^"
]
}
]
}

View File

@ -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,
},

View File

@ -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";
@ -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;
}

View File

@ -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

View File

@ -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...)
}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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{
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 {

View File

@ -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

View File

@ -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()

View File

@ -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"
@ -22,8 +23,8 @@ type sig = struct{}
const (
devID = "dev1234"
proto = agd.ProtoDoH
clientCtry = agd.CountryAD
clientASN agd.ASN = 42
clientCtry = geoip.CountryAD
clientASN geoip.ASN = 42
)
func TestRuntimeRecorder_success(t *testing.T) {

View File

@ -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")
)

View File

@ -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"

View File

@ -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

View File

@ -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) {

View File

@ -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:

View File

@ -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
}

View File

@ -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,

View File

@ -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
}

View File

@ -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.

View File

@ -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
}
@ -110,16 +111,8 @@ func (m *Manager) newInterfaceListener(
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
}),
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.
//

View File

@ -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) {

View File

@ -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,
)
}

View File

@ -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 }

View File

@ -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()

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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",

View File

@ -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
}

View File

@ -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

58
internal/cmd/dns.go Normal file
View File

@ -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
}
}

View File

@ -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{}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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{
dnsSrv := &agd.Server{
Name: name,
BindData: bindData,
Protocol: agd.ProtoDNS,
ReadTimeout: dnsConf.ReadTimeout.Duration,
WriteTimeout: dnsConf.WriteTimeout.Duration,
LinkedIPEnabled: srv.LinkedIPEnabled,
})
case srvProtoDNSCrypt:
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(),
})
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -18,9 +18,9 @@ import (
type limitConn struct {
net.Conn
serverInfo *dnsserver.ServerInfo
decrement func()
start time.Time
serverInfo dnsserver.ServerInfo
isClosed atomic.Bool
}

View File

@ -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()

View File

@ -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,

View File

@ -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
}

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