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,11 +11,247 @@ The format is **not** based on [Keep a Changelog][kec], since the project
## AGDNS-1761 / Build 702
* The property `upstream` has been modified. Its property `timeout` has been
replaced with the new property `servers.timeout` for each server in the
`servers` list. Concomitantly the `fallback.timeout` has been replaced with
`fallback.servers.timeout` for each fallback server. The `fallback.servers`
now supports not only the addresses of the servers, but URLs in the
`[scheme://]ip:port` format like it's done with the main servers. So replace
this:
```yaml
upstream:
# …
servers:
- 'tcp://1.1.1.1:53'
- '127.0.0.1:5358'
timeout: 2s
fallback:
servers:
- 8.8.4.4:53
timeout: 1s
```
with this:
```yaml
upstream:
# …
servers:
- address: 'tcp://1.1.1.1:53'
timeout: 2s
- address: '127.0.0.1:5358'
timeout: 2s
fallback:
servers:
- address: '8.8.4.4:53'
timeout: 1s
```
Adjust the value and add new ones, if necessary.
## AGDNS-698 / Build 701
* The object `dns` has new properties: `read_timeout`, `tcp_idle_timeout`, and
`write_timeout`. So replace this:
```yaml
dns:
max_udp_response_size: 1024B
```
with this:
```yaml
dns:
read_timeout: 2s
tcp_idle_timeout: 30s
write_timeout: 2s
handle_timeout: 1s
max_udp_response_size: 1024B
```
The values in the example are previous defaults.
## AGDNS-1751 / Build 691
* The property `upstream.server` has been removed. Its former content is
moved to the newly added property `servers`, which now extended to contain
a list of URLs of main upstream servers. So replace this:
```yaml
upstream:
# …
server: `8.8.8.8:53`
```
with this:
```yaml
upstream:
# …
servers:
- `8.8.8.8:53`
```
Adjust the value and add new ones, if necessary.
## AGDNS-1759 / Build 684
* The object `backend` has a new property, `full_refresh_retry_interval`. So
replace this:
```yaml
backend:
# …
full_refresh_interval: 24h
```
with this:
```yaml
backend:
# …
full_refresh_interval: 24h
full_refresh_retry_interval: 1h
```
Adjust the value, if necessary.
## AGDNS-1744 / Build 681
* Metric `forward_request_total` has a new label `network`. This label
describes the network type (`tcp` or `udp`), over which an upstream has
finished processing request.
## AGDNS-1738 / Build 678
* Object `dns` has a new property, describing maximum size of DNS response
over UDP protocol.
```yaml
dns:
max_udp_response_size: 1024B
handle_timeout: 1s
```
## AGDNS-1735 / Build 677
* The property `upstream.fallback` has been changed. Its former content is
moved to the newly added property `servers`. The new property `timeout`,
which describes query timeout to fallback servers, was added. So replace
this:
```yaml
upstream:
fallback:
- 1.1.1.1:53
- 8.8.8.8:53
```
with this:
```yaml
upstream:
fallback:
servers:
- 1.1.1.1:53
- 8.8.8.8:53
timeout: 1s
```
Adjust the new values, if necessary. Note that the query timeout to fallback
servers was previously defined with `upstream.timeout` property, which now
describes the query timeout to the primary servers only.
## AGDNS-1178 / Build 676
* The new object `dns` has been added:
```yaml
dns:
handle_timeout: 1s
```
## AGDNS-1620 / Build 673
* Object `ratelimit` has two new properties: `quic` and `tcp`. They configure
QUIC and TCP connection limits. Example configuration:
```yaml
ratelimit:
# …
quic:
enabled: true
max_streams_per_peer: 100
tcp:
enabled: true
max_pipeline_count: 100
```
## AGDNS-1684 / Build 661
* Profile's file cache version was incremented. The new field `access` has
been added.
## AGDNS-1664 / Build 636
* The environment variables `BILLSTAT_URL` and `PROFILES_URL` no longer
support HTTP(s) endpoints. Use GRPC(S) instead.
## AGDNS-1667 / Build 633
* `ratelimit` configuration properties `back_off_count`, `back_off_duration`
and `back_off_period` have been renamed to `backoff_count`,
`backoff_duration` and `backoff_period`. So replace this:
```yaml
ratelimit:
back_off_period: 10m
back_off_count: 1000
back_off_duration: 30m
```
with this:
```yaml
ratelimit:
backoff_period: 10m
backoff_count: 1000
backoff_duration: 30m
```
## AGDNS-1607 / Build 617 ## 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. to block requests, and a lists of client subnets to block access from.
Example configuration: Example configuration:
```yaml ```yaml
access: access:
@ -31,10 +267,11 @@ The format is **not** based on [Keep a Changelog][kec], since the project
## AGDNS-1619 / Build 611 ## 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. billing statistics upload.
* The environment variable `BILLSTAT_URL`, which describes the endpoint for
backend billing statistics uploader API, now supports GRPC endpoints. * 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` * The optional property `bind_interfaces` of `server_groups.*.servers`
objects has been changed, property `subnet` is now an array and has been 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 ```yaml
bind_interfaces: bind_interfaces:
@ -98,6 +335,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project
## AGDNS-1580 / Build 562 ## AGDNS-1580 / Build 562
* The environment variable `DNSDB_PATH` has been removed. * The environment variable `DNSDB_PATH` has been removed.
* New configuration `dnsdb` has been added, it has an enabled/disabled flag * 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 and the property `max_size` which describes the maximum amount of records in
the in-memory buffer. Example configuration: 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 identifiers, grouped by endpoint identifier and known server names. All
unknown server names are grouped in `other` label: unknown server names are grouped in `other` label:
``` ```none
# TYPE dns_tls_handshake_total counter # 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 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 ) BRANCH = $$( git rev-parse --abbrev-ref HEAD )
GOAMD64 = v1 GOAMD64 = v1
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOTOOLCHAIN = go1.21.5
RACE = 0 RACE = 0
REVISION = $$( git rev-parse --short HEAD ) REVISION = $$( git rev-parse --short HEAD )
VERSION = 0 VERSION = 0
@ -32,6 +33,7 @@ ENV = env\
GO="$(GO.MACRO)"\ GO="$(GO.MACRO)"\
GOAMD64='$(GOAMD64)'\ GOAMD64='$(GOAMD64)'\
GOPROXY='$(GOPROXY)'\ GOPROXY='$(GOPROXY)'\
GOTOOLCHAIN='$(GOTOOLCHAIN)'\
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\ PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
RACE='$(RACE)'\ RACE='$(RACE)'\
REVISION="$(REVISION)"\ REVISION="$(REVISION)"\
@ -51,6 +53,7 @@ test: go-test
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.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-gen: ; $(ENV) "$(SHELL)" ./scripts/make/go-gen.sh
go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh
go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.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 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 sync-github: ; $(ENV) "$(SHELL)" ./scripts/make/github-sync.sh

View File

@ -24,15 +24,13 @@ ratelimit:
subnet_key_len: 48 subnet_key_len: 48
# The time during which to count the number of times a client has hit the # The time during which to count the number of times a client has hit the
# rate limit for a back off. # rate limit for a back off.
# backoff_period: 10m
# TODO(a.garipov): Rename to "backoff_period" along with others.
back_off_period: 10m
# How many times a client hits the rate limit before being held in the back # How many times a client hits the rate limit before being held in the back
# off. # off.
back_off_count: 1000 backoff_count: 1000
# How much a client that has hit the rate limit too often stays in the back # How much a client that has hit the rate limit too often stays in the back
# off. # off.
back_off_duration: 30m backoff_duration: 30m
# Configuration for the allowlist. # Configuration for the allowlist.
allowlist: allowlist:
@ -52,6 +50,19 @@ ratelimit:
stop: 1000 stop: 1000
resume: 800 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 settings.
access: access:
# Domains to block. # Domains to block.
@ -78,11 +89,17 @@ cache:
# DNS upstream configuration. # DNS upstream configuration.
upstream: upstream:
server: '8.8.8.8:53' servers:
timeout: 2s - address: 'tcp://1.1.1.1:53'
timeout: 2s
- address: '8.8.4.4:53'
timeout: 2s
fallback: fallback:
- 1.1.1.1:53 servers:
- 8.8.8.8:53 - address: '1.1.1.1:53'
timeout: 1s
- address: '8.8.8.8:53'
timeout: 1s
healthcheck: healthcheck:
enabled: true enabled: true
interval: 2s interval: 2s
@ -90,6 +107,25 @@ upstream:
backoff_duration: 30s backoff_duration: 30s
domain_template: '${RANDOM}.neverssl.com' 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 configuration.
dnsdb: dnsdb:
enabled: true enabled: true
@ -106,6 +142,9 @@ backend:
refresh_interval: 15s refresh_interval: 15s
# How often AdGuard DNS performs full synchronization. # How often AdGuard DNS performs full synchronization.
full_refresh_interval: 24h 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. # How often AdGuard DNS sends the billing statistics to the backend.
bill_stat_interval: 15s bill_stat_interval: 15s

View File

@ -15,6 +15,7 @@ configuration file with comments.
* [Cache](#cache) * [Cache](#cache)
* [Upstream](#upstream) * [Upstream](#upstream)
* [Healthcheck](#upstream-healthcheck) * [Healthcheck](#upstream-healthcheck)
* [Common DNS settings](#dns)
* [DNSDB](#dnsdb) * [DNSDB](#dnsdb)
* [Backend](#backend) * [Backend](#backend)
* [Query log](#query_log) * [Query log](#query_log)
@ -130,13 +131,13 @@ The `ratelimit` object has the following properties:
**Example:** `1KB`. **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 The time during which to count the number of requests that a client has sent
over the RPS. over the RPS.
**Example:** `10m`. **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. How long a client that has hit the RPS too often stays in the backoff state.
**Example:** `30m`. **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 The `ipv6` configuration object has the same properties as the `ipv4` one
above. above.
* <a href="#ratelimit-back_off_count" id="ratelimit-back_off_count" name="ratelimit-back_off_count">`back_off_count`</a>: * <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 Maximum number of requests a client can make above the RPS within a
a `back_off_period`. When a client exceeds this limit, requests aren't `backoff_period`. When a client exceeds this limit, requests aren't allowed
allowed from client's subnet until `back_off_duration` ends. from client's subnet until `backoff_duration` ends.
**Example:** `1000`. **Example:** `1000`.
@ -188,11 +189,11 @@ The `ratelimit` object has the following properties:
**Example:** `30s`. **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 `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 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 (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> ### <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). 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 [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: The `upstream` object has the following properties:
* <a href="#upstream-server" id="upstream-server" name="upstream-server">`server`</a>: * <a href="#upstream-servers" id="upstream-servers" name="upstream-servers">`servers`</a>:
The URL of the main upstream server, in the `[scheme://]ip:port` format. 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). ```yaml
- `tcp://1.1.1.1:53`: regular DNS (over TCP). 'servers':
- `udp://1.1.1.1:53`: regular DNS (over UDP). # Regular DNS (over UDP with TCP fallback).
- address: '8.8.8.8:53'
* <a href="#upstream-timeout" id="upstream-timeout" name="upstream-timeout">`timeout`</a>: timeout: 2s
Timeout for all outgoing DNS requests, as a human-readable duration. # Regular DNS (over TCP).
- address: 'tcp://1.1.1.1:53'
**Example:** `2s`. 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>: * <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` Fallback servers configuration. It has the following properties:
format. These are use used in case a network error occurs while requesting
the main upstream server.
**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 * `healthcheck`: Healthcheck configuration. See
[below](#upstream-healthcheck). [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> ## <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>: * <a href="#dnsdb-enabled" id="dnsdb-enabled" name="dnsdb-enabled">`enabled`</a>:
If true, the DNSDB memory buffer is enabled. If true, the DNSDB memory buffer is enabled.
@ -382,6 +468,13 @@ The `backend` object has the following properties:
**Example:** `24h`. **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>: * <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 How often AdGuard DNS sends the billing statistics to the backend, as
a human-readable duration. 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 The optional listen addresses and optional TLS configuration for the web
service in addition to the ones in the DNS-over-HTTPS handlers. The 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 `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 settings](#server_groups-*-tls). In the special case of `GET /robots.txt`
special response is served; this response could be overwritten with static requests, a special response is served; this response could be overwritten
content. with static content.
**Property example:** **Property example:**
@ -604,7 +697,6 @@ The optional `web` object has the following properties:
**Example:** `30s`. **Example:** `30s`.
[http-block-pages]: http.md#block-pages [http-block-pages]: http.md#block-pages
[http-dnscheck-test]: http.md#dhscheck-test
[http-linked-ip-proxy]: http.md#linked-ip-proxy [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>: * <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, How often AdGuard DNS refreshes the rule-list filters from the filter index,
as well as the blocked services list from the [blocked list as well as the blocked services list from the [blocked list
index][env-blocked_services)]. index][env-blocked_services].
**Example:** `1h`. **Example:** `1h`.

View File

@ -13,7 +13,7 @@
Development is supported on Linux and macOS (aka Darwin) systems. 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. 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> </p>
<ul> <ul>
<li> <li>
<code>../internal/agd/country_generate.go</code>; <code>../internal/geoip/country_generate.go</code>;
</li> </li>
<li> <li>
<code>../internal/geoip/asntops_generate.go</code>; <code>../internal/geoip/asntops_generate.go</code>;
</li> </li>
<li>
<code>../internal/ecscache/ecsblockilist_generate.go</code>;
</li>
<li> <li>
<code>../internal/profiledb/internal/filecachepb/filecache.pb.go</code>. <code>../internal/profiledb/internal/filecachepb/filecache.pb.go</code>.
</li> </li>
@ -207,7 +210,7 @@ rm -f -r ./test/cache/
mkdir ./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-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/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]. 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 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 1024 to some other ports. Otherwise, `sudo` or `doas` is required to run
`AdGuardDNS`. `AdGuardDNS`.
@ -250,7 +256,7 @@ If you're using an OS different from Linux, you also need to make these changes:
```sh ```sh
env \ env \
ADULT_BLOCKING_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/adult_blocking.txt' \ 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' \ 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' \ CONSUL_ALLOWLIST_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/consul_allowlist.json' \
CONFIG_PATH='./config.yaml' \ CONFIG_PATH='./config.yaml' \
@ -258,10 +264,10 @@ env \
FILTER_CACHE_PATH='./test/cache' \ FILTER_CACHE_PATH='./test/cache' \
NEW_REG_DOMAINS_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/nrd.txt' \ NEW_REG_DOMAINS_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/nrd.txt' \
PROFILES_CACHE_PATH='./test/profilecache.pb' \ 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' \ 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' \ 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' \ GEOIP_COUNTRY_PATH='./test/GeoIP2-City-Test.mmdb' \
QUERYLOG_PATH='./test/cache/querylog.jsonl' \ QUERYLOG_PATH='./test/cache/querylog.jsonl' \
LINKED_IP_TARGET_URL='https://httpbin.agrd.workers.dev/anything' \ 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) * [`ADULT_BLOCKING_URL`](#ADULT_BLOCKING_URL)
* [`BILLSTAT_URL`](#BILLSTAT_URL) * [`BILLSTAT_URL`](#BILLSTAT_URL)
* [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL) * [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL)
* [`CONFIG_PATH`](#CONFIG_PATH)
* [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL) * [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL)
* [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL) * [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL)
* [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_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_CACHE_PATH`](#FILTER_CACHE_PATH)
* [`FILTER_INDEX_URL`](#FILTER_INDEX_URL)
* [`GENERAL_SAFE_SEARCH_URL`](#GENERAL_SAFE_SEARCH_URL) * [`GENERAL_SAFE_SEARCH_URL`](#GENERAL_SAFE_SEARCH_URL)
* [`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`](#GEOIP_ASN_PATH) * [`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`](#GEOIP_ASN_PATH)
* [`LINKED_IP_TARGET_URL`](#LINKED_IP_TARGET_URL) * [`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_CACHE_PATH`](#PROFILES_CACHE_PATH)
* [`PROFILES_URL`](#PROFILES_URL) * [`PROFILES_URL`](#PROFILES_URL)
* [`QUERYLOG_PATH`](#QUERYLOG_PATH) * [`QUERYLOG_PATH`](#QUERYLOG_PATH)
* [`RESEARCH_METRICS`](#RESEARCH_METRICS)
* [`RESEARCH_LOGS`](#RESEARCH_LOGS) * [`RESEARCH_LOGS`](#RESEARCH_LOGS)
* [`RESEARCH_METRICS`](#RESEARCH_METRICS)
* [`RULESTAT_URL`](#RULESTAT_URL) * [`RULESTAT_URL`](#RULESTAT_URL)
* [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL) * [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL)
* [`SENTRY_DSN`](#SENTRY_DSN) * [`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> ## <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 The base backend URL for backend billing statistics uploader API. Supports
and GRPC protocols. In case of HTTP the backend endpoint must reply with a 200 GRPC (`grpc://` and`grpcs://`) URLs. See the [external HTTP API requirements
status code on success. See the [external HTTP API requirements
section][ext-billstat]. section][ext-billstat].
**Default:** No default value, the variable is **required.** **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> ## <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://`) The base backend URL for profiles API. Supports GRPC (`grpc://` and`grpcs://`)
and GRPC (`grpc://` and `grpcs://`) URLs. In case of HTTP the backend endpoint URLs. See the [external API requirements section][ext-profiles].
must reply with a 200 status code on success. See the [external API
requirements section][ext-profiles].
**Default:** No default value, the variable is **required.** **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> ## <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 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 variable points. Supports `grpc(s)` URLs. The service must correspond to
protocol, the service must correspond to `./internal/backendpb/backend.proto`. `./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
}
]
}
```
[env-billstat_url]: environment.md#BILLSTAT_URL [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> ## <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 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 variable points. Supports `grpc(s)` URLs. The service must correspond to
protocol, the service must correspond to `./internal/backendpb/backend.proto`. `./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^"
]
}
]
}
```
[env-profiles_url]: environment.md#PROFILES_URL [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. 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 See also [file `internal/querylog/entry.go`][file-entry.go] for an explanation
of the properties, their names, and mnemonics. of the properties, their names, and mnemonics.

52
go.mod
View File

@ -1,32 +1,32 @@
module github.com/AdguardTeam/AdGuardDNS module github.com/AdguardTeam/AdGuardDNS
go 1.20 go 1.21.5
require ( require (
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0 github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
github.com/AdguardTeam/golibs v0.15.0 github.com/AdguardTeam/golibs v0.18.1
github.com/AdguardTeam/urlfilter v0.17.0 github.com/AdguardTeam/urlfilter v0.17.2
github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/ameshkov/dnscrypt/v2 v2.2.7
github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc
github.com/bluele/gcache v0.0.2 github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/caarlos0/env/v7 v7.1.0 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/google/renameio/v2 v2.0.0
github.com/miekg/dns v1.1.55 github.com/miekg/dns v1.1.56
github.com/oschwald/maxminddb-golang v1.10.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.15.1 github.com/prometheus/client_golang v1.17.0
github.com/prometheus/client_model v0.4.0 github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.44.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 github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.14.0 golang.org/x/net v0.17.0
golang.org/x/sys v0.11.0 golang.org/x/sys v0.13.0
golang.org/x/time v0.3.0 golang.org/x/time v0.3.0
google.golang.org/grpc v1.56.2 google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.30.0 google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
@ -39,21 +39,21 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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/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/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/onsi/ginkgo/v2 v2.13.0 // indirect
github.com/panjf2000/ants/v2 v2.7.5 // indirect github.com/panjf2000/ants/v2 v2.8.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
golang.org/x/crypto v0.12.0 // indirect go.uber.org/mock v0.3.0 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/text v0.12.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // 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 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.18.1 h1:6u0fvrIj2qjUsRdbIGJ9AR0g5QRSWdKIo/DYl3tp5aM=
github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI= github.com/AdguardTeam/golibs v0.18.1/go.mod h1:DKhCIXHcUYtBhU8ibTLKh1paUL96n5zhQBlx763sj+U=
github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow= github.com/AdguardTeam/urlfilter v0.17.2 h1:xTntfr1UWah8m6wwoXJmFgplFk/+kL/hDu204ptrM1U=
github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU= 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 h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 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= 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-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 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= 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.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= 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 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 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 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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 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/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= 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 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 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 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 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 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.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= 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 h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 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 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 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 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 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 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.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 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 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= 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.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= 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.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= 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 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 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 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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.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.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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 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/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= 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= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 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/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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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 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 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,4 +1,4 @@
go 1.20 go 1.21.5
use ( 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.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= 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.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/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= 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 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= 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.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.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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= 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 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= 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.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/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 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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= 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 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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-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 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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= 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 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= 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.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 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 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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= 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/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 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 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 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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= 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 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 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 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.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 h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 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 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 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/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 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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= 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-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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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/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 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.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 h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 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= 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-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-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= 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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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 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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= 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 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 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4=
github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I=
github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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.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 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 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= 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.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 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 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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= 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/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-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 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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 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 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 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI= 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/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 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= 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/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= 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/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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= 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/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.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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= 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/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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= 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= 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/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 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ=
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= 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 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.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.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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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.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.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 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-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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/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.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 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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= 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-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.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.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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.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.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/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.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 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 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 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.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.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.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.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/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-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.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-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-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-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.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.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3/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.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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= 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-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-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-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 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.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= 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.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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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/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-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/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY= 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= 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= 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" "github.com/AdguardTeam/urlfilter/filterlist"
) )
// unit is a convenient alias for struct{} // blocklistFilterID ia the ID for the urlfilter rule list to use in the
type unit = struct{} // 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. // Interface is the access manager interface.
type Interface interface { type Interface interface {
@ -24,24 +26,24 @@ type Interface interface {
IsBlockedIP(ip netip.Addr) (blocked bool, rule string) IsBlockedIP(ip netip.Addr) (blocked bool, rule string)
} }
// type check // Global controls IP and client blocking that takes place before all other
var _ Interface = (*Manager)(nil) // processing. Global is safe for concurrent use.
type Global struct {
// Manager controls IP and client blocking that takes place before all blockedIPs map[netip.Addr]string
// other processing. An Manager is safe for concurrent use.
type Manager struct {
blockedIPs map[netip.Addr]unit
blockedHostsEng *urlfilter.DNSEngine blockedHostsEng *urlfilter.DNSEngine
blockedNets []netip.Prefix blockedNets []netip.Prefix
} }
// New create an Manager. The parameters assumed to be valid. // NewGlobal create a new Global from provided parameters.
func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) { func NewGlobal(blockedDomains, blockedSubnets []string) (g *Global, err error) {
am = &Manager{ g = &Global{
blockedIPs: map[netip.Addr]unit{}, 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{} b := &strings.Builder{}
for _, h := range blockedDomains { for _, h := range blockedDomains {
@ -50,7 +52,7 @@ func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) {
lists := []filterlist.RuleList{ lists := []filterlist.RuleList{
&filterlist.StringRuleList{ &filterlist.StringRuleList{
ID: 0, ID: blocklistFilterID,
RulesText: b.String(), RulesText: b.String(),
IgnoreCosmetic: true, IgnoreCosmetic: true,
}, },
@ -61,44 +63,53 @@ func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) {
return nil, fmt.Errorf("adding blocked hosts: %w", err) 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 // processAccessList is a helper for processing a list of strings, each of them
// assumed be a valid IP address or a valid CIDR. // could be an IP address or a CIDR.
func processAccessList(strs []string, ips map[netip.Addr]unit, nets *[]netip.Prefix) { func processAccessList(strs []string, ips map[netip.Addr]string, nets *[]netip.Prefix) (err error) {
for _, s := range strs { for _, s := range strs {
var err error
var ip netip.Addr var ip netip.Addr
var ipnet netip.Prefix var ipnet netip.Prefix
if ip, err = netip.ParseAddr(s); err == nil { if ip, err = netip.ParseAddr(s); err == nil {
ips[ip] = unit{} ips[ip] = ip.String()
} else if ipnet, err = netip.ParsePrefix(s); err == nil { } else if ipnet, err = netip.ParsePrefix(s); err == nil {
*nets = append(*nets, ipnet) *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. // type check
func (am *Manager) IsBlockedHost(host string, qt uint16) (blocked bool) { var _ Interface = (*Global)(nil)
_, blocked = am.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{
// 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, Hostname: host,
DNSType: qt, DNSType: qt,
}) })
return blocked if matched && res.NetworkRule != nil {
} return !res.NetworkRule.Whitelist
// 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()
} }
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) { if ipnet.Contains(ip) {
return true, ipnet.String() return true, ipnet.String()
} }

View File

@ -10,11 +10,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestAccessManager_IsBlockedHost(t *testing.T) { func TestGlobal_IsBlockedHost(t *testing.T) {
am, err := access.New([]string{ global, err := access.NewGlobal([]string{
"block.test", "block.test",
"UPPERCASE.test", "UPPERCASE.test",
"||block_aaaa.test^$dnstype=AAAA", "||block_aaaa.test^$dnstype=AAAA",
"||allowlist.test^",
"@@||allow.allowlist.test^",
}, []string{}) }, []string{})
require.NoError(t, err) require.NoError(t, err)
@ -53,18 +55,28 @@ func TestAccessManager_IsBlockedHost(t *testing.T) {
name: "block_qt", name: "block_qt",
host: "block_aaaa.test", host: "block_aaaa.test",
qt: dns.TypeAAAA, 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 { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { 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) tc.want(t, blocked)
}) })
} }
} }
func TestAccessManager_IsBlockedIP(t *testing.T) { func TestGlobal_IsBlockedIP(t *testing.T) {
am, err := access.New([]string{}, []string{ global, err := access.NewGlobal([]string{}, []string{
"1.1.1.1", "1.1.1.1",
"2.2.2.0/8", "2.2.2.0/8",
}) })
@ -99,7 +111,7 @@ func TestAccessManager_IsBlockedIP(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
blocked, rule := am.IsBlockedIP(tc.ip) blocked, rule := global.IsBlockedIP(tc.ip)
tc.want(t, blocked) tc.want(t, blocked)
assert.Equal(t, tc.wantRule, rule) 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 // 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 // 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 // index. If slashes is true, it also looks for slashes. If there are no such
// runes, i is -1. // runes, i is -1.

View File

@ -2,16 +2,10 @@ package agd_test
import ( import (
"testing" "testing"
"time"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
) )
// Common Constants And Utilities
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
testutil.DiscardLogOutput(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" "net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
) )
@ -73,7 +74,7 @@ type RequestInfo struct {
Profile *Profile Profile *Profile
// Location is the GeoIP location data about the remote IP address, if any. // 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 // ECS contains the EDNS Client Subnet option information of the request, if
// any. // any.
@ -107,9 +108,10 @@ type RequestInfo struct {
QType dnsmsg.RRType QType dnsmsg.RRType
// QClass is the class of question for this request. // QClass is the class of question for this request.
//
// TODO(a.garipov): Use more.
QClass dnsmsg.Class 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. // ECS is the content of the EDNS Client Subnet option of a DNS message.
@ -118,7 +120,7 @@ type RequestInfo struct {
type ECS struct { type ECS struct {
// Location is the GeoIP location data about the IP address from the // Location is the GeoIP location data about the IP address from the
// request's ECS data, if any. // request's ECS data, if any.
Location *Location Location *geoip.Location
// Subnet is the source subnet. // Subnet is the source subnet.
Subnet netip.Prefix 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) 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" "math"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
@ -37,11 +38,17 @@ type Profile struct {
// [internal/profiledb/internal.FileCacheVersion]. // [internal/profiledb/internal.FileCacheVersion].
SafeBrowsing *SafeBrowsingSettings 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. // BlockingMode defines the way blocked responses are constructed.
// //
// NOTE: Do not change fields of this structure without incrementing // NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion]. // [internal/profiledb/internal.FileCacheVersion].
BlockingMode dnsmsg.BlockingModeCodec BlockingMode dnsmsg.BlockingMode
// ID is the unique ID of this profile. // 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 // NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion]. // [internal/profiledb/internal.FileCacheVersion].
BlockFirefoxCanary bool 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. // ProfileID is the ID of a profile. It is an opaque string.

View File

@ -2,9 +2,13 @@ package agd
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"net/netip" "net/netip"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"github.com/ameshkov/dnscrypt/v2" "github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -84,12 +88,34 @@ type Server struct {
// TLS is the TLS configuration for this server, if any. // TLS is the TLS configuration for this server, if any.
TLS *tls.Config 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 // Name is the unique name of the server. Not to be confused with a TLS
// Server Name. // Server Name.
Name ServerName Name ServerName
// BindData are the socket binding data for this server. // bindData are the socket binding data for this server.
BindData []*ServerBindData 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 is the protocol of the server.
Protocol Protocol Protocol Protocol
@ -99,8 +125,75 @@ type Server struct {
LinkedIPEnabled bool 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 // 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. // TODO(a.garipov): Consider turning this into a sum type.
// //
@ -108,7 +201,7 @@ type Server struct {
// like BindConfig. // like BindConfig.
type ServerBindData struct { type ServerBindData struct {
ListenConfig netext.ListenConfig ListenConfig netext.ListenConfig
Address string PrefixAddr *agdnet.PrefixNetAddr
AddrPort netip.AddrPort AddrPort netip.AddrPort
} }
@ -124,3 +217,35 @@ type DNSCryptConfig struct {
// ProviderName is the name of the DNSCrypt provider. // ProviderName is the name of the DNSCrypt provider.
ProviderName string 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 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) { func testURL() (u *url.URL) {
return &url.URL{ return &url.URL{
Scheme: "http", 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) { func NormalizeDomain(fqdn string) (host string) {
return strings.ToLower(strings.TrimSuffix(fqdn, ".")) 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 ( import (
"fmt" "fmt"
"net"
"net/netip" "net/netip"
) )
// FormatPrefixAddr returns either a simple IP:port address or one with the // PrefixNetAddr is a wrapper around netip.Prefix that makes it a [net.Addr].
// prefix length appended after a slash, depending on whether or not subnet is a type PrefixNetAddr struct {
// single-address subnet. This is done to make using the IP:port part easier to Prefix netip.Prefix
// split off using functions like [strings.Cut]. Net string
func FormatPrefixAddr(subnet netip.Prefix, port uint16) (s string) { Port uint16
addrPort := netip.AddrPortFrom(subnet.Addr(), port) }
if subnet.IsSingleIP() {
// 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 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" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
) )
func ExampeFormatPrefixAddr() { func ExamplePrefixNetAddr_string() {
fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.4/32"), 5678)) fmt.Println(&agdnet.PrefixNetAddr{
fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.0/24"), 5678)) 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: // Output:
// 1.2.3.4:5678 // 1.2.3.4:5678

View File

@ -1,12 +1,11 @@
//go:build linux package agdnet_test
package bindtodevice
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"testing" "testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -19,14 +18,14 @@ func TestPrefixAddr(t *testing.T) {
fullPrefix := netip.MustParsePrefix("1.2.3.4/32") fullPrefix := netip.MustParsePrefix("1.2.3.4/32")
testCases := []struct { testCases := []struct {
in *prefixNetAddr in *agdnet.PrefixNetAddr
want string want string
name string name string
}{{ }{{
in: &prefixNetAddr{ in: &agdnet.PrefixNetAddr{
prefix: testSubnetIPv4, Prefix: testSubnetIPv4,
network: network, Net: network,
port: port, Port: port,
}, },
want: fmt.Sprintf( want: fmt.Sprintf(
"%s/%d", "%s/%d",
@ -34,10 +33,10 @@ func TestPrefixAddr(t *testing.T) {
), ),
name: "ipv4", name: "ipv4",
}, { }, {
in: &prefixNetAddr{ in: &agdnet.PrefixNetAddr{
prefix: testSubnetIPv6, Prefix: testSubnetIPv6,
network: network, Net: network,
port: port, Port: port,
}, },
want: fmt.Sprintf( want: fmt.Sprintf(
"%s/%d", "%s/%d",
@ -45,10 +44,10 @@ func TestPrefixAddr(t *testing.T) {
), ),
name: "ipv6", name: "ipv6",
}, { }, {
in: &prefixNetAddr{ in: &agdnet.PrefixNetAddr{
prefix: fullPrefix, Prefix: fullPrefix,
network: network, Net: network,
port: port, Port: port,
}, },
want: netip.AddrPortFrom(fullPrefix.Addr(), port).String(), want: netip.AddrPortFrom(fullPrefix.Addr(), port).String(),
name: "ipv4_full", name: "ipv4_full",

View File

@ -14,11 +14,11 @@ import (
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
) )
// Resolvers
// Resolver is the DNS resolver interface. // 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 { type Resolver interface {
// LookupNetIP returns a slice of host's IP addresses of family specified by // LookupNetIP returns a slice of host's IP addresses of family specified by
// fam, which must be either [netutil.AddrFamilyIPv4] or // 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 ( import (
"testing" "testing"
"time"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
) )
@ -9,3 +10,6 @@ import (
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
testutil.DiscardLogOutput(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 ( import (
"context" "context"
"fmt" "fmt"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/rand"
) )
// Refreshable Entities And Utilities
// Refresher is the interface for entities that can update themselves. // Refresher is the interface for entities that can update themselves.
type Refresher interface { type Refresher interface {
Refresh(ctx context.Context) (err error) Refresh(ctx context.Context) (err error)
} }
var _ Service = (*RefreshWorker)(nil) // RefreshWorker is an [Interface] implementation that updates its [Refresher]
// every tick of the provided ticker.
// RefreshWorker is a Service that updates its refreshable entity every tick of
// the provided ticker.
type RefreshWorker struct { type RefreshWorker struct {
done chan unit done chan unit
context func() (ctx context.Context, cancel context.CancelFunc) context func() (ctx context.Context, cancel context.CancelFunc)
logRoutine func(format string, args ...any) logRoutine func(format string, args ...any)
tick *time.Ticker tick *time.Ticker
refr Refresher rand *rand.Rand
errColl ErrorCollector refr Refresher
name string errColl errcoll.Interface
name string
maxStartSleep time.Duration
refrOnShutdown bool refrOnShutdown bool
} }
@ -40,13 +40,22 @@ type RefreshWorkerConfig struct {
Refresher Refresher Refresher Refresher
// ErrColl is used to collect errors during refreshes. // 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 // Name is the name of this worker. It is used for logging and error
// collecting. // collecting.
//
// TODO(a.garipov): Consider accepting a slog.Logger or removing this and
// making all Refreshers handle their own logging.
Name string Name string
// Interval is the refresh interval. Must be greater than zero. // 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 Interval time.Duration
// RefreshOnShutdown, if true, instructs the worker to call the Refresher's // 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 // than on the Info one. This is useful to prevent routine logs from
// workers with a small interval from overflowing with messages. // workers with a small interval from overflowing with messages.
RoutineLogsAreDebug bool 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 // NewRefreshWorker returns a new valid *RefreshWorker with the provided
@ -72,27 +89,39 @@ func NewRefreshWorker(c *RefreshWorkerConfig) (w *RefreshWorker) {
logRoutine = log.Info 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{ return &RefreshWorker{
done: make(chan unit), done: make(chan unit),
context: c.Context, context: c.Context,
logRoutine: logRoutine, logRoutine: logRoutine,
tick: time.NewTicker(c.Interval), tick: time.NewTicker(c.Interval),
rand: rng,
refr: c.Refresher, refr: c.Refresher,
errColl: c.ErrColl, errColl: c.ErrColl,
name: c.Name, name: c.Name,
maxStartSleep: maxStartSleep,
refrOnShutdown: c.RefreshOnShutdown, 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. // nil.
func (w *RefreshWorker) Start() (err error) { func (w *RefreshWorker) Start(_ context.Context) (err error) {
go w.refreshInALoop() go w.refreshInALoop()
return nil 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) { func (w *RefreshWorker) Shutdown(ctx context.Context) (err error) {
if w.refrOnShutdown { if w.refrOnShutdown {
err = w.refr.Refresh(ctx) err = w.refr.Refresh(ctx)
@ -105,9 +134,8 @@ func (w *RefreshWorker) Shutdown(ctx context.Context) (err error) {
name := w.name name := w.name
if err != nil { if err != nil {
err = fmt.Errorf("refresh on shutdown: %w", err) err = fmt.Errorf("refresh on shutdown: %w", err)
log.Error("%s: shut down with error: %s", name, err)
} else { } else {
log.Info("%s: shut down successfully", name) log.Info("worker %q: shut down successfully", name)
} }
return err return err
@ -119,24 +147,57 @@ func (w *RefreshWorker) refreshInALoop() {
name := w.name name := w.name
defer log.OnPanic(name) defer log.OnPanic(name)
log.Info("%s: starting refresh loop", name) log.Info("worker %q: starting refresh loop", name)
for { for {
select { select {
case <-w.done: case <-w.done:
log.Info("%s: finished refresh loop", name) log.Info("worker %q: finished refresh loop", name)
return return
case <-w.tick.C: case <-w.tick.C:
w.refresh() if w.sleepRandom() {
w.refresh()
}
} }
} }
} }
// sleepRandom sleeps for up to maxStartSleep unless it's zero. shouldRefresh
// shows if a refresh should be performed once the sleep is finished.
func (w *RefreshWorker) sleepRandom() (shouldRefresh bool) {
if w.maxStartSleep == 0 {
return true
}
sleepDur := time.Duration(w.rand.Int63n(int64(w.maxStartSleep)))
w.logRoutine("worker %q: sleeping for %s before refresh", w.name, sleepDur)
timer := time.NewTimer(sleepDur)
defer func() {
if !timer.Stop() {
// We don't know if the timer's value has been consumed yet or not,
// so use a select with default to make sure that this doesn't
// block.
select {
case <-timer.C:
default:
}
}
}()
select {
case <-w.done:
return false
case <-timer.C:
return true
}
}
// refresh refreshes the entity and logs the status of the refresh. // refresh refreshes the entity and logs the status of the refresh.
func (w *RefreshWorker) refresh() { func (w *RefreshWorker) refresh() {
name := w.name 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 // TODO(a.garipov): Consider adding a helper for enriching errors with
// context deadline data without duplication. See an example in method // context deadline data without duplication. See an example in method
@ -144,15 +205,15 @@ func (w *RefreshWorker) refresh() {
ctx, cancel := w.context() ctx, cancel := w.context()
defer cancel() defer cancel()
log.Debug("%s: starting refresh", name) log.Debug("worker %q: starting refresh", name)
err := w.refr.Refresh(ctx) err := w.refr.Refresh(ctx)
log.Debug("%s: finished refresh", name) log.Debug("worker %q: finished refresh", name)
if err != nil { if err != nil {
Collectf(ctx, w.errColl, "%s: %w", name, err) errcoll.Collectf(ctx, w.errColl, "%s: %w", name, err)
return 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 ( import (
"context" "context"
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
@ -45,11 +45,11 @@ func newTestRefresher(t *testing.T, respErr error) (refr *agdtest.Refresher, syn
// newRefrConf returns worker configuration. // newRefrConf returns worker configuration.
func newRefrConf( func newRefrConf(
t *testing.T, t *testing.T,
refr agd.Refresher, refr agdservice.Refresher,
ivl time.Duration, ivl time.Duration,
refrOnShutDown bool, refrOnShutDown bool,
errCh chan sig, errCh chan sig,
) (conf *agd.RefreshWorkerConfig) { ) (conf *agdservice.RefreshWorkerConfig) {
t.Helper() t.Helper()
pt := testutil.PanicT{} pt := testutil.PanicT{}
@ -60,7 +60,7 @@ func newRefrConf(
}, },
} }
return &agd.RefreshWorkerConfig{ return &agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) { Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), testTimeout) return context.WithTimeout(context.Background(), testTimeout)
}, },
@ -70,6 +70,7 @@ func newRefrConf(
Interval: ivl, Interval: ivl,
RefreshOnShutdown: refrOnShutDown, RefreshOnShutdown: refrOnShutDown,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
} }
} }
@ -78,18 +79,15 @@ func TestRefreshWorker(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil) refr, syncCh := newTestRefresher(t, nil)
errCh := make(chan sig, 1) 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) require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout) testutil.RequireReceive(t, syncCh, testTimeout)
require.Empty(t, errCh) require.Empty(t, errCh)
shutdown, cancel := context.WithTimeout(context.Background(), testTimeout) err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
defer cancel()
err = w.Shutdown(shutdown)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -97,15 +95,12 @@ func TestRefreshWorker(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil) refr, syncCh := newTestRefresher(t, nil)
errCh := make(chan sig, 1) 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) require.NoError(t, err)
shutdown, cancel := context.WithTimeout(context.Background(), testTimeout) err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
defer cancel()
err = w.Shutdown(shutdown)
require.NoError(t, err) require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout) testutil.RequireReceive(t, syncCh, testTimeout)
@ -116,18 +111,15 @@ func TestRefreshWorker(t *testing.T) {
errRefr, syncCh := newTestRefresher(t, testError) errRefr, syncCh := newTestRefresher(t, testError)
errCh := make(chan sig, 1) 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) require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout) testutil.RequireReceive(t, syncCh, testTimeout)
testutil.RequireReceive(t, errCh, testTimeout) testutil.RequireReceive(t, errCh, testTimeout)
shutdown, cancel := context.WithTimeout(context.Background(), testTimeout) err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
defer cancel()
err = w.Shutdown(shutdown)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -135,15 +127,12 @@ func TestRefreshWorker(t *testing.T) {
errRefr, syncCh := newTestRefresher(t, testError) errRefr, syncCh := newTestRefresher(t, testError)
errCh := make(chan sig, 1) 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) require.NoError(t, err)
shutdown, cancel := context.WithTimeout(context.Background(), testTimeout) err = w.Shutdown(agdtest.ContextWithTimeout(t, testTimeout))
defer cancel()
err = w.Shutdown(shutdown)
assert.ErrorIs(t, err, testError) assert.ErrorIs(t, err, testError)
testutil.RequireReceive(t, syncCh, testTimeout) 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 package agdtest
import ( import (
"context"
"testing"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@ -18,5 +20,23 @@ const FilteredResponseTTLSec = 10
// NewConstructor returns a standard dnsmsg.Constructor for tests. // NewConstructor returns a standard dnsmsg.Constructor for tests.
func NewConstructor() (c *dnsmsg.Constructor) { 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/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck" "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
@ -29,34 +31,6 @@ import (
// Module AdGuardDNS // 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 // Package access
// type check // type check
@ -83,7 +57,7 @@ func (a *AccessManager) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) {
// type check // type check
var _ agdnet.Resolver = (*Resolver)(nil) var _ agdnet.Resolver = (*Resolver)(nil)
// Resolver is an agd.Resolver for tests. // Resolver is an [agdnet.Resolver] for tests.
type Resolver struct { type Resolver struct {
OnLookupNetIP func( OnLookupNetIP func(
ctx context.Context, ctx context.Context,
@ -92,7 +66,7 @@ type Resolver struct {
) (ips []netip.Addr, err error) ) (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( func (r *Resolver) LookupNetIP(
ctx context.Context, ctx context.Context,
fam netutil.AddrFamily, fam netutil.AddrFamily,
@ -101,6 +75,21 @@ func (r *Resolver) LookupNetIP(
return r.OnLookupNetIP(ctx, fam, host) 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 // Package billstat
// type check // type check
@ -111,8 +100,8 @@ type BillStatRecorder struct {
OnRecord func( OnRecord func(
ctx context.Context, ctx context.Context,
id agd.DeviceID, id agd.DeviceID,
ctry agd.Country, ctry geoip.Country,
asn agd.ASN, asn geoip.ASN,
start time.Time, start time.Time,
proto agd.Protocol, proto agd.Protocol,
) )
@ -122,8 +111,8 @@ type BillStatRecorder struct {
func (r *BillStatRecorder) Record( func (r *BillStatRecorder) Record(
ctx context.Context, ctx context.Context,
id agd.DeviceID, id agd.DeviceID,
ctry agd.Country, ctry geoip.Country,
asn agd.ASN, asn geoip.ASN,
start time.Time, start time.Time,
proto agd.Protocol, 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) 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 // Package filter
// type check // type check
@ -263,25 +269,18 @@ var _ geoip.Interface = (*GeoIP)(nil)
// GeoIP is a geoip.Interface for tests. // GeoIP is a geoip.Interface for tests.
type GeoIP struct { type GeoIP struct {
OnSubnetByLocation func( OnSubnetByLocation func(l *geoip.Location, fam netutil.AddrFamily) (n netip.Prefix, err error)
c agd.Country, OnData func(host string, ip netip.Addr) (l *geoip.Location, err error)
a agd.ASN,
fam netutil.AddrFamily,
) (n netip.Prefix, err error)
OnData func(host string, ip netip.Addr) (l *agd.Location, err error)
} }
// SubnetByLocation implements the geoip.Interface interface for *GeoIP. // SubnetByLocation implements the geoip.Interface interface for *GeoIP.
func (g *GeoIP) SubnetByLocation( func (g *GeoIP) SubnetByLocation(l *geoip.Location, fam netutil.AddrFamily,
c agd.Country,
a agd.ASN,
fam netutil.AddrFamily,
) (n netip.Prefix, err error) { ) (n netip.Prefix, err error) {
return g.OnSubnetByLocation(c, a, fam) return g.OnSubnetByLocation(l, fam)
} }
// Data implements the geoip.Interface interface for *GeoIP. // 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) 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. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.31.0 // protoc-gen-go v1.31.0
// protoc v4.23.4 // protoc v4.25.1
// source: backend.proto // source: backend.proto
package backendpb package backendpb
@ -94,6 +94,8 @@ type DNSProfile struct {
// *DNSProfile_BlockingModeNullIp // *DNSProfile_BlockingModeNullIp
// *DNSProfile_BlockingModeRefused // *DNSProfile_BlockingModeRefused
BlockingMode isDNSProfile_BlockingMode `protobuf_oneof:"blocking_mode"` 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() { func (x *DNSProfile) Reset() {
@ -247,6 +249,20 @@ func (x *DNSProfile) GetBlockingModeRefused() *BlockingModeREFUSED {
return nil 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 { type isDNSProfile_BlockingMode interface {
isDNSProfile_BlockingMode() isDNSProfile_BlockingMode()
} }
@ -1021,6 +1037,148 @@ func (x *DeviceBillingStat) GetQueries() uint32 {
return 0 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 protoreflect.FileDescriptor
var file_backend_proto_rawDesc = []byte{ 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x52, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x70, 0x5f, 0x6c, 0x6f,
0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52,
0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0c, 0x69, 0x70, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a,
0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x06, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x62, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x06,
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, 0x65,
0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
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, 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, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x62, 0x6c,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x14, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, 0x6f,
0x6f, 0x6d, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69,
0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x72, 0x64, 0x18,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x72, 0x64, 0x22,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
0x4d, 0x41, 0x49, 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
0x44, 0x22, 0xe3, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62,
0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x70,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x70,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x70,
0x10, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x69, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74,
0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x65, 0x64, 0x49, 0x70, 0x73, 0x22, 0x87, 0x02, 0x0a, 0x10, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x61, 0x6c, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e,
0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61, 0x64,
0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x04, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
0x73, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x61, 0x73, 0x6e, 0x12, 0x18, 0x0a, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01,
0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65, 0x53,
0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x32, 0x8a, 0x01, 0x0a, 0x0a, 0x44, 0x4e, 0x53, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x67, 0x65, 0x74, 0x44, 0x4e, 0x53, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x13, 0x2e, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65, 0x53,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52,
0x73, 0x61, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x42, 0x69, 0x6c, 0x6c, 0x69, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x12, 0x2d, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01,
0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22,
0x74, 0x79, 0x28, 0x01, 0x42, 0x4a, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x67, 0x75, 0x54, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69,
0x61, 0x72, 0x64, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x6e, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6d, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x10, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x52, 0x03, 0x74, 0x6d, 0x7a, 0x12, 0x2e, 0x0a, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x52,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0b, 0x2e, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x57, 0x65, 0x65,
0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x4e, 0x53, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 ( var (
@ -1208,7 +1391,7 @@ func file_backend_proto_rawDescGZIP() []byte {
return file_backend_proto_rawDescData 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{}{ var file_backend_proto_goTypes = []interface{}{
(*DNSProfilesRequest)(nil), // 0: DNSProfilesRequest (*DNSProfilesRequest)(nil), // 0: DNSProfilesRequest
(*DNSProfile)(nil), // 1: DNSProfile (*DNSProfile)(nil), // 1: DNSProfile
@ -1224,42 +1407,47 @@ var file_backend_proto_goTypes = []interface{}{
(*BlockingModeNullIP)(nil), // 11: BlockingModeNullIP (*BlockingModeNullIP)(nil), // 11: BlockingModeNullIP
(*BlockingModeREFUSED)(nil), // 12: BlockingModeREFUSED (*BlockingModeREFUSED)(nil), // 12: BlockingModeREFUSED
(*DeviceBillingStat)(nil), // 13: DeviceBillingStat (*DeviceBillingStat)(nil), // 13: DeviceBillingStat
(*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp (*AccessSettings)(nil), // 14: AccessSettings
(*durationpb.Duration)(nil), // 15: google.protobuf.Duration (*CidrRange)(nil), // 15: CidrRange
(*emptypb.Empty)(nil), // 16: google.protobuf.Empty (*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{ 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 2, // 1: DNSProfile.safe_browsing:type_name -> SafeBrowsingSettings
4, // 2: DNSProfile.parental:type_name -> ParentalSettings 4, // 2: DNSProfile.parental:type_name -> ParentalSettings
8, // 3: DNSProfile.rule_lists:type_name -> RuleListsSettings 8, // 3: DNSProfile.rule_lists:type_name -> RuleListsSettings
3, // 4: DNSProfile.devices:type_name -> DeviceSettings 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 9, // 6: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
10, // 7: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN 10, // 7: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
11, // 8: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP 11, // 8: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP
12, // 9: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED 12, // 9: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED
5, // 10: ParentalSettings.schedule:type_name -> ScheduleSettings 14, // 10: DNSProfile.access:type_name -> AccessSettings
6, // 11: ScheduleSettings.weeklyRange:type_name -> WeeklyRange 5, // 11: ParentalSettings.schedule:type_name -> ScheduleSettings
7, // 12: WeeklyRange.mon:type_name -> DayRange 6, // 12: ScheduleSettings.weeklyRange:type_name -> WeeklyRange
7, // 13: WeeklyRange.tue:type_name -> DayRange 7, // 13: WeeklyRange.mon:type_name -> DayRange
7, // 14: WeeklyRange.wed:type_name -> DayRange 7, // 14: WeeklyRange.tue:type_name -> DayRange
7, // 15: WeeklyRange.thu:type_name -> DayRange 7, // 15: WeeklyRange.wed:type_name -> DayRange
7, // 16: WeeklyRange.fri:type_name -> DayRange 7, // 16: WeeklyRange.thu:type_name -> DayRange
7, // 17: WeeklyRange.sat:type_name -> DayRange 7, // 17: WeeklyRange.fri:type_name -> DayRange
7, // 18: WeeklyRange.sun:type_name -> DayRange 7, // 18: WeeklyRange.sat:type_name -> DayRange
15, // 19: DayRange.start:type_name -> google.protobuf.Duration 7, // 19: WeeklyRange.sun:type_name -> DayRange
15, // 20: DayRange.end:type_name -> google.protobuf.Duration 17, // 20: DayRange.start:type_name -> google.protobuf.Duration
14, // 21: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp 17, // 21: DayRange.end:type_name -> google.protobuf.Duration
0, // 22: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest 16, // 22: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
13, // 23: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat 15, // 23: AccessSettings.allowlist_cidr:type_name -> CidrRange
1, // 24: DNSService.getDNSProfiles:output_type -> DNSProfile 15, // 24: AccessSettings.blocklist_cidr:type_name -> CidrRange
16, // 25: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty 0, // 25: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
24, // [24:26] is the sub-list for method output_type 13, // 26: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
22, // [22:24] is the sub-list for method input_type 1, // 27: DNSService.getDNSProfiles:output_type -> DNSProfile
22, // [22:22] is the sub-list for extension type_name 18, // 28: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
22, // [22:22] is the sub-list for extension extendee 27, // [27:29] is the sub-list for method output_type
0, // [0:22] is the sub-list for field type_name 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() } func init() { file_backend_proto_init() }
@ -1436,6 +1624,30 @@ func file_backend_proto_init() {
return nil 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{}{ file_backend_proto_msgTypes[1].OneofWrappers = []interface{}{
(*DNSProfile_BlockingModeCustomIp)(nil), (*DNSProfile_BlockingModeCustomIp)(nil),
@ -1449,7 +1661,7 @@ func file_backend_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_backend_proto_rawDesc, RawDescriptor: file_backend_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 14, NumMessages: 16,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -1,7 +1,5 @@
syntax = "proto3"; syntax = "proto3";
option go_package = "./backendpb";
import "google/protobuf/duration.proto"; import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
@ -25,7 +23,7 @@ service DNSService {
/* /*
Stores devices activity. Stores devices activity.
*/ */
rpc saveDevicesBillingStat(stream DeviceBillingStat) returns (google.protobuf.Empty); rpc saveDevicesBillingStat(stream DeviceBillingStat) returns (google.protobuf.Empty);
} }
@ -52,6 +50,8 @@ message DNSProfile {
BlockingModeNullIP blocking_mode_null_ip = 15; BlockingModeNullIP blocking_mode_null_ip = 15;
BlockingModeREFUSED blocking_mode_refused = 16; BlockingModeREFUSED blocking_mode_refused = 16;
} }
bool ip_log_enabled = 17;
AccessSettings access = 18;
} }
message SafeBrowsingSettings { message SafeBrowsingSettings {
@ -122,3 +122,17 @@ message DeviceBillingStat {
uint32 asn = 5; uint32 asn = 5;
uint32 queries = 6; 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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.3.0 // - protoc-gen-go-grpc v1.3.0
// - protoc v4.23.4 // - protoc v4.25.1
// source: backend.proto // source: backend.proto
package backendpb package backendpb

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure" "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. // reportf is a helper method for reporting non-critical errors.
func reportf(ctx context.Context, errColl agd.ErrorCollector, format string, args ...any) { func reportf(ctx context.Context, errColl errcoll.Interface, format string, args ...any) {
agd.Collectf(ctx, errColl, "backendpb: "+format, args...) 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/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
@ -17,7 +18,7 @@ import (
type BillStatConfig struct { type BillStatConfig struct {
// ErrColl is the error collector that is used to collect critical and // ErrColl is the error collector that is used to collect critical and
// non-critical errors. // non-critical errors.
ErrColl agd.ErrorCollector ErrColl errcoll.Interface
// Endpoint is the backend API URL. The scheme should be either "grpc" or // Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs". // "grpcs".
@ -46,7 +47,7 @@ func NewBillStat(c *BillStatConfig) (b *BillStat, err error) {
// TODO(a.garipov): Consider uniting with [ProfileStorage] into a single // TODO(a.garipov): Consider uniting with [ProfileStorage] into a single
// backendpb.Client. // backendpb.Client.
type BillStat struct { type BillStat struct {
errColl agd.ErrorCollector errColl errcoll.Interface
// client is the current GRPC client. // client is the current GRPC client.
client DNSServiceClient client DNSServiceClient

View File

@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb" "github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -30,7 +31,7 @@ func TestBillStat_Upload(t *testing.T) {
wantRecord := &billstat.Record{ wantRecord := &billstat.Record{
Time: time.Time{}, Time: time.Time{},
Country: agd.CountryCY, Country: geoip.CountryCY,
ASN: 1221, ASN: 1221,
Queries: 1122, Queries: 1122,
Proto: agd.ProtoDNS, Proto: agd.ProtoDNS,

View File

@ -9,13 +9,18 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf" "github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "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/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
@ -24,7 +29,7 @@ import (
type ProfileStorageConfig struct { type ProfileStorageConfig struct {
// ErrColl is the error collector that is used to collect critical and // ErrColl is the error collector that is used to collect critical and
// non-critical errors. // non-critical errors.
ErrColl agd.ErrorCollector ErrColl errcoll.Interface
// Endpoint is the backend API URL. The scheme should be either "grpc" or // Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs". // "grpcs".
@ -35,7 +40,7 @@ type ProfileStorageConfig struct {
// that retrieves the profile and device information from the business logic // that retrieves the profile and device information from the business logic
// backend. It is safe for concurrent use. // backend. It is safe for concurrent use.
type ProfileStorage struct { type ProfileStorage struct {
errColl agd.ErrorCollector errColl errcoll.Interface
// client is the current GRPC client. // client is the current GRPC client.
client DNSServiceClient client DNSServiceClient
@ -66,7 +71,7 @@ func (s *ProfileStorage) Profiles(
) (resp *profiledb.StorageResponse, err error) { ) (resp *profiledb.StorageResponse, err error) {
stream, err := s.client.GetDNSProfiles(ctx, toProtobuf(req)) stream, err := s.client.GetDNSProfiles(ctx, toProtobuf(req))
if err != nil { 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()) }() defer func() { err = errors.WithDeferred(err, stream.CloseSend()) }()
@ -76,7 +81,7 @@ func (s *ProfileStorage) Profiles(
} }
stats := &profilesCallStats{ stats := &profilesCallStats{
isFullSync: req.SyncTime == time.Time{}, isFullSync: req.SyncTime.IsZero(),
} }
for { for {
@ -87,7 +92,7 @@ func (s *ProfileStorage) Profiles(
break break
} }
return nil, fmt.Errorf("receiving profile: %w", profErr) return nil, fmt.Errorf("receiving profile: %w", fixGRPCError(profErr))
} }
stats.endRecv() stats.endRecv()
@ -115,11 +120,27 @@ func (s *ProfileStorage) Profiles(
return resp, nil 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. // toInternal converts the protobuf-encoded data into a profile structure.
func (x *DNSProfile) toInternal( func (x *DNSProfile) toInternal(
ctx context.Context, ctx context.Context,
updTime time.Time, updTime time.Time,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (profile *agd.Profile, devices []*agd.Device, err error) { ) (profile *agd.Profile, devices []*agd.Device, err error) {
if x == nil { if x == nil {
return nil, nil, fmt.Errorf("profile is nil") return nil, nil, fmt.Errorf("profile is nil")
@ -159,11 +180,13 @@ func (x *DNSProfile) toInternal(
FilteredResponseTTL: fltRespTTL, FilteredResponseTTL: fltRespTTL,
FilteringEnabled: x.FilteringEnabled, FilteringEnabled: x.FilteringEnabled,
SafeBrowsing: x.SafeBrowsing.toInternal(), SafeBrowsing: x.SafeBrowsing.toInternal(),
Access: x.Access.toInternal(ctx, errColl),
RuleListsEnabled: listsEnabled, RuleListsEnabled: listsEnabled,
QueryLogEnabled: x.QueryLogEnabled, QueryLogEnabled: x.QueryLogEnabled,
Deleted: x.Deleted, Deleted: x.Deleted,
BlockPrivateRelay: x.BlockPrivateRelay, BlockPrivateRelay: x.BlockPrivateRelay,
BlockFirefoxCanary: x.BlockFirefoxCanary, BlockFirefoxCanary: x.BlockFirefoxCanary,
IPLogEnabled: x.IpLogEnabled,
}, devices, nil }, devices, nil
} }
@ -171,7 +194,7 @@ func (x *DNSProfile) toInternal(
// one. If x is nil, toInternal returns nil. // one. If x is nil, toInternal returns nil.
func (x *ParentalSettings) toInternal( func (x *ParentalSettings) toInternal(
ctx context.Context, ctx context.Context,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (s *agd.ParentalProtectionSettings, err error) { ) (s *agd.ParentalProtectionSettings, err error) {
if x == nil { if x == nil {
return nil, 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 // blockedSvcsToInternal is a helper that converts the blocked service IDs from
// the backend response to AdGuard DNS blocked service IDs. // the backend response to AdGuard DNS blocked service IDs.
func blockedSvcsToInternal( func blockedSvcsToInternal(
ctx context.Context, ctx context.Context,
errColl agd.ErrorCollector, errColl errcoll.Interface,
respSvcs []string, respSvcs []string,
) (svcs []agd.BlockedServiceID) { ) (svcs []agd.BlockedServiceID) {
l := len(respSvcs) 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 // blockingModeToInternal converts a protobuf blocking-mode sum-type to an
// internal one. If pbm is nil, blockingModeToInternal returns a null-IP // internal one. If pbm is nil, blockingModeToInternal returns a null-IP
// blocking mode. // 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) { switch pbm := pbm.(type) {
case nil: case nil:
m.Mode = &dnsmsg.BlockingModeNullIP{} return &dnsmsg.BlockingModeNullIP{}, nil
case *DNSProfile_BlockingModeCustomIp: case *DNSProfile_BlockingModeCustomIp:
custom := &dnsmsg.BlockingModeCustomIP{} custom := &dnsmsg.BlockingModeCustomIP{}
err = custom.IPv4.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv4) err = custom.IPv4.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv4)
if err != nil { 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) err = custom.IPv6.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv6)
if err != nil { 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: case *DNSProfile_BlockingModeNxdomain:
m.Mode = &dnsmsg.BlockingModeNXDOMAIN{} return &dnsmsg.BlockingModeNXDOMAIN{}, nil
case *DNSProfile_BlockingModeNullIp: case *DNSProfile_BlockingModeNullIp:
m.Mode = &dnsmsg.BlockingModeNullIP{} return &dnsmsg.BlockingModeNullIP{}, nil
case *DNSProfile_BlockingModeRefused: case *DNSProfile_BlockingModeRefused:
m.Mode = &dnsmsg.BlockingModeREFUSED{} return &dnsmsg.BlockingModeREFUSED{}, nil
default: default:
// Consider unhandled type-switch cases programmer errors. // 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 // 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( func devicesToInternal(
ctx context.Context, ctx context.Context,
ds []*DeviceSettings, ds []*DeviceSettings,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (out []*agd.Device, ids []agd.DeviceID) { ) (out []*agd.Device, ids []agd.DeviceID) {
l := len(ds) l := len(ds)
if l == 0 { if l == 0 {
@ -379,7 +450,7 @@ func (ds *DeviceSettings) toInternal() (dev *agd.Device, err error) {
func rulesToInternal( func rulesToInternal(
ctx context.Context, ctx context.Context,
respRules []string, respRules []string,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (rules []agd.FilterRuleText) { ) (rules []agd.FilterRuleText) {
l := len(respRules) l := len(respRules)
if l == 0 { if l == 0 {
@ -406,7 +477,7 @@ func rulesToInternal(
// false and nil. // false and nil.
func (x *RuleListsSettings) toInternal( func (x *RuleListsSettings) toInternal(
ctx context.Context, ctx context.Context,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (enabled bool, filterLists []agd.FilterListID) { ) (enabled bool, filterLists []agd.FilterListID) {
if x == nil { if x == nil {
return false, nil return false, nil

View File

@ -7,10 +7,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -60,13 +62,7 @@ func TestDNSProfile_ToInternal(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Error(t, errCollErr) require.Error(t, errCollErr)
// See the TODO in [blockingModeToInternal]. assert.Equal(t, newProfile(t), got)
wantProf := newProfile(t)
wantProf.BlockingMode = dnsmsg.BlockingModeCodec{
Mode: &dnsmsg.BlockingModeNullIP{},
}
assert.Equal(t, wantProf, got)
assert.Equal(t, newDevices(t), gotDevices) assert.Equal(t, newDevices(t), gotDevices)
}) })
@ -127,31 +123,55 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(ctx, TestUpdTime, errColl) _, _, err := dp.toInternal(ctx, TestUpdTime, errColl)
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv6: unexpected slice size", err) 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 // newDNSProfileWithBadData returns a new instance of *DNSProfile with bad
// for tests. // devices data for tests.
func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) { func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
tb.Helper() tb.Helper()
dayRange := &DayRange{ invalidDevices := []*DeviceSettings{{
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,
}, {
Id: "invalid-too-long-device-id", Id: "invalid-too-long-device-id",
Name: "device_name", Name: "device_name",
FilteringEnabled: true, FilteringEnabled: true,
@ -179,45 +199,10 @@ func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
DedicatedIps: [][]byte{[]byte("1")}, DedicatedIps: [][]byte{[]byte("1")},
}} }}
return &DNSProfile{ dp = NewTestDNSProfile(tb)
DnsId: string(testProfileID), dp.Devices = append(dp.Devices, invalidDevices...)
FilteringEnabled: true,
QueryLogEnabled: true, return dp
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,
}
} }
// NewTestDNSProfile returns a new instance of *DNSProfile for tests. // 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), FilteredResponseTtl: durationpb.New(10 * time.Second),
BlockPrivateRelay: true, BlockPrivateRelay: true,
BlockFirefoxCanary: true, BlockFirefoxCanary: true,
IpLogEnabled: true,
BlockingMode: &DNSProfile_BlockingModeCustomIp{ BlockingMode: &DNSProfile_BlockingModeCustomIp{
BlockingModeCustomIp: &BlockingModeCustomIP{ BlockingModeCustomIp: &BlockingModeCustomIP{
Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")), Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")),
Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")), 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, BlockNewlyRegisteredDomains: false,
} }
wantBlockingMode := dnsmsg.BlockingModeCodec{ wantBlockingMode := &dnsmsg.BlockingModeCustomIP{
Mode: &dnsmsg.BlockingModeCustomIP{ IPv4: netip.MustParseAddr("1.2.3.4"),
IPv4: netip.MustParseAddr("1.2.3.4"), IPv6: netip.MustParseAddr("1234::cdef"),
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{ return &agd.Profile{
Parental: wantParental, Parental: wantParental,
BlockingMode: wantBlockingMode, BlockingMode: wantBlockingMode,
@ -348,12 +354,14 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
CustomRules: []agd.FilterRuleText{"||example.org^"}, CustomRules: []agd.FilterRuleText{"||example.org^"},
FilteredResponseTTL: 10 * time.Second, FilteredResponseTTL: 10 * time.Second,
SafeBrowsing: wantSafeBrowsing, SafeBrowsing: wantSafeBrowsing,
Access: wantAccess,
RuleListsEnabled: true, RuleListsEnabled: true,
FilteringEnabled: true, FilteringEnabled: true,
QueryLogEnabled: true, QueryLogEnabled: true,
Deleted: false, Deleted: false,
BlockPrivateRelay: true, BlockPrivateRelay: true,
BlockFirefoxCanary: true, BlockFirefoxCanary: true,
IPLogEnabled: true,
} }
} }
@ -390,30 +398,30 @@ func TestSyncTimeFromTrailer(t *testing.T) {
milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10) milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10)
testCases := []struct { testCases := []struct {
in metadata.MD
wantError string wantError string
want time.Time want time.Time
name string name string
in metadata.MD
}{{ }{{
in: metadata.MD{},
wantError: "empty value", wantError: "empty value",
want: time.Time{}, want: time.Time{},
name: "no_key", name: "no_key",
in: metadata.MD{},
}, { }, {
in: metadata.MD{"sync_time": []string{}},
wantError: "empty value", wantError: "empty value",
want: time.Time{}, want: time.Time{},
name: "empty_key", name: "empty_key",
in: metadata.MD{"sync_time": []string{}},
}, { }, {
in: metadata.MD{"sync_time": []string{""}},
wantError: `invalid value: strconv.ParseInt: parsing "": invalid syntax`, wantError: `invalid value: strconv.ParseInt: parsing "": invalid syntax`,
want: time.Time{}, want: time.Time{},
name: "empty_value", name: "empty_value",
in: metadata.MD{"sync_time": []string{""}},
}, { }, {
in: metadata.MD{"sync_time": []string{milliseconds}},
wantError: "", wantError: "",
want: TestUpdTime, want: TestUpdTime,
name: "success", name: "success",
in: metadata.MD{"sync_time": []string{milliseconds}},
}} }}
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
) )
// Common Constants, Types, And Utilities // Common Constants, Types, And Utilities
@ -15,8 +16,8 @@ type Recorder interface {
Record( Record(
ctx context.Context, ctx context.Context,
id agd.DeviceID, id agd.DeviceID,
ctry agd.Country, ctry geoip.Country,
asn agd.ASN, asn geoip.ASN,
start time.Time, start time.Time,
proto agd.Protocol, proto agd.Protocol,
) )
@ -32,8 +33,8 @@ type EmptyRecorder struct{}
func (EmptyRecorder) Record( func (EmptyRecorder) Record(
_ context.Context, _ context.Context,
_ agd.DeviceID, _ agd.DeviceID,
_ agd.Country, _ geoip.Country,
_ agd.ASN, _ geoip.ASN,
_ time.Time, _ time.Time,
_ agd.Protocol, _ agd.Protocol,
) { ) {
@ -51,10 +52,10 @@ type Record struct {
Time time.Time Time time.Time
// Country is the detected country of the client's IP address, if any. // 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 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 // 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 // most recent sync. This value is an int32 to be in sync with the business

View File

@ -7,6 +7,8 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "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/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
@ -51,8 +53,8 @@ var _ Recorder = (*RuntimeRecorder)(nil)
func (r *RuntimeRecorder) Record( func (r *RuntimeRecorder) Record(
ctx context.Context, ctx context.Context,
id agd.DeviceID, id agd.DeviceID,
ctry agd.Country, ctry geoip.Country,
asn agd.ASN, asn geoip.ASN,
start time.Time, start time.Time,
proto agd.Protocol, proto agd.Protocol,
) { ) {
@ -80,10 +82,10 @@ func (r *RuntimeRecorder) Record(
} }
// type check // type check
var _ agd.Refresher = (*RuntimeRecorder)(nil) var _ agdservice.Refresher = (*RuntimeRecorder)(nil)
// Refresh implements the agd.Refresher interface for *RuntimeRecorder. It // Refresh implements the [agdserivce.Refresher] interface for *RuntimeRecorder.
// uploads the currently available data and resets it. // It uploads the currently available data and resets it.
func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) { func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) {
records := r.resetRecords() records := r.resetRecords()

View File

@ -8,6 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -20,10 +21,10 @@ type sig = struct{}
// Common constants for tests. // Common constants for tests.
const ( const (
devID = "dev1234" devID = "dev1234"
proto = agd.ProtoDoH proto = agd.ProtoDoH
clientCtry = agd.CountryAD clientCtry = geoip.CountryAD
clientASN agd.ASN = 42 clientASN geoip.ASN = 42
) )
func TestRuntimeRecorder_success(t *testing.T) { func TestRuntimeRecorder_success(t *testing.T) {

View File

@ -6,7 +6,7 @@ import (
"time" "time"
) )
// Common timeout for tests // testTimeout is a common timeout for tests.
const testTimeout = 1 * time.Second const testTimeout = 1 * time.Second
// Common addresses for tests. // Common addresses for tests.
@ -24,5 +24,4 @@ var (
// Common subnets for tests. // Common subnets for tests.
var ( var (
testSubnetIPv4 = netip.MustParsePrefix("1.2.3.0/24") testSubnetIPv4 = netip.MustParsePrefix("1.2.3.0/24")
testSubnetIPv6 = netip.MustParsePrefix("1234:5678::/64")
) )

View File

@ -3,6 +3,7 @@ package bindtodevice_test
import ( import (
"net/netip" "net/netip"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice" "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
@ -12,6 +13,9 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m) testutil.DiscardLogOutput(m)
} }
// testTimeout is a common timeout for tests.
const testTimeout = 1 * time.Second
// Common interface listener IDs for tests // Common interface listener IDs for tests
const ( const (
testID1 bindtodevice.ID = "id1" testID1 bindtodevice.ID = "id1"

View File

@ -5,8 +5,7 @@ package bindtodevice
import ( import (
"fmt" "fmt"
"net/netip" "net/netip"
"slices"
"golang.org/x/exp/slices"
) )
// connIndex is the data structure that contains the channel listeners and // connIndex is the data structure that contains the channel listeners and

View File

@ -4,10 +4,10 @@ package bindtodevice
import ( import (
"net/netip" "net/netip"
"slices"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
) )
func TestSubnetCompare(t *testing.T) { func TestSubnetCompare(t *testing.T) {

View File

@ -8,24 +8,24 @@ import (
"net" "net"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/agdsync"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/syncutil"
) )
// interfaceListener contains information about a single interface listener. // interfaceListener contains information about a single interface listener.
type interfaceListener struct { type interfaceListener struct {
conns *connIndex conns *connIndex
listenConf *net.ListenConfig listenConf *net.ListenConfig
bodyPool *agdsync.TypedPool[[]byte] bodyPool *syncutil.Pool[[]byte]
oobPool *agdsync.TypedPool[[]byte] oobPool *syncutil.Pool[[]byte]
writeRequests chan *packetConnWriteReq writeRequests chan *packetConnWriteReq
done chan unit done chan unit
errColl agd.ErrorCollector errColl errcoll.Interface
ifaceName string ifaceName string
port uint16 port uint16
} }
@ -37,7 +37,7 @@ func (l *interfaceListener) listenTCP(errCh chan<- error) {
defer log.OnPanic("interfaceListener.listenTCP") defer log.OnPanic("interfaceListener.listenTCP")
ctx := context.Background() 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) tcpListener, err := l.listenConf.Listen(ctx, "tcp", addrStr)
errCh <- err errCh <- err
@ -62,7 +62,7 @@ func (l *interfaceListener) listenTCP(errCh chan<- error) {
var conn net.Conn var conn net.Conn
conn, err = tcpListener.Accept() conn, err = tcpListener.Accept()
if err != nil { if err != nil {
agd.Collectf(ctx, l.errColl, "%s: accepting: %w", logPrefix, err) errcoll.Collectf(ctx, l.errColl, "%s: accepting: %w", logPrefix, err)
continue continue
} }
@ -78,7 +78,7 @@ func (l *interfaceListener) processConn(conn net.Conn, logPrefix string) {
raddr := conn.RemoteAddr() raddr := conn.RemoteAddr()
if lsnr := l.conns.listener(laddr.Addr()); lsnr != nil { if lsnr := l.conns.listener(laddr.Addr()); lsnr != nil {
if !lsnr.send(conn) { 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 return
@ -90,7 +90,7 @@ func (l *interfaceListener) processConn(conn net.Conn, logPrefix string) {
err := conn.Close() err := conn.Close()
if err != nil { 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") defer log.OnPanic("interfaceListener.listenUDP")
ctx := context.Background() 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) packetConn, err := l.listenConf.ListenPacket(ctx, "udp", addrStr)
if err != nil { if err != nil {
errCh <- err errCh <- err
@ -131,7 +131,7 @@ func (l *interfaceListener) listenUDP(errCh chan<- error) {
err = l.readUDP(udpConn, logPrefix) err = l.readUDP(udpConn, logPrefix)
if err != nil { 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) { 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 return nil
@ -194,7 +194,7 @@ func (l *interfaceListener) writeUDP(c *net.UDPConn) {
var req *packetConnWriteReq var req *packetConnWriteReq
select { select {
case <-l.done: case <-l.done:
log.Info("%s: done", logPrefix) optlog.Debug1("%s: done", logPrefix)
return return
case req = <-l.writeRequests: case req = <-l.writeRequests:

View File

@ -6,6 +6,7 @@ import (
"context" "context"
"net" "net"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
) )
@ -19,7 +20,7 @@ import (
type ListenConfig struct { type ListenConfig struct {
packetConn *chanPacketConn packetConn *chanPacketConn
listener *chanListener listener *chanListener
addr string addr *agdnet.PrefixNetAddr
} }
// type check // type check
@ -44,8 +45,7 @@ func (lc *ListenConfig) ListenPacket(
return lc.packetConn, nil return lc.packetConn, nil
} }
// Addr returns the address on which lc accepts connections. See // Addr returns the address on which lc accepts connections. addr.Net is empty.
// [agdnet.FormatPrefixAddr] for the format. func (lc *ListenConfig) Addr() (addr *agdnet.PrefixNetAddr) {
func (lc *ListenConfig) Addr() (addr string) {
return lc.addr return lc.addr
} }

View File

@ -14,7 +14,11 @@ import (
func TestListenConfig(t *testing.T) { func TestListenConfig(t *testing.T) {
pc := newChanPacketConn(nil, testSubnetIPv4, nil, testLAddr) pc := newChanPacketConn(nil, testSubnetIPv4, nil, testLAddr)
lsnr := newChanListener(nil, testSubnetIPv4, testLAddr) lsnr := newChanListener(nil, testSubnetIPv4, testLAddr)
addr := agdnet.FormatPrefixAddr(testSubnetIPv4, 1234) addr := &agdnet.PrefixNetAddr{
Prefix: testSubnetIPv4,
Net: "",
Port: 1234,
}
c := &ListenConfig{ c := &ListenConfig{
packetConn: pc, packetConn: pc,
listener: lsnr, listener: lsnr,

View File

@ -4,9 +4,12 @@ package bindtodevice
import ( import (
"context" "context"
"fmt"
"net" "net"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/errors"
) )
// ListenConfig is a [netext.ListenConfig] implementation that uses the // ListenConfig is a [netext.ListenConfig] implementation that uses the
@ -31,7 +34,10 @@ func (lc *ListenConfig) Listen(
network string, network string,
address string, address string,
) (l net.Listener, err error) { ) (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 // ListenPacket implements the [netext.ListenConfig] interface for
@ -43,13 +49,15 @@ func (lc *ListenConfig) ListenPacket(
network string, network string,
address string, address string,
) (c net.PacketConn, err error) { ) (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 // Addr returns the address on which lc accepts connections.
// [agdnet.FormatPrefixAddr] for the format.
// //
// It is only supported on Linux. // It is only supported on Linux.
func (lc *ListenConfig) Addr() (addr string) { func (lc *ListenConfig) Addr() (addr *agdnet.PrefixNetAddr) {
return "" return nil
} }

View File

@ -1,6 +1,6 @@
package bindtodevice 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 // ManagerConfig is the configuration structure for [NewManager]. All fields
// must be set. // must be set.
@ -11,7 +11,7 @@ type ManagerConfig struct {
// ErrColl is the error collector that is used to collect non-critical // ErrColl is the error collector that is used to collect non-critical
// errors. // errors.
ErrColl agd.ErrorCollector ErrColl errcoll.Interface
// ChannelBufferSize is the size of the buffers of the channels used to // ChannelBufferSize is the size of the buffers of the channels used to
// dispatch TCP connections and UDP sessions. // dispatch TCP connections and UDP sessions.

View File

@ -9,13 +9,14 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "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/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mapsutil" "github.com/AdguardTeam/golibs/mapsutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -24,7 +25,7 @@ type Manager struct {
interfaces InterfaceStorage interfaces InterfaceStorage
closeOnce *sync.Once closeOnce *sync.Once
ifaceListeners map[ID]*interfaceListener ifaceListeners map[ID]*interfaceListener
errColl agd.ErrorCollector errColl errcoll.Interface
done chan unit done chan unit
chanBufSize int chanBufSize int
} }
@ -108,18 +109,10 @@ func (m *Manager) newInterfaceListener(
port uint16, port uint16,
) (l *interfaceListener) { ) (l *interfaceListener) {
return &interfaceListener{ return &interfaceListener{
conns: &connIndex{}, conns: &connIndex{},
listenConf: newListenConfig(ifaceName, ctrlConf), listenConf: newListenConfig(ifaceName, ctrlConf),
bodyPool: agdsync.NewTypedPool(func() (v *[]byte) { bodyPool: syncutil.NewSlicePool[byte](bodySize),
b := make([]byte, bodySize) oobPool: syncutil.NewSlicePool[byte](netext.IPDstOOBSize),
return &b
}),
oobPool: agdsync.NewTypedPool(func() (v *[]byte) {
b := make([]byte, netext.IPDstOOBSize)
return &b
}),
writeRequests: make(chan *packetConnWriteReq, m.chanBufSize), writeRequests: make(chan *packetConnWriteReq, m.chanBufSize),
done: m.done, done: m.done,
errColl: m.errColl, 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) lsnrCh := make(chan net.Conn, m.chanBufSize)
lsnr := newChanListener(lsnrCh, subnet, &prefixNetAddr{ lsnr := newChanListener(lsnrCh, subnet, &agdnet.PrefixNetAddr{
prefix: subnet, Prefix: subnet,
network: "tcp", Net: "tcp",
port: l.port, Port: l.port,
}) })
err = l.conns.addListener(lsnr) 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) sessCh := make(chan *packetSession, m.chanBufSize)
pConn := newChanPacketConn(sessCh, subnet, l.writeRequests, &prefixNetAddr{ pConn := newChanPacketConn(sessCh, subnet, l.writeRequests, &agdnet.PrefixNetAddr{
prefix: subnet, Prefix: subnet,
network: "udp", Net: "udp",
port: l.port, Port: l.port,
}) })
err = l.conns.addPacketConn(pConn) err = l.conns.addPacketConn(pConn)
@ -182,7 +175,11 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
return &ListenConfig{ return &ListenConfig{
packetConn: pConn, packetConn: pConn,
listener: lsnr, listener: lsnr,
addr: agdnet.FormatPrefixAddr(subnet, l.port), addr: &agdnet.PrefixNetAddr{
Prefix: subnet,
Net: "",
Port: l.port,
},
}, nil }, nil
} }
@ -214,13 +211,15 @@ func (m *Manager) validateIfaceSubnet(ifaceName string, subnet netip.Prefix) (er
} }
// type check // 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 implements the [agdservice.Interface] interface for *Manager. If m is
// Start returns nil, since this feature is optional. // nil, Start returns nil, since this feature is optional.
// //
// TODO(a.garipov): Consider an interface solution. // 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 { if m == nil {
return nil return nil
} }
@ -250,8 +249,8 @@ func (m *Manager) Start() (err error) {
return nil return nil
} }
// Shutdown implements the [agd.Service] interface for *Manager. If m is nil, // Shutdown implements the [agdservice.Interface] interface for *Manager. If m
// Shutdown returns nil, since this feature is optional. // is nil, Shutdown returns nil, since this feature is optional.
// //
// TODO(a.garipov): Consider an interface solution. // TODO(a.garipov): Consider an interface solution.
// //

View File

@ -233,10 +233,10 @@ func TestManager(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, lc) require.NotNil(t, lc)
err = m.Start() err = m.Start(agdtest.ContextWithTimeout(t, testTimeout))
require.NoError(t, err) require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) { 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) { t.Run("tcp", func(t *testing.T) {

View File

@ -4,9 +4,10 @@ package bindtodevice
import ( import (
"context" "context"
"fmt"
"net/netip" "net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
) )
@ -22,17 +23,11 @@ func NewManager(c *ManagerConfig) (m *Manager) {
return &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. // Add creates a new interface-listener record in m.
// //
// It is only supported on Linux. // It is only supported on Linux.
func (m *Manager) Add(id ID, ifaceName string, port uint16, cc *ControlConfig) (err error) { 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 // 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. // It is only supported on Linux.
func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err error) { 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 // 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 implements the [agdservice.Interface] interface for *Manager. If m is
// Start returns nil, since this feature is optional. // nil, Start returns nil, since this feature is optional.
// //
// It is only supported on Linux. // It is only supported on Linux.
func (m *Manager) Start() (err error) { func (m *Manager) Start(_ context.Context) (err error) {
if m == nil { if m == nil {
return 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 implements the [agdservice.Interface] interface for *Manager. If m
// Shutdown returns nil, since this feature is optional. // is nil, Shutdown returns nil, since this feature is optional.
// //
// It is only supported on Linux. // It is only supported on Linux.
func (m *Manager) Shutdown(_ context.Context) (err error) { func (m *Manager) Shutdown(_ context.Context) (err error) {
@ -68,5 +66,8 @@ func (m *Manager) Shutdown(_ context.Context) (err error) {
return nil 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"
"net/netip" "net/netip"
"os" "os"
"slices"
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
@ -21,7 +22,6 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
"golang.org/x/sys/unix" "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 // SubtestListenControlTCP is a shared subtest that uses lc to dial a listener
// perform two-way communication using the resulting connection. // and perform two-way communication using the resulting connection.
func SubtestListenControlTCP( func SubtestListenControlTCP(
t *testing.T, t *testing.T,
lc netext.ListenConfig, lc netext.ListenConfig,
@ -120,7 +120,7 @@ func SubtestListenControlTCP(
require.NoError(t, err) require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, lsnr.Close) 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(), "/") addrStr, _, _ := strings.Cut(lsnr.Addr().String(), "/")
addr, err := netip.ParseAddrPort(addrStr) addr, err := netip.ParseAddrPort(addrStr)
require.NoError(t, err) require.NoError(t, err)
@ -201,7 +201,7 @@ func SubtestListenControlUDP(
require.NoError(t, err) require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, packetConn.Close) 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(), "/") addrStr, _, _ := strings.Cut(packetConn.LocalAddr().String(), "/")
addr, err := netip.ParseAddrPort(addrStr) addr, err := netip.ParseAddrPort(addrStr)
require.NoError(t, err) require.NoError(t, err)
@ -501,8 +501,7 @@ func BenchmarkReadPacketSession(b *testing.B) {
Type: unix.IP_ORIGDSTADDR, Type: unix.IP_ORIGDSTADDR,
} }
// TODO(a.garipov): Use binary.NativeEndian in Go 1.21 here and below. err := binary.Write(oobBuf, binary.NativeEndian, ctrlMsgHdr)
err := binary.Write(oobBuf, binary.LittleEndian, ctrlMsgHdr)
require.NoError(b, err) require.NoError(b, err)
pktInfo := unix.Inet4Pktinfo{ pktInfo := unix.Inet4Pktinfo{
@ -510,7 +509,7 @@ func BenchmarkReadPacketSession(b *testing.B) {
Addr: *(*[4]byte)(testRAddr.IP), Addr: *(*[4]byte)(testRAddr.IP),
} }
err = binary.Write(oobBuf, binary.LittleEndian, pktInfo) err = binary.Write(oobBuf, binary.NativeEndian, pktInfo)
require.NoError(b, err) require.NoError(b, err)
oobData := oobBuf.Bytes() oobData := oobBuf.Bytes()

View File

@ -4,12 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/backend"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb" "github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
@ -34,6 +33,10 @@ type backendConfig struct {
// synchronization. // synchronization.
FullRefreshIvl timeutil.Duration `yaml:"full_refresh_interval"` 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 // BillStatIvl defines how often AdGuard DNS sends the billing statistics to
// the backend. // the backend.
BillStatIvl timeutil.Duration `yaml:"bill_stat_interval"` BillStatIvl timeutil.Duration `yaml:"bill_stat_interval"`
@ -50,6 +53,8 @@ func (c *backendConfig) validate() (err error) {
return newMustBePositiveError("refresh_interval", c.RefreshIvl) return newMustBePositiveError("refresh_interval", c.RefreshIvl)
case c.FullRefreshIvl.Duration <= 0: case c.FullRefreshIvl.Duration <= 0:
return newMustBePositiveError("full_refresh_interval", c.FullRefreshIvl) 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: case c.BillStatIvl.Duration <= 0:
return newMustBePositiveError("bill_stat_interval", c.BillStatIvl) return newMustBePositiveError("bill_stat_interval", c.BillStatIvl)
default: default:
@ -64,7 +69,7 @@ func setupBackend(
conf *backendConfig, conf *backendConfig,
envs *environments, envs *environments,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (profDB *profiledb.Default, rec *billstat.RuntimeRecorder, err error) { ) (profDB *profiledb.Default, rec *billstat.RuntimeRecorder, err error) {
rec, err = setupBillStat(conf, envs, sigHdlr, errColl) rec, err = setupBillStat(conf, envs, sigHdlr, errColl)
if err != nil { if err != nil {
@ -87,7 +92,7 @@ func setupBillStat(
conf *backendConfig, conf *backendConfig,
envs *environments, envs *environments,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (rec *billstat.RuntimeRecorder, err error) { ) (rec *billstat.RuntimeRecorder, err error) {
apiURL := netutil.CloneURL(&envs.BillStatURL.URL) apiURL := netutil.CloneURL(&envs.BillStatURL.URL)
billStatUploader, err := setupBillStatUploader(apiURL, errColl) billStatUploader, err := setupBillStatUploader(apiURL, errColl)
@ -102,7 +107,7 @@ func setupBillStat(
refrIvl := conf.RefreshIvl.Duration refrIvl := conf.RefreshIvl.Duration
timeout := conf.Timeout.Duration timeout := conf.Timeout.Duration
billStatRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ billStatRefr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) { Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout) return context.WithTimeout(context.Background(), timeout)
}, },
@ -112,8 +117,9 @@ func setupBillStat(
Interval: refrIvl, Interval: refrIvl,
RefreshOnShutdown: true, RefreshOnShutdown: true,
RoutineLogsAreDebug: true, RoutineLogsAreDebug: true,
RandomizeStart: false,
}) })
err = billStatRefr.Start() err = billStatRefr.Start(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("starting bill stat recorder refresher: %w", err) return nil, fmt.Errorf("starting bill stat recorder refresher: %w", err)
} }
@ -129,7 +135,7 @@ func setupProfDB(
conf *backendConfig, conf *backendConfig,
envs *environments, envs *environments,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (profDB *profiledb.Default, err error) { ) (profDB *profiledb.Default, err error) {
apiURL := netutil.CloneURL(&envs.ProfilesURL.URL) apiURL := netutil.CloneURL(&envs.ProfilesURL.URL)
profStrg, err := setupProfStorage(apiURL, errColl) profStrg, err := setupProfStorage(apiURL, errColl)
@ -137,15 +143,21 @@ func setupProfDB(
return nil, fmt.Errorf("creating profile storage: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("creating default profile database: %w", err) return nil, fmt.Errorf("creating default profile database: %w", err)
} }
refrIvl := conf.RefreshIvl.Duration 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) { Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), timeout) return context.WithTimeout(context.Background(), timeout)
}, },
@ -155,8 +167,9 @@ func setupProfDB(
Interval: refrIvl, Interval: refrIvl,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: true, RoutineLogsAreDebug: true,
RandomizeStart: true,
}) })
err = profDBRefr.Start() err = profDBRefr.Start(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("starting default profile database refresher: %w", err) return nil, fmt.Errorf("starting default profile database refresher: %w", err)
} }
@ -168,8 +181,6 @@ func setupProfDB(
// Backend API URL schemes. // Backend API URL schemes.
const ( const (
schemeHTTP = "http"
schemeHTTPS = "https"
schemeGRPC = "grpc" schemeGRPC = "grpc"
schemeGRPCS = "grpcs" schemeGRPCS = "grpcs"
) )
@ -178,42 +189,32 @@ const (
// provided API URL. // provided API URL.
func setupProfStorage( func setupProfStorage(
apiURL *url.URL, apiURL *url.URL,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (s profiledb.Storage, err error) { ) (s profiledb.Storage, err error) {
switch apiURL.Scheme { scheme := apiURL.Scheme
case schemeGRPC, schemeGRPCS: if scheme == schemeGRPC || scheme == schemeGRPCS {
return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{ return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
Endpoint: apiURL, Endpoint: apiURL,
ErrColl: errColl, 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 // setupBillStatUploader creates and returns a billstat uploader depending on
// the provided API URL. // the provided API URL.
func setupBillStatUploader( func setupBillStatUploader(
apiURL *url.URL, apiURL *url.URL,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (s billstat.Uploader, err error) { ) (s billstat.Uploader, err error) {
switch apiURL.Scheme { scheme := apiURL.Scheme
case schemeGRPC, schemeGRPCS: if scheme == schemeGRPC || scheme == schemeGRPCS {
return backendpb.NewBillStat(&backendpb.BillStatConfig{ return backendpb.NewBillStat(&backendpb.BillStatConfig{
ErrColl: errColl, ErrColl: errColl,
Endpoint: apiURL, 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" "net/url"
"strings" "strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck" "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
@ -44,7 +44,7 @@ type checkConfig struct {
func (c *checkConfig) toInternal( func (c *checkConfig) toInternal(
envs *environments, envs *environments,
messages *dnsmsg.Constructor, messages *dnsmsg.Constructor,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (conf *dnscheck.ConsulConfig) { ) (conf *dnscheck.ConsulConfig) {
var kvURL, sessURL *url.URL var kvURL, sessURL *url.URL
if envs.ConsulDNSCheckKVURL != nil && envs.ConsulDNSCheckSessionURL != nil { 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/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc" "github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck" "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@ -78,7 +79,7 @@ func Main() {
// //
// See AGDNS-884. // See AGDNS-884.
geoIP, geoIPRefr := &geoip.File{}, &agd.RefreshWorker{} geoIP, geoIPRefr := &geoip.File{}, &agdservice.RefreshWorker{}
geoIPErrCh := make(chan error, 1) geoIPErrCh := make(chan error, 1)
go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl) 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 // TODO(ameshkov): Consider making a separated max_size config for
// safe-browsing and adult-blocking filters. // 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( safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter(
c.SafeBrowsing, c.SafeBrowsing,
filteringResolver, filteringResolver,
cloner,
agd.FilterListIDSafeBrowsing, agd.FilterListIDSafeBrowsing,
envs.SafeBrowsingURL, envs.SafeBrowsingURL,
envs.FilterCachePath, envs.FilterCachePath,
@ -110,6 +113,7 @@ func Main() {
adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter( adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter(
c.AdultBlocking, c.AdultBlocking,
filteringResolver, filteringResolver,
cloner,
agd.FilterListIDAdultBlocking, agd.FilterListIDAdultBlocking,
envs.AdultBlockingURL, envs.AdultBlockingURL,
envs.FilterCachePath, envs.FilterCachePath,
@ -123,6 +127,7 @@ func Main() {
// Reuse general safe browsing filter configuration. // Reuse general safe browsing filter configuration.
c.SafeBrowsing, c.SafeBrowsing,
filteringResolver, filteringResolver,
cloner,
agd.FilterListIDNewRegDomains, agd.FilterListIDNewRegDomains,
envs.NewRegDomainsURL, envs.NewRegDomainsURL,
envs.FilterCachePath, envs.FilterCachePath,
@ -137,6 +142,7 @@ func Main() {
fltStrgConf := c.Filters.toInternal( fltStrgConf := c.Filters.toInternal(
errColl, errColl,
filteringResolver, filteringResolver,
cloner,
envs, envs,
safeBrowsingFilter, safeBrowsingFilter,
adultBlockingFilter, adultBlockingFilter,
@ -150,30 +156,37 @@ func Main() {
fltGroups, err := c.FilteringGroups.toInternal(fltStrg) fltGroups, err := c.FilteringGroups.toInternal(fltStrg)
check(err) 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() btdCtrlConf, ctrlConf := c.Network.toInternal()
btdMgr, err := c.InterfaceListeners.toInternal(errColl, btdCtrlConf) btdMgr, err := c.InterfaceListeners.toInternal(errColl, btdCtrlConf)
check(err) 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) check(err)
sigHdlr.add(btdMgr) 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 // TLS keys logging
if envs.SSLKeyLogFile != "" { if envs.SSLKeyLogFile != "" {
@ -227,14 +240,13 @@ func Main() {
webSvc := websvc.New(webConf) webSvc := websvc.New(webConf)
// The web service is considered critical, so its Start method panics // The web service is considered critical, so its Start method panics
// instead of returning an error. // instead of returning an error.
_ = webSvc.Start() _ = webSvc.Start(context.Background())
sigHdlr.add(webSvc) sigHdlr.add(webSvc)
// DNS service // DNS service
fwdConf, err := c.Upstream.toInternal() fwdConf := c.Upstream.toInternal()
check(err)
handler := forward.NewHandler(fwdConf) handler := forward.NewHandler(fwdConf)
@ -246,8 +258,11 @@ func Main() {
} }
dnsConf := &dnssvc.Config{ dnsConf := &dnssvc.Config{
AccessManager: accessManager,
Messages: messages, Messages: messages,
Cloner: cloner,
ControlConf: ctrlConf,
ConnLimiter: connLimiter,
AccessManager: accessGlobal,
SafeBrowsing: hashprefix.NewMatcher(hashStorages), SafeBrowsing: hashprefix.NewMatcher(hashStorages),
BillStat: billStatRec, BillStat: billStatRec,
ProfileDB: profDB, ProfileDB: profDB,
@ -261,9 +276,9 @@ func Main() {
QueryLog: c.buildQueryLog(envs), QueryLog: c.buildQueryLog(envs),
RuleStat: ruleStat, RuleStat: ruleStat,
RateLimit: rateLimiter, RateLimit: rateLimiter,
ConnLimiter: connLimiter,
FilteringGroups: fltGroups, FilteringGroups: fltGroups,
ServerGroups: srvGrps, ServerGroups: srvGrps,
HandleTimeout: c.DNS.HandleTimeout.Duration,
CacheSize: c.Cache.Size, CacheSize: c.Cache.Size,
ECSCacheSize: c.Cache.ECSSize, ECSCacheSize: c.Cache.ECSSize,
CacheMinTTL: c.Cache.TTLOverride.Min.Duration, CacheMinTTL: c.Cache.TTLOverride.Min.Duration,
@ -271,7 +286,6 @@ func Main() {
UseECSCache: c.Cache.Type == cacheTypeECS, UseECSCache: c.Cache.Type == cacheTypeECS,
ResearchMetrics: bool(envs.ResearchMetrics), ResearchMetrics: bool(envs.ResearchMetrics),
ResearchLogs: bool(envs.ResearchLogs), ResearchLogs: bool(envs.ResearchLogs),
ControlConf: ctrlConf,
} }
dnsSvc, err := dnssvc.New(dnsConf) dnsSvc, err := dnssvc.New(dnsConf)
@ -283,14 +297,14 @@ func Main() {
check(err) check(err)
upstreamHealthcheckUpd := newUpstreamHealthcheck(handler, c.Upstream, errColl) upstreamHealthcheckUpd := newUpstreamHealthcheck(handler, c.Upstream, errColl)
err = upstreamHealthcheckUpd.Start() err = upstreamHealthcheckUpd.Start(context.Background())
check(err) check(err)
sigHdlr.add(upstreamHealthcheckUpd) sigHdlr.add(upstreamHealthcheckUpd)
// The DNS service is considered critical, so its Start method panics // The DNS service is considered critical, so its Start method panics
// instead of returning an error. // instead of returning an error.
_ = dnsSvc.Start() _ = dnsSvc.Start(context.Background())
sigHdlr.add(dnsSvc) sigHdlr.add(dnsSvc)
@ -300,7 +314,7 @@ func Main() {
// The debug HTTP service is considered critical, so its Start method panics // The debug HTTP service is considered critical, so its Start method panics
// instead of returning an error. // instead of returning an error.
_ = debugSvc.Start() _ = debugSvc.Start(context.Background())
sigHdlr.add(debugSvc) sigHdlr.add(debugSvc)
@ -320,7 +334,7 @@ func Main() {
// //
// TODO(a.garipov): Consider making into a helper in package agd and using // TODO(a.garipov): Consider making into a helper in package agd and using
// everywhere. // everywhere.
func collectPanics(errColl agd.ErrorCollector) { func collectPanics(errColl errcoll.Interface) {
v := recover() v := recover()
if v == nil { if v == nil {
return return

View File

@ -33,6 +33,9 @@ type configuration struct {
// DNSDB is the configuration of DNSDB buffer. // DNSDB is the configuration of DNSDB buffer.
DNSDB *dnsDBConfig `yaml:"dnsdb"` 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 // Backend is the AdGuard HTTP backend service configuration. See the
// environments type for more backend parameters. // environments type for more backend parameters.
Backend *backendConfig `yaml:"backend"` Backend *backendConfig `yaml:"backend"`
@ -123,6 +126,9 @@ func (c *configuration) validate() (err error) {
}, { }, {
validate: c.DNSDB.validate, validate: c.DNSDB.validate,
name: "dnsdb", name: "dnsdb",
}, {
validate: c.DNS.validate,
name: "dns",
}, { }, {
validate: c.Backend.validate, validate: c.Backend.validate,
name: "backend", name: "backend",

View File

@ -86,7 +86,7 @@ func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) (err error)
func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) { func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) {
for _, srvGrp := range serverGroups { for _, srvGrp := range serverGroups {
for _, s := range srvGrp.Servers { for _, s := range srvGrp.Servers {
if containsIPv6BindAddress(s.BindData) { if s.HasIPv6() {
return true return true
} }
} }
@ -94,15 +94,3 @@ func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) {
return false 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 package cmd
import ( import (
"cmp"
"fmt" "fmt"
"net/netip" "net/netip"
"slices"
"strings" "strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
@ -11,7 +13,6 @@ import (
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/exp/slices"
) )
// Discovery Of Designated Resolvers (DDR) configuration // Discovery Of Designated Resolvers (DDR) configuration
@ -57,16 +58,8 @@ func ddrRecsToSVCBTmpls(
tmpls = appendDDRSVCBTmpls(tmpls, msgs, r, target) 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) { slices.SortStableFunc(tmpls, func(a, b *dns.SVCB) (res int) {
switch x, y := a.Priority, b.Priority; { return cmp.Compare(a.Priority, b.Priority)
case x < y:
return -1
case x > y:
return +1
default:
return 0
}
}) })
return targets, tmpls 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 package cmd
import ( import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
) )
// DNSDB Configuration.
// dnsDBConfig is the configuration of the DNSDB module. // dnsDBConfig is the configuration of the DNSDB module.
type dnsDBConfig struct { type dnsDBConfig struct {
// MaxSize is the maximum amount of records in the memory buffer. // 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. // 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 { if !c.Enabled {
return dnsdb.Empty{} return dnsdb.Empty{}
} }

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -8,7 +9,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "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/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
@ -16,6 +17,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat" "github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/caarlos0/env/v7" "github.com/caarlos0/env/v7"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
) )
@ -24,20 +26,20 @@ import (
// environments represents the configuration that is kept in the environment. // environments represents the configuration that is kept in the environment.
type environments struct { type environments struct {
AdultBlockingURL *agdhttp.URL `env:"ADULT_BLOCKING_URL,notEmpty"` AdultBlockingURL *urlutil.URL `env:"ADULT_BLOCKING_URL,notEmpty"`
BillStatURL *agdhttp.URL `env:"BILLSTAT_URL,notEmpty"` BillStatURL *urlutil.URL `env:"BILLSTAT_URL,notEmpty"`
BlockedServiceIndexURL *agdhttp.URL `env:"BLOCKED_SERVICE_INDEX_URL,notEmpty"` BlockedServiceIndexURL *urlutil.URL `env:"BLOCKED_SERVICE_INDEX_URL,notEmpty"`
ConsulAllowlistURL *agdhttp.URL `env:"CONSUL_ALLOWLIST_URL,notEmpty"` ConsulAllowlistURL *urlutil.URL `env:"CONSUL_ALLOWLIST_URL,notEmpty"`
ConsulDNSCheckKVURL *agdhttp.URL `env:"CONSUL_DNSCHECK_KV_URL"` ConsulDNSCheckKVURL *urlutil.URL `env:"CONSUL_DNSCHECK_KV_URL"`
ConsulDNSCheckSessionURL *agdhttp.URL `env:"CONSUL_DNSCHECK_SESSION_URL"` ConsulDNSCheckSessionURL *urlutil.URL `env:"CONSUL_DNSCHECK_SESSION_URL"`
FilterIndexURL *agdhttp.URL `env:"FILTER_INDEX_URL,notEmpty"` FilterIndexURL *urlutil.URL `env:"FILTER_INDEX_URL,notEmpty"`
GeneralSafeSearchURL *agdhttp.URL `env:"GENERAL_SAFE_SEARCH_URL,notEmpty"` GeneralSafeSearchURL *urlutil.URL `env:"GENERAL_SAFE_SEARCH_URL,notEmpty"`
LinkedIPTargetURL *agdhttp.URL `env:"LINKED_IP_TARGET_URL"` LinkedIPTargetURL *urlutil.URL `env:"LINKED_IP_TARGET_URL"`
NewRegDomainsURL *agdhttp.URL `env:"NEW_REG_DOMAINS_URL,notEmpty"` NewRegDomainsURL *urlutil.URL `env:"NEW_REG_DOMAINS_URL,notEmpty"`
ProfilesURL *agdhttp.URL `env:"PROFILES_URL,notEmpty"` ProfilesURL *urlutil.URL `env:"PROFILES_URL,notEmpty"`
RuleStatURL *agdhttp.URL `env:"RULESTAT_URL"` RuleStatURL *urlutil.URL `env:"RULESTAT_URL"`
SafeBrowsingURL *agdhttp.URL `env:"SAFE_BROWSING_URL,notEmpty"` SafeBrowsingURL *urlutil.URL `env:"SAFE_BROWSING_URL,notEmpty"`
YoutubeSafeSearchURL *agdhttp.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"` YoutubeSafeSearchURL *urlutil.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"`
ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"` ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"`
FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"` 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"` 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"` LogTimestamp strictBool `env:"LOG_TIMESTAMP" envDefault:"1"`
LogVerbose strictBool `env:"VERBOSE" envDefault:"0"` LogVerbose strictBool `env:"VERBOSE" envDefault:"0"`
@ -84,7 +86,7 @@ func (envs *environments) configureLogs() {
} }
// buildErrColl builds and returns an error collector from environment. // 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 dsn := envs.SentryDSN
if dsn == "stderr" { if dsn == "stderr" {
return errcoll.NewWriterErrorCollector(os.Stderr), nil 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. // registers its refresher in sigHdlr, if necessary.
func (envs *environments) buildRuleStat( func (envs *environments) buildRuleStat(
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (r rulestat.Interface, err error) { ) (r rulestat.Interface, err error) {
if envs.RuleStatURL == nil { if envs.RuleStatURL == nil {
log.Info("main: warning: not collecting rule stats") log.Info("main: warning: not collecting rule stats")
@ -169,7 +171,7 @@ func (envs *environments) buildRuleStat(
URL: &envs.RuleStatURL.URL, URL: &envs.RuleStatURL.URL,
}) })
refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: httpRuleStat, Refresher: httpRuleStat,
ErrColl: errColl, ErrColl: errColl,
@ -178,8 +180,9 @@ func (envs *environments) buildRuleStat(
Interval: 10 * time.Minute, Interval: 10 * time.Minute,
RefreshOnShutdown: true, RefreshOnShutdown: true,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
}) })
err = refr.Start() err = refr.Start(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("starting rulestat refresher: %w", err) return nil, fmt.Errorf("starting rulestat refresher: %w", err)
} }

View File

@ -5,8 +5,10 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "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"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -49,8 +51,9 @@ type filtersConfig struct {
// toInternal converts c to the filter storage configuration for the DNS server. // toInternal converts c to the filter storage configuration for the DNS server.
// cacheDir must exist. c is assumed to be valid. // cacheDir must exist. c is assumed to be valid.
func (c *filtersConfig) toInternal( func (c *filtersConfig) toInternal(
errColl agd.ErrorCollector, errColl errcoll.Interface,
resolver agdnet.Resolver, resolver agdnet.Resolver,
cloner *dnsmsg.Cloner,
envs *environments, envs *environments,
safeBrowsing *hashprefix.Filter, safeBrowsing *hashprefix.Filter,
adultBlocking *hashprefix.Filter, adultBlocking *hashprefix.Filter,
@ -67,6 +70,7 @@ func (c *filtersConfig) toInternal(
Now: time.Now, Now: time.Now,
ErrColl: errColl, ErrColl: errColl,
Resolver: resolver, Resolver: resolver,
Cloner: cloner,
CacheDir: envs.FilterCachePath, CacheDir: envs.FilterCachePath,
CustomFilterCacheSize: c.CustomFilterCacheSize, CustomFilterCacheSize: c.CustomFilterCacheSize,
SafeSearchCacheSize: c.SafeSearchCacheSize, SafeSearchCacheSize: c.SafeSearchCacheSize,
@ -75,7 +79,7 @@ func (c *filtersConfig) toInternal(
RuleListCacheSize: c.RuleListCache.Size, RuleListCacheSize: c.RuleListCache.Size,
RefreshIvl: c.RefreshIvl.Duration, RefreshIvl: c.RefreshIvl.Duration,
UseRuleListCache: c.RuleListCache.Enabled, 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( func setupFilterStorage(
conf *filter.DefaultStorageConfig, conf *filter.DefaultStorageConfig,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
refreshTimeout time.Duration, refreshTimeout time.Duration,
) (strg *filter.DefaultStorage, err error) { ) (strg *filter.DefaultStorage, err error) {
strg, err = filter.NewDefaultStorage(conf) strg, err = filter.NewDefaultStorage(conf)
@ -141,7 +145,7 @@ func setupFilterStorage(
return nil, fmt.Errorf("creating default filter storage: %w", err) 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) { Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), refreshTimeout) return context.WithTimeout(context.Background(), refreshTimeout)
}, },
@ -151,8 +155,9 @@ func setupFilterStorage(
Interval: conf.RefreshIvl, Interval: conf.RefreshIvl,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
}) })
err = refr.Start() err = refr.Start(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("starting default filter storage update: %w", err) return nil, fmt.Errorf("starting default filter storage update: %w", err)
} }

View File

@ -1,9 +1,11 @@
package cmd package cmd
import ( import (
"context"
"fmt" "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/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/timeutil" "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. // refresher have been created successfully or an error if not.
func setupGeoIP( func setupGeoIP(
geoIPPtr *geoip.File, geoIPPtr *geoip.File,
refrPtr *agd.RefreshWorker, refrPtr *agdservice.RefreshWorker,
errCh chan<- error, errCh chan<- error,
conf *geoIPConfig, conf *geoIPConfig,
envs *environments, envs *environments,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) { ) {
geoIP, err := envs.geoIP(conf) geoIP, err := envs.geoIP(conf)
if err != nil { if err != nil {
@ -60,7 +62,7 @@ func setupGeoIP(
return return
} }
refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: geoIP, Refresher: geoIP,
ErrColl: errColl, ErrColl: errColl,
@ -68,8 +70,9 @@ func setupGeoIP(
Interval: conf.RefreshIvl.Duration, Interval: conf.RefreshIvl.Duration,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
}) })
err = refr.Start() err = refr.Start(context.Background())
if err != nil { if err != nil {
errCh <- fmt.Errorf("starting geoip refresher: %w", err) errCh <- fmt.Errorf("starting geoip refresher: %w", err)

View File

@ -1,8 +1,8 @@
package cmd package cmd
import ( import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice" "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mapsutil" "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. // toInternal converts c to a bindtodevice.Manager. c is assumed to be valid.
func (c *interfaceListenersConfig) toInternal( func (c *interfaceListenersConfig) toInternal(
errColl agd.ErrorCollector, errColl errcoll.Interface,
ctrlConf *bindtodevice.ControlConfig, ctrlConf *bindtodevice.ControlConfig,
) (m *bindtodevice.Manager, err error) { ) (m *bindtodevice.Manager, err error) {
if c == nil { if c == nil {

View File

@ -1,14 +1,16 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter" "github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
"github.com/AdguardTeam/AdGuardDNS/internal/consul" "github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
@ -32,22 +34,28 @@ type rateLimitConfig struct {
// Rate limit options for IPv6 addresses. // Rate limit options for IPv6 addresses.
IPv6 *rateLimitOptions `yaml:"ipv6"` 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 // ResponseSizeEstimate is the size of the estimate of the size of one DNS
// response for the purposes of rate limiting. Responses over this estimate // response for the purposes of rate limiting. Responses over this estimate
// are counted as several responses. // are counted as several responses.
ResponseSizeEstimate datasize.ByteSize `yaml:"response_size_estimate"` 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. // 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. // 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. // 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, if true, makes the server refuse DNS * queries.
RefuseANY bool `yaml:"refuse_any"` 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. // toInternal converts c to the rate limiting configuration for the DNS server.
// c is assumed to be valid. // c is assumed to be valid.
func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.BackOffConfig) { func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.BackoffConfig) {
return &ratelimit.BackOffConfig{ return &ratelimit.BackoffConfig{
Allowlist: al, Allowlist: al,
ResponseSizeEstimate: int(c.ResponseSizeEstimate.Bytes()), ResponseSizeEstimate: int(c.ResponseSizeEstimate.Bytes()),
Duration: c.BackOffDuration.Duration, Duration: c.BackoffDuration.Duration,
Period: c.BackOffPeriod.Duration, Period: c.BackoffPeriod.Duration,
IPv4RPS: c.IPv4.RPS, IPv4RPS: c.IPv4.RPS,
IPv4SubnetKeyLen: c.IPv4.SubnetKeyLen, IPv4SubnetKeyLen: c.IPv4.SubnetKeyLen,
IPv6RPS: c.IPv6.RPS, IPv6RPS: c.IPv6.RPS,
IPv6SubnetKeyLen: c.IPv6.SubnetKeyLen, IPv6SubnetKeyLen: c.IPv6.SubnetKeyLen,
Count: c.BackOffCount, Count: c.BackoffCount,
RefuseANY: c.RefuseANY, RefuseANY: c.RefuseANY,
} }
} }
@ -111,25 +119,15 @@ func (c *rateLimitConfig) validate() (err error) {
return fmt.Errorf("allowlist: %w", errNilConfig) 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( return coalesceError(
validatePositive("back_off_count", c.BackOffCount), validateProp("connection_limit", c.ConnectionLimit.validate),
validatePositive("back_off_duration", c.BackOffDuration), validateProp("ipv4", c.IPv4.validate),
validatePositive("back_off_period", c.BackOffPeriod), 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("response_size_estimate", c.ResponseSizeEstimate),
validatePositive("allowlist.refresh_interval", c.Allowlist.RefreshIvl), validatePositive("allowlist.refresh_interval", c.Allowlist.RefreshIvl),
) )
@ -141,8 +139,8 @@ func setupRateLimiter(
conf *rateLimitConfig, conf *rateLimitConfig,
consulAllowlist *url.URL, consulAllowlist *url.URL,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (rateLimiter *ratelimit.BackOff, connLimiter *connlimiter.Limiter, err error) { ) (rateLimiter *ratelimit.Backoff, connLimiter *connlimiter.Limiter, err error) {
allowSubnets, err := agdnet.ParseSubnets(conf.Allowlist.List...) allowSubnets, err := agdnet.ParseSubnets(conf.Allowlist.List...)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("parsing allowlist subnets: %w", err) 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) return nil, nil, fmt.Errorf("creating allowlist refresher: %w", err)
} }
refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: refresher, Refresher: refresher,
ErrColl: errColl, ErrColl: errColl,
@ -162,15 +160,17 @@ func setupRateLimiter(
Interval: conf.Allowlist.RefreshIvl.Duration, Interval: conf.Allowlist.RefreshIvl.Duration,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
}) })
err = refr.Start()
err = refr.Start(context.Background())
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("starting allowlist refresher: %w", err) return nil, nil, fmt.Errorf("starting allowlist refresher: %w", err)
} }
sigHdlr.add(refr) 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 // connLimitConfig is the configuration structure for the stream-connection
@ -229,3 +229,41 @@ func (c *connLimitConfig) validate() (err error) {
return nil 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 package cmd
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
@ -37,12 +41,13 @@ type safeBrowsingConfig struct {
// toInternal converts c to the safe browsing filter configuration for the // toInternal converts c to the safe browsing filter configuration for the
// filter storage of the DNS server. c is assumed to be valid. // filter storage of the DNS server. c is assumed to be valid.
func (c *safeBrowsingConfig) toInternal( func (c *safeBrowsingConfig) toInternal(
errColl agd.ErrorCollector, errColl errcoll.Interface,
resolver agdnet.Resolver, resolver agdnet.Resolver,
cloner *dnsmsg.Cloner,
id agd.FilterListID, id agd.FilterListID,
url *agdhttp.URL, url *urlutil.URL,
cacheDir string, cacheDir string,
maxSize int64, maxSize uint64,
) (fltConf *hashprefix.FilterConfig, err error) { ) (fltConf *hashprefix.FilterConfig, err error) {
hashes, err := hashprefix.NewStorage("") hashes, err := hashprefix.NewStorage("")
if err != nil { if err != nil {
@ -50,6 +55,7 @@ func (c *safeBrowsingConfig) toInternal(
} }
return &hashprefix.FilterConfig{ return &hashprefix.FilterConfig{
Cloner: cloner,
Hashes: hashes, Hashes: hashes,
URL: netutil.CloneURL(&url.URL), URL: netutil.CloneURL(&url.URL),
ErrColl: errColl, ErrColl: errColl,
@ -88,14 +94,15 @@ func (c *safeBrowsingConfig) validate() (err error) {
func setupHashPrefixFilter( func setupHashPrefixFilter(
conf *safeBrowsingConfig, conf *safeBrowsingConfig,
resolver *agdnet.CachingResolver, resolver *agdnet.CachingResolver,
cloner *dnsmsg.Cloner,
id agd.FilterListID, id agd.FilterListID,
url *agdhttp.URL, url *urlutil.URL,
cachePath string, cachePath string,
maxSize int64, maxSize uint64,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (strg *hashprefix.Storage, flt *hashprefix.Filter, err error) { ) (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 { if err != nil {
return nil, nil, fmt.Errorf("configuring hash prefix filter %s: %w", id, err) 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) 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, Context: ctxWithDefaultTimeout,
Refresher: flt, Refresher: flt,
ErrColl: errColl, ErrColl: errColl,
@ -113,8 +120,9 @@ func setupHashPrefixFilter(
Interval: fltConf.Staleness, Interval: fltConf.Staleness,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: false, RoutineLogsAreDebug: false,
RandomizeStart: false,
}) })
err = refr.Start() err = refr.Start(context.Background())
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("starting refresher for hash prefix filter %s: %w", id, err) 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" "github.com/AdguardTeam/golibs/stringutil"
) )
// Server configuration
// toInternal returns the configuration of DNS servers for a single server // 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( func (srvs servers) toInternal(
tlsConfig *agd.TLS, tlsConfig *agd.TLS,
btdMgr *bindtodevice.Manager, btdMgr *bindtodevice.Manager,
ratelimitConf *rateLimitConfig,
dnsConf *dnsConfig,
) (dnsSrvs []*agd.Server, err error) { ) (dnsSrvs []*agd.Server, err error) {
dnsSrvs = make([]*agd.Server, 0, len(srvs)) dnsSrvs = make([]*agd.Server, 0, len(srvs))
for _, srv := range srvs { for _, srv := range srvs {
@ -28,29 +28,41 @@ func (srvs servers) toInternal(
} }
name := agd.ServerName(srv.Name) name := agd.ServerName(srv.Name)
switch p := srv.Protocol; p { dnsSrv := &agd.Server{
case srvProtoDNS: Name: name,
dnsSrvs = append(dnsSrvs, &agd.Server{ ReadTimeout: dnsConf.ReadTimeout.Duration,
Name: name, WriteTimeout: dnsConf.WriteTimeout.Duration,
BindData: bindData, LinkedIPEnabled: srv.LinkedIPEnabled,
Protocol: agd.ProtoDNS, Protocol: srv.Protocol.toInternal(),
LinkedIPEnabled: srv.LinkedIPEnabled, }
})
case srvProtoDNSCrypt: 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 var dcConf *agd.DNSCryptConfig
dcConf, err = srv.DNSCrypt.toInternal() dcConf, err = srv.DNSCrypt.toInternal()
if err != nil { if err != nil {
return nil, fmt.Errorf("server %q: dnscrypt: %w", srv.Name, err) return nil, fmt.Errorf("server %q: dnscrypt: %w", srv.Name, err)
} }
dnsSrvs = append(dnsSrvs, &agd.Server{ dnsSrv.DNSCrypt = dcConf
DNSCrypt: dcConf,
Name: name,
BindData: bindData,
Protocol: agd.ProtoDNSCrypt,
LinkedIPEnabled: srv.LinkedIPEnabled,
})
default: default:
dnsSrv.TCPConf = tcpConf
dnsSrv.QUICConf = &agd.QUICConfig{
MaxStreamsPerPeer: ratelimitConf.QUIC.MaxStreamsPerPeer,
QUICLimitsEnabled: ratelimitConf.QUIC.Enabled,
}
tlsConf := tlsConfig.Conf.Clone() tlsConf := tlsConfig.Conf.Clone()
// Attach the functions that will count TLS handshake metrics. // Attach the functions that will count TLS handshake metrics.
@ -62,14 +74,12 @@ func (srvs servers) toInternal(
tlsConf.Certificates, tlsConf.Certificates,
) )
dnsSrvs = append(dnsSrvs, &agd.Server{ dnsSrv.TLS = tlsConf
TLS: tlsConf,
Name: name,
BindData: bindData,
Protocol: p.toInternal(),
LinkedIPEnabled: srv.LinkedIPEnabled,
})
} }
dnsSrv.SetBindData(bindData)
dnsSrvs = append(dnsSrvs, dnsSrv)
} }
return dnsSrvs, nil 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 // 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) { func (p serverProto) toInternal() (sp agd.Protocol) {
switch p { switch p {
case srvProtoDNS:
return agd.ProtoDNS
case srvProtoDNSCrypt:
return agd.ProtoDNSCrypt
case srvProtoHTTPS: case srvProtoHTTPS:
return agd.ProtoDoH return agd.ProtoDoH
case srvProtoQUIC: case srvProtoQUIC:
@ -215,7 +229,7 @@ func (s *server) bindData(
bindData = append(bindData, &agd.ServerBindData{ bindData = append(bindData, &agd.ServerBindData{
ListenConfig: lc, ListenConfig: lc,
Address: lc.Addr(), PrefixAddr: lc.Addr(),
}) })
} }
} }

View File

@ -10,18 +10,19 @@ import (
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
) )
// Server Group Configuration
// serverGroups are the DNS server groups. A valid instance of serverGroups has // serverGroups are the DNS server groups. A valid instance of serverGroups has
// no nil items. // no nil items.
type serverGroups []*serverGroup type serverGroups []*serverGroup
// toInternal returns the configuration for all server groups in the DNS // 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( func (srvGrps serverGroups) toInternal(
messages *dnsmsg.Constructor, messages *dnsmsg.Constructor,
btdMgr *bindtodevice.Manager, btdMgr *bindtodevice.Manager,
fltGrps map[agd.FilteringGroupID]*agd.FilteringGroup, fltGrps map[agd.FilteringGroupID]*agd.FilteringGroup,
ratelimitConf *rateLimitConfig,
dnsConf *dnsConfig,
) (svcSrvGrps []*agd.ServerGroup, err error) { ) (svcSrvGrps []*agd.ServerGroup, err error) {
svcSrvGrps = make([]*agd.ServerGroup, len(srvGrps)) svcSrvGrps = make([]*agd.ServerGroup, len(srvGrps))
for i, g := range srvGrps { for i, g := range srvGrps {
@ -44,7 +45,7 @@ func (srvGrps serverGroups) toInternal(
FilteringGroup: fltGrpID, 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 { if err != nil {
return nil, fmt.Errorf("server group %q: %w", g.Name, err) return nil, fmt.Errorf("server group %q: %w", g.Name, err)
} }

View File

@ -6,7 +6,7 @@ import (
"os/signal" "os/signal"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -17,7 +17,7 @@ type signalHandler struct {
// services are the services that are shut down before application // services are the services that are shut down before application
// exiting. // exiting.
services []agd.Service services []agdservice.Interface
} }
// newSignalHandler returns a new signalHandler that shuts down services. // 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. // 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) h.services = append(h.services, s)
} }

View File

@ -7,15 +7,17 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "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/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/slices"
) )
// TLS Configuration And Utilities // TLS Configuration And Utilities
@ -217,9 +219,6 @@ func newTicketRotator(grps []*agd.ServerGroup) (tr *ticketRotator, err error) {
return tr, nil return tr, nil
} }
// type check
var _ agd.Refresher = (*ticketRotator)(nil)
// sessTickLen is the length of a single TLS session ticket key in bytes. // 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 // 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. // 48 bytes of the hashed data as the key name, the AES key, and the HMAC key.
const sessTickLen = 32 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) { func (r *ticketRotator) Refresh(_ context.Context) (err error) {
for conf, files := range r.confs { for conf, files := range r.confs {
keys := make([][sessTickLen]byte, 0, len(files)) keys := make([][sessTickLen]byte, 0, len(files))
@ -299,14 +301,14 @@ func enableTLSKeyLogging(grps []*agd.ServerGroup, keyLogFileName string) (err er
func setupTicketRotator( func setupTicketRotator(
srvGrps []*agd.ServerGroup, srvGrps []*agd.ServerGroup,
sigHdlr signalHandler, sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (err error) { ) (err error) {
tickRot, err := newTicketRotator(srvGrps) tickRot, err := newTicketRotator(srvGrps)
if err != nil { if err != nil {
return fmt.Errorf("setting up ticket rotator: %w", err) return fmt.Errorf("setting up ticket rotator: %w", err)
} }
refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: tickRot, Refresher: tickRot,
ErrColl: errColl, ErrColl: errColl,
@ -315,8 +317,9 @@ func setupTicketRotator(
Interval: 1 * time.Minute, Interval: 1 * time.Minute,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: true, RoutineLogsAreDebug: true,
RandomizeStart: false,
}) })
err = refr.Start() err = refr.Start(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("starting ticket rotator refresh: %w", err) return fmt.Errorf("starting ticket rotator refresh: %w", err)
} }

View File

@ -8,41 +8,39 @@ import (
"strings" "strings"
"time" "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/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
// DNS upstream configuration // DNS upstream configuration
// upstreamConfig module configuration // upstreamConfig is the upstream module configuration.
type upstreamConfig struct { type upstreamConfig struct {
// Healthcheck contains the upstream healthcheck configuration. // Healthcheck contains the upstream healthcheck configuration.
Healthcheck *upstreamHealthcheckConfig `yaml:"healthcheck"` Healthcheck *upstreamHealthcheckConfig `yaml:"healthcheck"`
// Server is the upstream url of the server we're using to forward DNS // Fallback is the configuration for the upstream fallback servers.
// queries. It starts with tcp://, udp://, or with an IP address. Fallback *upstreamFallbackConfig `yaml:"fallback"`
Server string `yaml:"server"`
// FallbackServers is a list of the DNS servers we're using to fallback to // Servers is a list of the upstream servers configurations we use to
// when the upstream server fails to respond // forward DNS queries.
FallbackServers []netip.AddrPort `yaml:"fallback"` Servers []*upstreamServerConfig `yaml:"servers"`
// Timeout is the timeout for DNS requests to the upstreams.
Timeout timeutil.Duration `yaml:"timeout"`
} }
// toInternal converts c to the data storage configuration for the DNS server. // toInternal converts c to the data storage configuration for the DNS server.
func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig, err error) { // c is assumed to be valid.
network, addrPort, err := splitUpstreamURL(c.Server) func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig) {
if err != nil { upstreams := c.Servers
return nil, err fallbacks := c.Fallback.Servers
}
fallbacks := c.FallbackServers upsConfs := toUpstreamConfigs(upstreams)
metricsListener := prometheus.NewForwardMetricsListener(len(fallbacks) + 1) fallbackConfs := toUpstreamConfigs(fallbacks)
metricsListener := prometheus.NewForwardMetricsListener(len(upstreams) + len(fallbacks))
var hcInit time.Duration var hcInit time.Duration
if c.Healthcheck.Enabled { if c.Healthcheck.Enabled {
@ -50,17 +48,15 @@ func (c *upstreamConfig) toInternal() (fwdConf *forward.HandlerConfig, err error
} }
fwdConf = &forward.HandlerConfig{ fwdConf = &forward.HandlerConfig{
Address: addrPort,
Network: network,
MetricsListener: metricsListener, MetricsListener: metricsListener,
HealthcheckDomainTmpl: c.Healthcheck.DomainTmpl, HealthcheckDomainTmpl: c.Healthcheck.DomainTmpl,
FallbackAddresses: c.FallbackServers, UpstreamsAddresses: upsConfs,
Timeout: c.Timeout.Duration, FallbackAddresses: fallbackConfs,
HealthcheckBackoffDuration: c.Healthcheck.BackoffDuration.Duration, HealthcheckBackoffDuration: c.Healthcheck.BackoffDuration.Duration,
HealthcheckInitDuration: hcInit, HealthcheckInitDuration: hcInit,
} }
return fwdConf, nil return fwdConf
} }
// validate returns an error if the upstream configuration is invalid. // validate returns an error if the upstream configuration is invalid.
@ -68,20 +64,20 @@ func (c *upstreamConfig) validate() (err error) {
switch { switch {
case c == nil: case c == nil:
return errNilConfig return errNilConfig
case c.Server == "": case len(c.Servers) == 0:
return errors.Error("no server") return errors.Error("no servers")
case len(c.FallbackServers) == 0:
return errors.Error("no fallback")
case c.Timeout.Duration <= 0:
return newMustBePositiveError("timeout", c.Timeout)
} }
err = validateAddrs(c.FallbackServers) for i, s := range c.Servers {
if err != nil { if err = s.validate(); err != nil {
return fmt.Errorf("fallback: %w", err) 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. // splitUpstreamURL separates server url to net protocol and port address.
@ -163,13 +159,13 @@ func (c *upstreamHealthcheckConfig) validate() (err error) {
func newUpstreamHealthcheck( func newUpstreamHealthcheck(
handler *forward.Handler, handler *forward.Handler,
conf *upstreamConfig, conf *upstreamConfig,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (refr agd.Service) { ) (refr agdservice.Interface) {
if !conf.Healthcheck.Enabled { 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) { Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout( return context.WithTimeout(
context.Background(), context.Background(),
@ -182,5 +178,76 @@ func newUpstreamHealthcheck(
Interval: conf.Healthcheck.Interval.Duration, Interval: conf.Healthcheck.Interval.Duration,
RefreshOnShutdown: false, RefreshOnShutdown: false,
RoutineLogsAreDebug: true, 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 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 // netipAddr is the type constraint for the types from [netip], which we can
// validate using [validateAddrs]. // validate using [validateAddrs].
type netipAddr interface { type netipAddr interface {

View File

@ -9,12 +9,12 @@ import (
"os" "os"
"path" "path"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/websvc" "github.com/AdguardTeam/AdGuardDNS/internal/websvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil" "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 // RootRedirectURL is the URL to which non-DNS and non-Debug HTTP requests
// are redirected. If not set, a 404 page is shown. // 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 // StaticContent is the content that is served statically at the given
// paths. If not set, no static content is shown. // paths. If not set, no static content is shown.
@ -60,7 +60,7 @@ type webConfig struct {
func (c *webConfig) toInternal( func (c *webConfig) toInternal(
envs *environments, envs *environments,
dnsCk http.Handler, dnsCk http.Handler,
errColl agd.ErrorCollector, errColl errcoll.Interface,
) (conf *websvc.Config, err error) { ) (conf *websvc.Config, err error) {
if c == nil { if c == nil {
return nil, nil return nil, nil
@ -179,7 +179,7 @@ type linkedIPServer struct {
// toInternal converts s to a linkedIP server configuration. s is assumed to be // toInternal converts s to a linkedIP server configuration. s is assumed to be
// valid. // valid.
func (s *linkedIPServer) toInternal( func (s *linkedIPServer) toInternal(
targetURL *agdhttp.URL, targetURL *urlutil.URL,
) (srv *websvc.LinkedIPServer, err error) { ) (srv *websvc.LinkedIPServer, err error) {
if s == nil { if s == nil {
return nil, nil return nil, nil

View File

@ -18,9 +18,9 @@ import (
type limitConn struct { type limitConn struct {
net.Conn net.Conn
serverInfo *dnsserver.ServerInfo
decrement func() decrement func()
start time.Time start time.Time
serverInfo dnsserver.ServerInfo
isClosed atomic.Bool 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 // Limit wraps lsnr to control the number of active connections. srvInfo is
// used for logging and metrics. // 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 name, addr := srvInfo.Name, srvInfo.Addr
proto := srvInfo.Proto.String() proto := srvInfo.Proto.String()

View File

@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
const testTimeout = 1 * time.Second const testTimeout = 1 * time.Second
// testServerInfo is the common server information for tests. // testServerInfo is the common server information for tests.
var testServerInfo = dnsserver.ServerInfo{ var testServerInfo = &dnsserver.ServerInfo{
Name: "test_server", Name: "test_server",
Addr: "127.0.0.1:0", Addr: "127.0.0.1:0",
Proto: agd.ProtoDoT, Proto: agd.ProtoDoT,

View File

@ -20,6 +20,10 @@ import (
type limitListener struct { type limitListener struct {
net.Listener 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 // counterCond is the condition variable that protects counter and isClosed
// through its locker, as well as signals when connections can be accepted // through its locker, as well as signals when connections can be accepted
// again or when the listener has been closed. // again or when the listener has been closed.
@ -35,10 +39,6 @@ type limitListener struct {
// waiting for an accept. // waiting for an accept.
waitingHist prometheus.Observer 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 shows whether this listener has been closed.
isClosed bool isClosed bool
} }

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