mirror of
https://github.com/AdguardTeam/AdGuardDNS.git
synced 2025-02-20 11:23:36 +08:00
Sync v2.8.0
This commit is contained in:
parent
5690301129
commit
41f7e6cb22
913
CHANGELOG.md
913
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
6
Makefile
6
Makefile
@ -23,7 +23,7 @@ VERBOSE.MACRO = $${VERBOSE:-0}
|
||||
BRANCH = $$( git rev-parse --abbrev-ref HEAD )
|
||||
GOAMD64 = v1
|
||||
GOPROXY = https://proxy.golang.org|direct
|
||||
GOTOOLCHAIN = go1.22.4
|
||||
GOTOOLCHAIN = go1.22.5
|
||||
RACE = 0
|
||||
REVISION = $$( git rev-parse --short HEAD )
|
||||
VERSION = 0
|
||||
@ -66,8 +66,8 @@ go-check: go-tools go-lint go-test
|
||||
# A quick check to make sure that all operating systems relevant to the
|
||||
# development of the project can be typechecked and built successfully.
|
||||
go-os-check:
|
||||
env GOOS='darwin' "$(GO.MACRO)" vet ./internal/...
|
||||
env GOOS='linux' "$(GO.MACRO)" vet ./internal/...
|
||||
env GOOS='darwin' "$(GO.MACRO)" vet ./internal/... ./internal/dnsserver/...
|
||||
env GOOS='linux' "$(GO.MACRO)" vet ./internal/... ./internal/dnsserver/...
|
||||
# Additionally, check the AdGuard Home OSs in the dnsserver module.
|
||||
env GOOS='freebsd' "$(GO.MACRO)" vet ./internal/dnsserver/...
|
||||
env GOOS='openbsd' "$(GO.MACRO)" vet ./internal/dnsserver/...
|
||||
|
104
doc/debughttp.md
104
doc/debughttp.md
@ -1,20 +1,75 @@
|
||||
# AdGuard DNS Debug HTTP API
|
||||
# AdGuard DNS debug HTTP API
|
||||
|
||||
The AdGuard DNS debug HTTP API is served on [`LISTEN_PORT`][env-listen_port] and
|
||||
contains various private debugging information.
|
||||
The AdGuard DNS debug HTTP API is served on [`LISTEN_PORT`][env-listen_port] and contains various private debugging information.
|
||||
|
||||
## Contents
|
||||
## Contents
|
||||
|
||||
* [`GET /dnsdb/csv`](#dnsdb-csv)
|
||||
* [`GET /health-check`](#health-check)
|
||||
* [`GET /metrics`](#metrics)
|
||||
* [`GET /debug/pprof`](#pprof)
|
||||
- [`GET /health-check`](#health-check)
|
||||
- [`GET /metrics`](#metrics)
|
||||
- [`GET /debug/pprof`](#pprof)
|
||||
- [`POST /debug/api/refresh`](#api-refresh)
|
||||
- [`POST /dnsdb/csv`](#dnsdb-csv)
|
||||
|
||||
[env-listen_port]: environment.md#LISTEN_PORT
|
||||
|
||||
## <a href="#health-check" id="health-check" name="health-check">`GET /health-check`</a>
|
||||
|
||||
A simple health check API. Always responds with a `200 OK` status and the plain-text body `OK`.
|
||||
|
||||
## <a href="#dnsdb-csv" id="dnsdb-csv" name="dnsdb-csv">`GET /dnsdb/csv`</a>
|
||||
## <a href="#metrics" id="metrics" name="metrics">`GET /metrics`</a>
|
||||
|
||||
Prometheus metrics HTTP API. See the [metrics page][metrics] for more details.
|
||||
|
||||
[metrics]: metrics.md
|
||||
|
||||
## <a href="#pprof" id="pprof" name="pprof">`GET /debug/pprof`</a>
|
||||
|
||||
The HTTP interface of Go's [PProf HTTP API][pprof api].
|
||||
|
||||
[pprof api]: https://pkg.go.dev/net/http/pprof
|
||||
|
||||
## <a href="#api-refresh" id="api-refresh" name="api-refresh">`POST /debug/api/refresh`</a>
|
||||
|
||||
Run some refresh jobs manually. This refresh does not alter the time of the next automatic refresh.
|
||||
|
||||
Example request:
|
||||
|
||||
```sh
|
||||
curl -d '{"ids":["*"]}' -v "http://${LISTEN_ADDR}:${LISTEN_PORT}/debug/api/refresh"
|
||||
```
|
||||
|
||||
Request body example:
|
||||
|
||||
```json
|
||||
{
|
||||
"ids": [
|
||||
"filter_storage",
|
||||
"adult_blocking"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Supported IDs:
|
||||
|
||||
- `adult_blocking`;
|
||||
- `filter_storage`;
|
||||
- `newly_registered_domains`;
|
||||
- `safe_browsing`.
|
||||
|
||||
The special ID `*`, when used alone, causes all available refresh tasks to be performed. Use with caution.
|
||||
|
||||
Response body example:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": {
|
||||
"adult_blocking": "ok",
|
||||
"filter_storage": "ok"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## <a href="#dnsdb-csv" id="dnsdb-csv" name="dnsdb-csv">`POST /dnsdb/csv`</a>
|
||||
|
||||
The CSV dump of the current DNSDB statistics. Example of the output:
|
||||
|
||||
@ -23,33 +78,4 @@ example.com,A,NOERROR,93.184.216.34,42
|
||||
example.com,AAAA,NOERROR,2606:2800:220:1:248:1893:25c8:1946,123
|
||||
```
|
||||
|
||||
The response is sent with the `Transfer-Encoding` set to `chunked` and with an
|
||||
HTTP trailer named `X-Error` which describes errors that might have occurred
|
||||
during the database dump.
|
||||
|
||||
> [!NOTE]
|
||||
> For legacy software reasons, despite the endpoint being a `GET` one, it
|
||||
> rotates the database, and so changes the internal state.
|
||||
|
||||
|
||||
|
||||
## <a href="#health-check" id="health-check" name="health-check">`GET /health-check`</a>
|
||||
|
||||
A simple health check API. Always responds with a `200 OK` status and the
|
||||
plain-text body `OK`.
|
||||
|
||||
|
||||
|
||||
## <a href="#metrics" id="metrics" name="metrics">`GET /metrics`</a>
|
||||
|
||||
Prometheus metrics HTTP API. See the [metrics page][metrics] for more details.
|
||||
|
||||
[metrics]: metrics.md
|
||||
|
||||
|
||||
|
||||
## <a href="#pprof" id="pprof" name="pprof">`GET /debug/pprof`</a>
|
||||
|
||||
The HTTP interface of Go's [PProf HTTP API][pprof api].
|
||||
|
||||
[pprof api]: https://pkg.go.dev/net/http/pprof
|
||||
The response is sent with the `Transfer-Encoding` set to `chunked` and with an HTTP trailer named `X-Error` which describes errors that might have occurred during the database dump.
|
||||
|
@ -185,6 +185,7 @@ cp -f config.dist.yaml config.yaml
|
||||
### <a href="#run-4" id="run-4" name="run-4">Step 4: prepare the test data</a>
|
||||
|
||||
```sh
|
||||
echo '<html><body>General content ahead</body></html>' > ./test/block_page_general.html
|
||||
echo '<html><body>Dangerous content ahead</body></html>' > ./test/block_page_sb.html
|
||||
echo '<html><body>Adult content ahead</body></html>' > ./test/block_page_adult.html
|
||||
echo '<html><body>Error 404</body></html>' > ./test/error_404.html
|
||||
|
@ -1,175 +1,139 @@
|
||||
# AdGuard DNS Environment Configuration
|
||||
# AdGuard DNS environment configuration
|
||||
|
||||
AdGuard DNS uses [environment variables][wiki-env] to store some of the more
|
||||
sensitive configuration. All other configuration is stored in the
|
||||
[configuration file][conf].
|
||||
AdGuard DNS uses [environment variables][wiki-env] to store some of the more sensitive configuration. All other configuration is stored in the [configuration file][conf].
|
||||
|
||||
## Contents
|
||||
## Contents
|
||||
|
||||
* [`ADULT_BLOCKING_URL`](#ADULT_BLOCKING_URL)
|
||||
* [`BILLSTAT_URL`](#BILLSTAT_URL)
|
||||
* [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL)
|
||||
* [`CONFIG_PATH`](#CONFIG_PATH)
|
||||
* [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL)
|
||||
* [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL)
|
||||
* [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL)
|
||||
* [`FILTER_CACHE_PATH`](#FILTER_CACHE_PATH)
|
||||
* [`FILTER_INDEX_URL`](#FILTER_INDEX_URL)
|
||||
* [`GENERAL_SAFE_SEARCH_URL`](#GENERAL_SAFE_SEARCH_URL)
|
||||
* [`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`](#GEOIP_ASN_PATH)
|
||||
* [`LINKED_IP_TARGET_URL`](#LINKED_IP_TARGET_URL)
|
||||
* [`LISTEN_ADDR`](#LISTEN_ADDR)
|
||||
* [`LISTEN_PORT`](#LISTEN_PORT)
|
||||
* [`LOG_TIMESTAMP`](#LOG_TIMESTAMP)
|
||||
* [`NEW_REG_DOMAINS_URL`](#NEW_REG_DOMAINS_URL)
|
||||
* [`PROFILES_CACHE_PATH`](#PROFILES_CACHE_PATH)
|
||||
* [`PROFILES_ENABLED`](#PROFILES_ENABLED)
|
||||
* [`PROFILES_URL`](#PROFILES_URL)
|
||||
* [`QUERYLOG_PATH`](#QUERYLOG_PATH)
|
||||
* [`RULESTAT_URL`](#RULESTAT_URL)
|
||||
* [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL)
|
||||
* [`SENTRY_DSN`](#SENTRY_DSN)
|
||||
* [`SSL_KEY_LOG_FILE`](#SSL_KEY_LOG_FILE)
|
||||
* [`VERBOSE`](#VERBOSE)
|
||||
* [`YOUTUBE_SAFE_SEARCH_URL`](#YOUTUBE_SAFE_SEARCH_URL)
|
||||
- [`ADULT_BLOCKING_URL`](#ADULT_BLOCKING_URL)
|
||||
- [`BILLSTAT_API_KEY`](#BILLSTAT_API_KEY)
|
||||
- [`BILLSTAT_URL`](#BILLSTAT_URL)
|
||||
- [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL)
|
||||
- [`CONFIG_PATH`](#CONFIG_PATH)
|
||||
- [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL)
|
||||
- [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL)
|
||||
- [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL)
|
||||
- [`FILTER_CACHE_PATH`](#FILTER_CACHE_PATH)
|
||||
- [`FILTER_INDEX_URL`](#FILTER_INDEX_URL)
|
||||
- [`GENERAL_SAFE_SEARCH_URL`](#GENERAL_SAFE_SEARCH_URL)
|
||||
- [`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`](#GEOIP_ASN_PATH)
|
||||
- [`LINKED_IP_TARGET_URL`](#LINKED_IP_TARGET_URL)
|
||||
- [`LISTEN_ADDR`](#LISTEN_ADDR)
|
||||
- [`LISTEN_PORT`](#LISTEN_PORT)
|
||||
- [`LOG_TIMESTAMP`](#LOG_TIMESTAMP)
|
||||
- [`NEW_REG_DOMAINS_URL`](#NEW_REG_DOMAINS_URL)
|
||||
- [`PROFILES_API_KEY`](#PROFILES_API_KEY)
|
||||
- [`PROFILES_CACHE_PATH`](#PROFILES_CACHE_PATH)
|
||||
- [`PROFILES_ENABLED`](#PROFILES_ENABLED)
|
||||
- [`PROFILES_URL`](#PROFILES_URL)
|
||||
- [`QUERYLOG_PATH`](#QUERYLOG_PATH)
|
||||
- [`RULESTAT_URL`](#RULESTAT_URL)
|
||||
- [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL)
|
||||
- [`SENTRY_DSN`](#SENTRY_DSN)
|
||||
- [`SSL_KEY_LOG_FILE`](#SSL_KEY_LOG_FILE)
|
||||
- [`VERBOSE`](#VERBOSE)
|
||||
- [`YOUTUBE_SAFE_SEARCH_URL`](#YOUTUBE_SAFE_SEARCH_URL)
|
||||
|
||||
[conf]: configuration.md
|
||||
[wiki-env]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
|
||||
|
||||
|
||||
## <a href="#ADULT_BLOCKING_URL" id="ADULT_BLOCKING_URL" name="ADULT_BLOCKING_URL">`ADULT_BLOCKING_URL`</a>
|
||||
## <a href="#ADULT_BLOCKING_URL" id="ADULT_BLOCKING_URL" name="ADULT_BLOCKING_URL">`ADULT_BLOCKING_URL`</a>
|
||||
|
||||
The URL of source list of rules for adult blocking filter.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
## <a href="#BILLSTAT_API_KEY" id="BILLSTAT_API_KEY" name="BILLSTAT_API_KEY">`BILLSTAT_API_KEY`</a>
|
||||
|
||||
The API key to use when authenticating queries to the billing statistics API, if any. The API key should be valid as defined by [RFC 6750].
|
||||
|
||||
## <a href="#BILLSTAT_URL" id="BILLSTAT_URL" name="BILLSTAT_URL">`BILLSTAT_URL`</a>
|
||||
**Default:** **Unset.**
|
||||
|
||||
The base backend URL for backend billing statistics uploader API. Supports
|
||||
GRPC (`grpc://` and`grpcs://`) URLs. See the [external HTTP API requirements
|
||||
section][ext-billstat].
|
||||
[RFC 6750]: https://datatracker.ietf.org/doc/html/rfc6750#section-2.1
|
||||
|
||||
## <a href="#BILLSTAT_URL" id="BILLSTAT_URL" name="BILLSTAT_URL">`BILLSTAT_URL`</a>
|
||||
|
||||
The base backend URL for backend billing statistics uploader API. Supports GRPC (`grpc://` and`grpcs://`) URLs. See the [external HTTP API requirements section][ext-billstat].
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-billstat]: externalhttp.md#backend-billstat
|
||||
|
||||
## <a href="#BLOCKED_SERVICE_INDEX_URL" id="BLOCKED_SERVICE_INDEX_URL" name="BLOCKED_SERVICE_INDEX_URL">`BLOCKED_SERVICE_INDEX_URL`</a>
|
||||
|
||||
|
||||
## <a href="#BLOCKED_SERVICE_INDEX_URL" id="BLOCKED_SERVICE_INDEX_URL" name="BLOCKED_SERVICE_INDEX_URL">`BLOCKED_SERVICE_INDEX_URL`</a>
|
||||
|
||||
The URL of the blocked service index file server. See the [external HTTP API
|
||||
requirements section][ext-blocked] on the expected format of the response.
|
||||
The URL of the blocked service index file server. See the [external HTTP API requirements section][ext-blocked] on the expected format of the response.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-blocked]: externalhttp.md#filters-blocked-services
|
||||
|
||||
|
||||
|
||||
## <a href="#CONFIG_PATH" id="CONFIG_PATH" name="CONFIG_PATH">`CONFIG_PATH`</a>
|
||||
## <a href="#CONFIG_PATH" id="CONFIG_PATH" name="CONFIG_PATH">`CONFIG_PATH`</a>
|
||||
|
||||
The path to the configuration file.
|
||||
|
||||
**Default:** `./config.yaml`.
|
||||
|
||||
## <a href="#CONSUL_ALLOWLIST_URL" id="CONSUL_ALLOWLIST_URL" name="CONSUL_ALLOWLIST_URL">`CONSUL_ALLOWLIST_URL`</a>
|
||||
|
||||
|
||||
## <a href="#CONSUL_ALLOWLIST_URL" id="CONSUL_ALLOWLIST_URL" name="CONSUL_ALLOWLIST_URL">`CONSUL_ALLOWLIST_URL`</a>
|
||||
|
||||
The URL of the Consul instance serving the dynamic part of the rate-limit
|
||||
allowlist. See the [external HTTP API requirements section][ext-consul] on the
|
||||
expected format of the response.
|
||||
The URL of the Consul instance serving the dynamic part of the rate-limit allowlist. See the [external HTTP API requirements section][ext-consul] on the expected format of the response.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-consul]: externalhttp.md#consul
|
||||
|
||||
## <a href="#CONSUL_DNSCHECK_KV_URL" id="CONSUL_DNSCHECK_KV_URL" name="CONSUL_DNSCHECK_KV_URL">`CONSUL_DNSCHECK_KV_URL`</a>
|
||||
|
||||
|
||||
## <a href="#CONSUL_DNSCHECK_KV_URL" id="CONSUL_DNSCHECK_KV_URL" name="CONSUL_DNSCHECK_KV_URL">`CONSUL_DNSCHECK_KV_URL`</a>
|
||||
|
||||
The URL of the KV API of the Consul instance used as a key-value database for
|
||||
the DNS server checking. It must end with `/kv/<NAMESPACE>` where `<NAMESPACE>`
|
||||
is any non-empty namespace. If not specified, the
|
||||
[`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL) is also
|
||||
omitted.
|
||||
The URL of the KV API of the Consul instance used as a key-value database for the DNS server checking. It must end with `/kv/<NAMESPACE>` where `<NAMESPACE>` is any non-empty namespace. If not specified, the [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL) is also omitted.
|
||||
|
||||
**Default:** **Unset.**
|
||||
|
||||
**Example:** `http://localhost:8500/v1/kv/test`
|
||||
|
||||
## <a href="#CONSUL_DNSCHECK_SESSION_URL" id="CONSUL_DNSCHECK_SESSION_URL" name="CONSUL_DNSCHECK_SESSION_URL">`CONSUL_DNSCHECK_SESSION_URL`</a>
|
||||
|
||||
|
||||
## <a href="#CONSUL_DNSCHECK_SESSION_URL" id="CONSUL_DNSCHECK_SESSION_URL" name="CONSUL_DNSCHECK_SESSION_URL">`CONSUL_DNSCHECK_SESSION_URL`</a>
|
||||
|
||||
The URL of the session API of the Consul instance used as a key-value database
|
||||
for the DNS server checking. If not specified, the
|
||||
[`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL) is also omitted.
|
||||
The URL of the session API of the Consul instance used as a key-value database for the DNS server checking. If not specified, the [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL) is also omitted.
|
||||
|
||||
**Default:** **Unset.**
|
||||
|
||||
**Example:** `http://localhost:8500/v1/session/create`
|
||||
|
||||
## <a href="#FILTER_CACHE_PATH" id="FILTER_CACHE_PATH" name="FILTER_CACHE_PATH">`FILTER_CACHE_PATH`</a>
|
||||
|
||||
|
||||
## <a href="#FILTER_CACHE_PATH" id="FILTER_CACHE_PATH" name="FILTER_CACHE_PATH">`FILTER_CACHE_PATH`</a>
|
||||
|
||||
The path to the directory used to store the cached version of all filters and
|
||||
filter indexes.
|
||||
The path to the directory used to store the cached version of all filters and filter indexes.
|
||||
|
||||
**Default:** `./filters/`.
|
||||
|
||||
## <a href="#FILTER_INDEX_URL" id="FILTER_INDEX_URL" name="FILTER_INDEX_URL">`FILTER_INDEX_URL`</a>
|
||||
|
||||
|
||||
## <a href="#FILTER_INDEX_URL" id="FILTER_INDEX_URL" name="FILTER_INDEX_URL">`FILTER_INDEX_URL`</a>
|
||||
|
||||
The URL of the filtering rule index file server. See the [external HTTP API
|
||||
requirements section][ext-lists] on the expected format of the response.
|
||||
The URL of the filtering rule index file server. See the [external HTTP API requirements section][ext-lists] on the expected format of the response.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-lists]: externalhttp.md#filters-lists
|
||||
|
||||
## <a href="#GENERAL_SAFE_SEARCH_URL" id="GENERAL_SAFE_SEARCH_URL" name="GENERAL_SAFE_SEARCH_URL">`GENERAL_SAFE_SEARCH_URL`</a>
|
||||
|
||||
|
||||
## <a href="#GENERAL_SAFE_SEARCH_URL" id="GENERAL_SAFE_SEARCH_URL" name="GENERAL_SAFE_SEARCH_URL">`GENERAL_SAFE_SEARCH_URL`</a>
|
||||
|
||||
The URL of the list of general safe search rewriting rules. See the [external
|
||||
HTTP API requirements section][ext-general] on the expected format of the
|
||||
response.
|
||||
The URL of the list of general safe search rewriting rules. See the [external HTTP API requirements section][ext-general] on the expected format of the response.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-general]: externalhttp.md#filters-safe-search
|
||||
|
||||
## <a href="#GEOIP_ASN_PATH" id="GEOIP_ASN_PATH" name="GEOIP_ASN_PATH">`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`</a>
|
||||
|
||||
|
||||
## <a href="#GEOIP_ASN_PATH" id="GEOIP_ASN_PATH" name="GEOIP_ASN_PATH">`GEOIP_ASN_PATH` and `GEOIP_COUNTRY_PATH`</a>
|
||||
|
||||
Paths to the files containing MaxMind GeoIP databases: for ASNs and for
|
||||
countries and continents respectively.
|
||||
Paths to the files containing MaxMind GeoIP databases: for ASNs and for countries and continents respectively.
|
||||
|
||||
**Default:** `./asn.mmdb` and `./country.mmdb`.
|
||||
|
||||
## <a href="#LINKED_IP_TARGET_URL" id="LINKED_IP_TARGET_URL" name="LINKED_IP_TARGET_URL">`LINKED_IP_TARGET_URL`</a>
|
||||
|
||||
|
||||
## <a href="#LINKED_IP_TARGET_URL" id="LINKED_IP_TARGET_URL" name="LINKED_IP_TARGET_URL">`LINKED_IP_TARGET_URL`</a>
|
||||
|
||||
The target URL to which linked IP API requests are proxied. In case [linked IP
|
||||
and dynamic DNS][conf-web-linked_ip] web server is configured, the variable is
|
||||
required. See the [external HTTP API requirements section][ext-linked_ip].
|
||||
The target URL to which linked IP API requests are proxied. In case [linked IP and dynamic DNS][conf-web-linked_ip] web server is configured, the variable is required. See the [external HTTP API requirements section][ext-linked_ip].
|
||||
|
||||
**Default:** **Unset.**
|
||||
|
||||
[conf-web-linked_ip]: configuration.md#web-linked_ip
|
||||
[ext-linked_ip]: externalhttp.md#backend-linkip
|
||||
|
||||
|
||||
|
||||
## <a href="#LISTEN_ADDR" id="LISTEN_ADDR" name="LISTEN_ADDR">`LISTEN_ADDR`</a>
|
||||
## <a href="#LISTEN_ADDR" id="LISTEN_ADDR" name="LISTEN_ADDR">`LISTEN_ADDR`</a>
|
||||
|
||||
The IP address on which to bind the [debug HTTP API][debughttp].
|
||||
|
||||
@ -177,96 +141,77 @@ The IP address on which to bind the [debug HTTP API][debughttp].
|
||||
|
||||
[debughttp]: debughttp.md
|
||||
|
||||
## <a href="#LISTEN_PORT" id="LISTEN_PORT" name="LISTEN_PORT">`LISTEN_PORT`</a>
|
||||
|
||||
|
||||
## <a href="#LISTEN_PORT" id="LISTEN_PORT" name="LISTEN_PORT">`LISTEN_PORT`</a>
|
||||
|
||||
The port on which to bind the [debug HTTP API][debughttp], which includes the
|
||||
health check, Prometheus, `pprof`, and other endpoints.
|
||||
The port on which to bind the [debug HTTP API][debughttp], which includes the health check, Prometheus, `pprof`, and other endpoints.
|
||||
|
||||
**Default:** `8181`.
|
||||
|
||||
## <a href="#LOG_TIMESTAMP" id="LOG_TIMESTAMP" name="LOG_TIMESTAMP">`LOG_TIMESTAMP`</a>
|
||||
|
||||
|
||||
## <a href="#LOG_TIMESTAMP" id="LOG_TIMESTAMP" name="LOG_TIMESTAMP">`LOG_TIMESTAMP`</a>
|
||||
|
||||
If `1`, show timestamps in the plain text logs. If `0`, don't show the
|
||||
timestamps.
|
||||
If `1`, show timestamps in the plain text logs. If `0`, don't show the timestamps.
|
||||
|
||||
**Default:** `1`.
|
||||
|
||||
## <a href="#NEW_REG_DOMAINS_URL" id="NEW_REG_DOMAINS_URL" name="NEW_REG_DOMAINS_URL">`NEW_REG_DOMAINS_URL`</a>
|
||||
|
||||
|
||||
## <a href="#NEW_REG_DOMAINS_URL" id="NEW_REG_DOMAINS_URL" name="NEW_REG_DOMAINS_URL">`NEW_REG_DOMAINS_URL`</a>
|
||||
|
||||
The URL of source list of rules for newly registered domains safe browsing
|
||||
filter.
|
||||
The URL of source list of rules for newly registered domains safe browsing filter.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
## <a href="#PROFILES_API_KEY" id="PROFILES_API_KEY" name="PROFILES_API_KEY">`PROFILES_API_KEY`</a>
|
||||
|
||||
The API key to use when authenticating queries to the profiles API, if any. The API key should be valid as defined by [RFC 6750].
|
||||
|
||||
## <a href="#PROFILES_CACHE_PATH" id="PROFILES_CACHE_PATH" name="PROFILES_CACHE_PATH">`PROFILES_CACHE_PATH`</a>
|
||||
**Default:** **Unset.**
|
||||
|
||||
## <a href="#PROFILES_CACHE_PATH" id="PROFILES_CACHE_PATH" name="PROFILES_CACHE_PATH">`PROFILES_CACHE_PATH`</a>
|
||||
|
||||
The path to the profile cache file:
|
||||
|
||||
* `none` means that the profile caching is disabled.
|
||||
- `none` means that the profile caching is disabled.
|
||||
|
||||
* A file with the extension `.pb` means that the profiles are cached in the
|
||||
protobuf format.
|
||||
- A file with the extension `.pb` means that the profiles are cached in the protobuf format.
|
||||
|
||||
Use the following command to inspect the cache, assuming that the version is
|
||||
correct:
|
||||
Use the following command to inspect the cache, assuming that the version is correct:
|
||||
|
||||
```sh
|
||||
protoc\
|
||||
--decode\
|
||||
profiledb.FileCache\
|
||||
./internal/profiledb/internal/filecachepb/filecache.proto\
|
||||
< /path/to/profilecache.pb
|
||||
```
|
||||
```sh
|
||||
protoc\
|
||||
--decode\
|
||||
profiledb.FileCache\
|
||||
./internal/profiledb/internal/filecachepb/filecache.proto\
|
||||
< /path/to/profilecache.pb
|
||||
```
|
||||
|
||||
The profile cache is read on start and is later updated on every
|
||||
[full refresh][conf-backend-full_refresh_interval].
|
||||
The profile cache is read on start and is later updated on every [full refresh][conf-backend-full_refresh_interval].
|
||||
|
||||
**Default:** `./profilecache.pb`.
|
||||
|
||||
[conf-backend-full_refresh_interval]: configuration.md#backend-full_refresh_interval
|
||||
|
||||
|
||||
|
||||
## <a href="#PROFILES_ENABLED" id="PROFILES_ENABLED" name="PROFILES_ENABLED">`PROFILES_ENABLED`</a>
|
||||
## <a href="#PROFILES_ENABLED" id="PROFILES_ENABLED" name="PROFILES_ENABLED">`PROFILES_ENABLED`</a>
|
||||
|
||||
If `0`, disables user profiles and devices recognition and billing.
|
||||
|
||||
**Default:** `1`.
|
||||
|
||||
## <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 GRPC (`grpc://` and`grpcs://`)
|
||||
URLs. See the [external API requirements section][ext-profiles].
|
||||
The base backend URL for profiles API. Supports GRPC (`grpc://` and`grpcs://`) URLs. See the [external API requirements section][ext-profiles].
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
[ext-profiles]: externalhttp.md#backend-profiles
|
||||
|
||||
|
||||
|
||||
## <a href="#QUERYLOG_PATH" id="QUERYLOG_PATH" name="QUERYLOG_PATH">`QUERYLOG_PATH`</a>
|
||||
## <a href="#QUERYLOG_PATH" id="QUERYLOG_PATH" name="QUERYLOG_PATH">`QUERYLOG_PATH`</a>
|
||||
|
||||
The path to the file into which the query log is going to be written.
|
||||
|
||||
**Default:** `./querylog.jsonl`.
|
||||
|
||||
## <a href="#RULESTAT_URL" id="RULESTAT_URL" name="RULESTAT_URL">`RULESTAT_URL`</a>
|
||||
|
||||
|
||||
## <a href="#RULESTAT_URL" id="RULESTAT_URL" name="RULESTAT_URL">`RULESTAT_URL`</a>
|
||||
|
||||
The URL to send filtering rule list statistics to. If empty or unset, the
|
||||
collection of filtering rule statistics is disabled. See the [external HTTP API
|
||||
requirements section][ext-rulestat] on the expected format of the response.
|
||||
The URL to send filtering rule list statistics to. If empty or unset, the collection of filtering rule statistics is disabled. See the [external HTTP API requirements section][ext-rulestat] on the expected format of the response.
|
||||
|
||||
**Default:** **Unset.**
|
||||
|
||||
@ -274,46 +219,32 @@ requirements section][ext-rulestat] on the expected format of the response.
|
||||
|
||||
[ext-rulestat]: externalhttp.md#rulestat
|
||||
|
||||
|
||||
|
||||
## <a href="#SAFE_BROWSING_URL" id="SAFE_BROWSING_URL" name="SAFE_BROWSING_URL">`SAFE_BROWSING_URL`</a>
|
||||
## <a href="#SAFE_BROWSING_URL" id="SAFE_BROWSING_URL" name="SAFE_BROWSING_URL">`SAFE_BROWSING_URL`</a>
|
||||
|
||||
The URL of source list of rules for dangerous domains safe browsing filter.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
||||
## <a href="#SENTRY_DSN" id="SENTRY_DSN" name="SENTRY_DSN">`SENTRY_DSN`</a>
|
||||
|
||||
|
||||
## <a href="#SENTRY_DSN" id="SENTRY_DSN" name="SENTRY_DSN">`SENTRY_DSN`</a>
|
||||
|
||||
Sentry error collector address. The special value `stderr` makes AdGuard DNS
|
||||
print these errors to standard error.
|
||||
Sentry error collector address. The special value `stderr` makes AdGuard DNS print these errors to standard error.
|
||||
|
||||
**Default:** `stderr`.
|
||||
|
||||
## <a href="#SSL_KEY_LOG_FILE" id="SSL_KEY_LOG_FILE" name="SSL_KEY_LOG_FILE">`SSL_KEY_LOG_FILE`</a>
|
||||
|
||||
|
||||
## <a href="#SSL_KEY_LOG_FILE" id="SSL_KEY_LOG_FILE" name="SSL_KEY_LOG_FILE">`SSL_KEY_LOG_FILE`</a>
|
||||
|
||||
If set, TLS key logs are written to this file to allow other programs (i.e.
|
||||
Wireshark) to decrypt packets. **Must only be used for debug purposes**.
|
||||
If set, TLS key logs are written to this file to allow other programs (i.e. Wireshark) to decrypt packets. **Must only be used for debug purposes**.
|
||||
|
||||
**Default:** **Unset.**
|
||||
|
||||
## <a href="#VERBOSE" id="VERBOSE" name="VERBOSE">`VERBOSE`</a>
|
||||
|
||||
|
||||
## <a href="#VERBOSE" id="VERBOSE" name="VERBOSE">`VERBOSE`</a>
|
||||
|
||||
When set to `1`, enable verbose logging. When set to `0`, disable it.
|
||||
When set to `1`, enable verbose logging. When set to `0`, disable it.
|
||||
|
||||
**Default:** `0`.
|
||||
|
||||
## <a href="#YOUTUBE_SAFE_SEARCH_URL" id="YOUTUBE_SAFE_SEARCH_URL" name="YOUTUBE_SAFE_SEARCH_URL">`YOUTUBE_SAFE_SEARCH_URL`</a>
|
||||
|
||||
|
||||
## <a href="#YOUTUBE_SAFE_SEARCH_URL" id="YOUTUBE_SAFE_SEARCH_URL" name="YOUTUBE_SAFE_SEARCH_URL">`YOUTUBE_SAFE_SEARCH_URL`</a>
|
||||
|
||||
The URL of the list of YouTube-specific safe search rewriting rules. See the
|
||||
[external HTTP API requirements section][ext-general] on the expected format of
|
||||
the response.
|
||||
The URL of the list of YouTube-specific safe search rewriting rules. See the [external HTTP API requirements section][ext-general] on the expected format of the response.
|
||||
|
||||
**Default:** No default value, the variable is **required.**
|
||||
|
@ -114,8 +114,8 @@ This endpoint, defined by [`FILTER_INDEX_URL`][env-filters], must respond with a
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"downloadUrl": "https://cdn.example.com/assets/my_filter.txt",
|
||||
"id": "my_filter"
|
||||
"filterKey": "my_filter",
|
||||
"downloadUrl": "https://cdn.example.com/assets/my_filter.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -58,8 +58,11 @@ rules to remember, which property means what. The properties are:
|
||||
“destination”.
|
||||
|
||||
> [!NOTE]
|
||||
> AdGuard DNS uses the common user-assigned ISO 3166-1 alpha-2 code `XK`
|
||||
> for the partially-recognized state of the Republic of Kosovo.
|
||||
> Just like in the `c` field, `XK` is used for the partially-recognized
|
||||
> state of the Republic of Kosovo. In addition to that, the code `QN`,
|
||||
> “Not Applicable”, is used when the resource-record type of the response
|
||||
> does not contain any IP-address information (for example, responses to
|
||||
> `TXT` requests).
|
||||
|
||||
**Example:** `"US"`
|
||||
|
||||
|
52
go.mod
52
go.mod
@ -1,33 +1,33 @@
|
||||
module github.com/AdguardTeam/AdGuardDNS
|
||||
|
||||
go 1.22.4
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
|
||||
github.com/AdguardTeam/golibs v0.23.2
|
||||
github.com/AdguardTeam/urlfilter v0.18.0
|
||||
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-20240607112746-5690301129fe
|
||||
github.com/AdguardTeam/golibs v0.24.0
|
||||
github.com/AdguardTeam/urlfilter v0.19.0
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/getsentry/sentry-go v0.27.0
|
||||
github.com/getsentry/sentry-go v0.28.1
|
||||
github.com/google/renameio/v2 v2.0.0
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/oschwald/maxminddb-golang v1.13.0
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/prometheus/common v0.52.3
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/prometheus/common v0.54.0
|
||||
github.com/quic-go/quic-go v0.45.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@ -39,19 +39,19 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.9.1 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.10.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
119
go.sum
119
go.sum
@ -1,7 +1,7 @@
|
||||
github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
|
||||
github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
|
||||
github.com/AdguardTeam/golibs v0.24.0 h1:qAnOq7BQtwSVo7Co9q703/n+nZ2Ap6smkugU9G9MomY=
|
||||
github.com/AdguardTeam/golibs v0.24.0/go.mod h1:9/vJcYznW7RlmCT/Qzi8XNZGj+ZbWfHZJmEXKnRpCAU=
|
||||
github.com/AdguardTeam/urlfilter v0.19.0 h1:q7eH13+yNETlpD/VD3u5rLQOripcUdEktqZFy+KiQLk=
|
||||
github.com/AdguardTeam/urlfilter v0.19.0/go.mod h1:+N54ZvxqXYLnXuvpaUhK2exDQW+djZBRSb6F6j0rkBY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
@ -10,8 +10,8 @@ github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0=
|
||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
@ -28,22 +28,20 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
|
||||
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 h1:ASJ/LAqdCHOyMYI+dwNxn7Rd8FscNkMyTr1KZU1JI/M=
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@ -52,16 +50,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/panjf2000/ants/v2 v2.9.1 h1:Q5vh5xohbsZXGcD6hhszzGqB7jSSc2/CRr3QKIga8Kw=
|
||||
github.com/panjf2000/ants/v2 v2.9.1/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
|
||||
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
@ -72,66 +70,65 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
|
||||
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
|
||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
||||
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/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/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56 h1:zviK8GX4VlMstrK3JkexM5UHjH1VOkRebH9y3jhSBGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240412170617-26222e5d3d56/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
319
go.work.sum
319
go.work.sum
@ -3,19 +3,138 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
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.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0=
|
||||
cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q=
|
||||
cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM=
|
||||
cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0=
|
||||
cloud.google.com/go/apigateway v1.6.5/go.mod h1:6wCwvYRckRQogyDDltpANi3zsCDl6kWi0b4Je+w2UiI=
|
||||
cloud.google.com/go/apigeeconnect v1.6.5/go.mod h1:MEKm3AiT7s11PqTfKE3KZluZA9O91FNysvd3E6SJ6Ow=
|
||||
cloud.google.com/go/apigeeregistry v0.8.3/go.mod h1:aInOWnqF4yMQx8kTjDqHNXjZGh/mxeNlAf52YqtASUs=
|
||||
cloud.google.com/go/appengine v1.8.5/go.mod h1:uHBgNoGLTS5di7BvU25NFDuKa82v0qQLjyMJLuPQrVo=
|
||||
cloud.google.com/go/area120 v0.8.5/go.mod h1:BcoFCbDLZjsfe4EkCnEq1LKvHSK0Ew/zk5UFu6GMyA0=
|
||||
cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM=
|
||||
cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4=
|
||||
cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk=
|
||||
cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y=
|
||||
cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY=
|
||||
cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc=
|
||||
cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc=
|
||||
cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
|
||||
cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc=
|
||||
cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE=
|
||||
cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ=
|
||||
cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM=
|
||||
cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc=
|
||||
cloud.google.com/go/cloudbuild v1.15.1/go.mod h1:gIofXZSu+XD2Uy+qkOrGKEx45zd7s28u/k8f99qKals=
|
||||
cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFotwdE79DieY=
|
||||
cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY=
|
||||
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk=
|
||||
cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
|
||||
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
|
||||
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
|
||||
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
|
||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI=
|
||||
cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA=
|
||||
cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE=
|
||||
cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4=
|
||||
cloud.google.com/go/dataflow v0.9.5/go.mod h1:udl6oi8pfUHnL0z6UN9Lf9chGqzDMVqcYTcZ1aPnCZQ=
|
||||
cloud.google.com/go/dataform v0.9.2/go.mod h1:S8cQUwPNWXo7m/g3DhWHsLBoufRNn9EgFrMgne2j7cI=
|
||||
cloud.google.com/go/datafusion v1.7.5/go.mod h1:bYH53Oa5UiqahfbNK9YuYKteeD4RbQSNMx7JF7peGHc=
|
||||
cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s=
|
||||
cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U=
|
||||
cloud.google.com/go/dataproc/v2 v2.4.0/go.mod h1:3B1Ht2aRB8VZIteGxQS/iNSJGzt9+CA0WGnDVMEm7Z4=
|
||||
cloud.google.com/go/dataqna v0.8.5/go.mod h1:vgihg1mz6n7pb5q2YJF7KlXve6tCglInd6XO0JGOlWM=
|
||||
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
|
||||
cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8=
|
||||
cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo=
|
||||
cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50=
|
||||
cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0=
|
||||
cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w=
|
||||
cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY=
|
||||
cloud.google.com/go/domains v0.9.5/go.mod h1:dBzlxgepazdFhvG7u23XMhmMKBjrkoUNaw0A8AQB55Y=
|
||||
cloud.google.com/go/edgecontainer v1.1.5/go.mod h1:rgcjrba3DEDEQAidT4yuzaKWTbkTI5zAMu3yy6ZWS0M=
|
||||
cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
|
||||
cloud.google.com/go/essentialcontacts v1.6.6/go.mod h1:XbqHJGaiH0v2UvtuucfOzFXN+rpL/aU5BCZLn4DYl1Q=
|
||||
cloud.google.com/go/eventarc v1.13.4/go.mod h1:zV5sFVoAa9orc/52Q+OuYUG9xL2IIZTbbuTHC6JSY8s=
|
||||
cloud.google.com/go/filestore v1.8.1/go.mod h1:MbN9KcaM47DRTIuLfQhJEsjaocVebNtNQhSLhKCF5GM=
|
||||
cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
|
||||
cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k=
|
||||
cloud.google.com/go/gkebackup v1.3.5/go.mod h1:KJ77KkNN7Wm1LdMopOelV6OodM01pMuK2/5Zt1t4Tvc=
|
||||
cloud.google.com/go/gkeconnect v0.8.5/go.mod h1:LC/rS7+CuJ5fgIbXv8tCD/mdfnlAadTaUufgOkmijuk=
|
||||
cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA=
|
||||
cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q=
|
||||
cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs=
|
||||
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
|
||||
cloud.google.com/go/iap v1.9.4/go.mod h1:vO4mSq0xNf/Pu6E5paORLASBwEmphXEjgCFg7aeNu1w=
|
||||
cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1UuBo=
|
||||
cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs=
|
||||
cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI=
|
||||
cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8=
|
||||
cloud.google.com/go/lifesciences v0.9.5/go.mod h1:OdBm0n7C0Osh5yZB7j9BXyrMnTRGBJIZonUMxo5CzPw=
|
||||
cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE=
|
||||
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
|
||||
cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI=
|
||||
cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI=
|
||||
cloud.google.com/go/mediatranslation v0.8.5/go.mod h1:y7kTHYIPCIfgyLbKncgqouXJtLsU+26hZhHEEy80fSs=
|
||||
cloud.google.com/go/memcache v1.10.5/go.mod h1:/FcblbNd0FdMsx4natdj+2GWzTq+cjZvMa1I+9QsuMA=
|
||||
cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE=
|
||||
cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg=
|
||||
cloud.google.com/go/networkconnectivity v1.14.4/go.mod h1:PU12q++/IMnDJAB+3r+tJtuCXCfwfN+C6Niyj6ji1Po=
|
||||
cloud.google.com/go/networkmanagement v1.9.4/go.mod h1:daWJAl0KTFytFL7ar33I6R/oNBH8eEOX/rBNHrC/8TA=
|
||||
cloud.google.com/go/networksecurity v0.9.5/go.mod h1:KNkjH/RsylSGyyZ8wXpue8xpCEK+bTtvof8SBfIhMG8=
|
||||
cloud.google.com/go/notebooks v1.11.3/go.mod h1:0wQyI2dQC3AZyQqWnRsp+yA+kY4gC7ZIVP4Qg3AQcgo=
|
||||
cloud.google.com/go/optimization v1.6.3/go.mod h1:8ve3svp3W6NFcAEFr4SfJxrldzhUl4VMUJmhrqVKtYA=
|
||||
cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8=
|
||||
cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I=
|
||||
cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8=
|
||||
cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws=
|
||||
cloud.google.com/go/phishingprotection v0.8.5/go.mod h1:g1smd68F7mF1hgQPuYn3z8HDbNre8L6Z0b7XMYFmX7I=
|
||||
cloud.google.com/go/policytroubleshooter v1.10.3/go.mod h1:+ZqG3agHT7WPb4EBIRqUv4OyIwRTZvsVDHZ8GlZaoxk=
|
||||
cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk=
|
||||
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
|
||||
cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE=
|
||||
cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0=
|
||||
cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU=
|
||||
cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ=
|
||||
cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0=
|
||||
cloud.google.com/go/redis v1.14.2/go.mod h1:g0Lu7RRRz46ENdFKQ2EcQZBAJ2PtJHJLuiiRuEXwyQw=
|
||||
cloud.google.com/go/resourcemanager v1.9.5/go.mod h1:hep6KjelHA+ToEjOfO3garMKi/CLYwTqeAw7YiEI9x8=
|
||||
cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I=
|
||||
cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE=
|
||||
cloud.google.com/go/run v1.3.4/go.mod h1:FGieuZvQ3tj1e9GnzXqrMABSuir38AJg5xhiYq+SF3o=
|
||||
cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE=
|
||||
cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4=
|
||||
cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc=
|
||||
cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU=
|
||||
cloud.google.com/go/servicedirectory v1.11.4/go.mod h1:Bz2T9t+/Ehg6x+Y7Ycq5xiShYLD96NfEsWNHyitj1qM=
|
||||
cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE=
|
||||
cloud.google.com/go/spanner v1.56.0/go.mod h1:DndqtUKQAt3VLuV2Le+9Y3WTnq5cNKrnLb/Piqcj+h0=
|
||||
cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA=
|
||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
||||
cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY=
|
||||
cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs=
|
||||
cloud.google.com/go/talent v1.6.6/go.mod h1:y/WQDKrhVz12WagoarpAIyKKMeKGKHWPoReZ0g8tseQ=
|
||||
cloud.google.com/go/texttospeech v1.7.5/go.mod h1:tzpCuNWPwrNJnEa4Pu5taALuZL4QRRLcb+K9pbhXT6M=
|
||||
cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs=
|
||||
cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M=
|
||||
cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk=
|
||||
cloud.google.com/go/video v1.20.4/go.mod h1:LyUVjyW+Bwj7dh3UJnUGZfyqjEto9DnrvTe1f/+QrW0=
|
||||
cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I=
|
||||
cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU=
|
||||
cloud.google.com/go/vmmigration v1.7.5/go.mod h1:pkvO6huVnVWzkFioxSghZxIGcsstDvYiVCxQ9ZH3eYI=
|
||||
cloud.google.com/go/vmwareengine v1.1.1/go.mod h1:nMpdsIVkUrSaX8UvmnBhzVzG7PPvNYc5BszcvIVudYs=
|
||||
cloud.google.com/go/vpcaccess v1.7.5/go.mod h1:slc5ZRvvjP78c2dnL7m4l4R9GwL3wDLcpIWz6P/ziig=
|
||||
cloud.google.com/go/webrisk v1.9.5/go.mod h1:aako0Fzep1Q714cPEM5E+mtYX8/jsfegAuS8aivxy3U=
|
||||
cloud.google.com/go/websecurityscanner v1.6.5/go.mod h1:QR+DWaxAz2pWooylsBF854/Ijvuoa3FCyS1zBa1rAVQ=
|
||||
cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3 h1:hJiie5Bf3QucGRa4ymsAUOxyhYwGEz1xrsVk0P8erlw=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
|
||||
@ -51,6 +170,8 @@ github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKd
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
|
||||
github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=
|
||||
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
@ -64,6 +185,8 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
@ -74,9 +197,11 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
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/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
|
||||
@ -100,6 +225,7 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
||||
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 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||
@ -108,6 +234,8 @@ github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbi
|
||||
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
|
||||
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
|
||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
|
||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
|
||||
@ -117,18 +245,23 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
|
||||
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
@ -157,7 +290,10 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/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-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
@ -167,6 +303,7 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
@ -175,6 +312,8 @@ github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo=
|
||||
github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
@ -187,51 +326,86 @@ github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/
|
||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0 h1:WcmKMm43DR7RdtlkEXQJyo5ws8iTp98CyhCCbOHMvNI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@ -242,7 +416,11 @@ github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs=
|
||||
github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNaAjyRJOo6AVS4=
|
||||
github.com/iris-contrib/httpexpect/v2 v2.12.1/go.mod h1:7+RB6W5oNClX7PTwJgJnsQP3ZuUUYB3u61KCqeSgZ88=
|
||||
github.com/iris-contrib/jade v1.1.4 h1:WoYdfyJFfZIUgqNAeOyRfTNQZOksSlZ6+FnXR3AEpX0=
|
||||
github.com/iris-contrib/jade v1.1.4/go.mod h1:EDqR+ur9piDl6DUgs6qRrlfzmlx/D5UybogqrXvJTBE=
|
||||
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
|
||||
@ -285,6 +463,8 @@ github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@ -310,6 +490,7 @@ github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T
|
||||
github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
|
||||
github.com/mailgun/raymond/v2 v2.0.46 h1:aOYHhvTpF5USySJ0o7cpPno/Uh2I5qg2115K25A+Ft4=
|
||||
github.com/mailgun/raymond/v2 v2.0.46/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
|
||||
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
|
||||
@ -331,6 +512,10 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
@ -341,6 +526,8 @@ github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXm
|
||||
github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY=
|
||||
github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
|
||||
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -354,10 +541,13 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
|
||||
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
|
||||
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
@ -372,23 +562,33 @@ github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaF
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg=
|
||||
github.com/quic-go/quic-go v0.39.4/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48 h1:vabduItPAIz9px5iryD5peyx7O3Ya8TBThapgXim98o=
|
||||
@ -441,6 +641,7 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
@ -449,12 +650,16 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=
|
||||
github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk=
|
||||
github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ=
|
||||
github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||
@ -463,10 +668,14 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc=
|
||||
github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
||||
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/viant/assertly v0.4.8 h1:5x1GzBaRteIwTr5RAGFVG14uNeRFxVNbXPWrK2qAgpc=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0 h1:6TteTDQ68CjgcCe8wH3D3ZhUQQOJXMTbj/D9rkk2a1k=
|
||||
@ -475,17 +684,38 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
|
||||
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
|
||||
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d h1:E2M5QgjZ/Jg+ObCQAudsXxuTsLj7Nl5RV/lZcQZmKSo=
|
||||
@ -494,13 +724,17 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
@ -508,17 +742,22 @@ golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6/go.mod h1:CxIveKay+FTh1D0yPZ
|
||||
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -526,12 +765,18 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
@ -540,8 +785,12 @@ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -554,22 +803,35 @@ golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -585,13 +847,20 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g=
|
||||
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
@ -611,17 +880,29 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
@ -632,6 +913,11 @@ golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
@ -640,6 +926,7 @@ google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
||||
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -654,6 +941,8 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
|
||||
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=
|
||||
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
|
||||
@ -661,6 +950,8 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA
|
||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||
@ -669,13 +960,38 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.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.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.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
|
||||
@ -691,7 +1007,10 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
||||
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
|
||||
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
||||
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Common Constants, Types, And Utilities
|
||||
|
||||
// firstNonIDRune returns the first non-printable or non-ASCII rune and its
|
||||
// index. If slashes is true, it also looks for slashes. If there are no such
|
||||
// runes, i is -1.
|
||||
|
@ -1,11 +1,27 @@
|
||||
package agd_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// Common long strings for tests.
|
||||
var (
|
||||
testLongStr = strings.Repeat("a", 200)
|
||||
testLongStrUnicode = strings.Repeat("ы", 200)
|
||||
)
|
||||
|
||||
// Common IDs for tests.
|
||||
const (
|
||||
testHumanIDStr = "My-Device-X--10"
|
||||
testHumanIDLowerStr = "my-device-x--10"
|
||||
|
||||
testHumanID agd.HumanID = testHumanIDStr
|
||||
)
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// Devices
|
||||
|
||||
// Device is a device of a device attached to a profile.
|
||||
//
|
||||
// NOTE: Do not change fields of this structure without incrementing
|
||||
@ -30,6 +28,10 @@ type Device struct {
|
||||
// Name is the human-readable name of the device.
|
||||
Name DeviceName
|
||||
|
||||
// HumanIDLower is the lower-case version of the normalized [HumanID] used
|
||||
// to create this device, if any.
|
||||
HumanIDLower HumanIDLower
|
||||
|
||||
// DedicatedIPs are the destination (server) IP-addresses dedicated to this
|
||||
// device, if any. A device can use one of these addresses as a DNS server
|
||||
// address for AdGuard DNS to recognize it.
|
||||
|
@ -1,7 +1,6 @@
|
||||
package agd_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
@ -12,9 +11,6 @@ import (
|
||||
func TestNewDeviceName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tooLong := strings.Repeat("a", 200)
|
||||
tooLongUnicode := strings.Repeat("ы", 200)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
@ -33,12 +29,12 @@ func TestNewDeviceName(t *testing.T) {
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "too_long",
|
||||
in: tooLong,
|
||||
wantErrMsg: `bad device name "` + tooLong + `": too long: got 200 runes, max 128`,
|
||||
in: testLongStr,
|
||||
wantErrMsg: `bad device name "` + testLongStr + `": too long: got 200 runes, max 128`,
|
||||
}, {
|
||||
name: "too_long_unicode",
|
||||
in: tooLongUnicode,
|
||||
wantErrMsg: `bad device name "` + tooLongUnicode + `": too long: got 200 runes, max 128`,
|
||||
in: testLongStrUnicode,
|
||||
wantErrMsg: `bad device name "` + testLongStrUnicode + `": too long: got 200 runes, max 128`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
82
internal/agd/devicetype.go
Normal file
82
internal/agd/devicetype.go
Normal file
@ -0,0 +1,82 @@
|
||||
package agd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// DeviceType is a type of a device as used in the Backend API.
|
||||
type DeviceType uint8
|
||||
|
||||
// DeviceType values.
|
||||
//
|
||||
// Do not change the order. Keep in sync with the Backend API.
|
||||
const (
|
||||
DeviceTypeNone DeviceType = 0
|
||||
DeviceTypeWindows DeviceType = 1
|
||||
DeviceTypeAndroid DeviceType = 2
|
||||
DeviceTypeMacOS DeviceType = 3
|
||||
DeviceTypeIOS DeviceType = 4
|
||||
DeviceTypeLinux DeviceType = 5
|
||||
DeviceTypeRouter DeviceType = 6
|
||||
DeviceTypeSmartTV DeviceType = 7
|
||||
DeviceTypeGameConsole DeviceType = 8
|
||||
DeviceTypeOther DeviceType = 9
|
||||
)
|
||||
|
||||
// deviceTypeStrings is a mapping between a device type and its default string
|
||||
// representation. Keep in sync with the DNS API.
|
||||
var deviceTypeStrings = []string{
|
||||
DeviceTypeNone: "(none)",
|
||||
DeviceTypeWindows: "win",
|
||||
DeviceTypeAndroid: "adr",
|
||||
DeviceTypeMacOS: "mac",
|
||||
DeviceTypeIOS: "ios",
|
||||
DeviceTypeLinux: "lnx",
|
||||
DeviceTypeRouter: "rtr",
|
||||
DeviceTypeSmartTV: "stv",
|
||||
DeviceTypeGameConsole: "gam",
|
||||
DeviceTypeOther: "otr",
|
||||
}
|
||||
|
||||
// DeviceTypeFromDNS converts a string into a valid device type. s is assumed
|
||||
// to be from a DNS FQDN or HTTP path. The matching is case-insensitive, and
|
||||
// "(none)", the string representation of [DeviceTypeNone], is not recognized,
|
||||
// since it's not a valid type in the DNS API.
|
||||
func DeviceTypeFromDNS(s string) (dt DeviceType, err error) {
|
||||
// Do not use [errors.Annotate] here, because it allocates even when the
|
||||
// error is nil.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bad device type %q: %w", s, err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = ValidateInclusion(len(s), 3, 3, UnitByte)
|
||||
if err != nil {
|
||||
// The error will be wrapped by the deferred helper.
|
||||
return DeviceTypeNone, err
|
||||
}
|
||||
|
||||
for i, dtStr := range deviceTypeStrings[1:] {
|
||||
if strings.EqualFold(s, dtStr) {
|
||||
return DeviceType(i + 1), nil
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceTypeNone, errors.Error("unknown device type")
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ fmt.Stringer = DeviceTypeNone
|
||||
|
||||
// String implements the [fmt.Stringer] interface for DeviceType.
|
||||
func (dt DeviceType) String() (s string) {
|
||||
if int(dt) < len(deviceTypeStrings) {
|
||||
return deviceTypeStrings[dt]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("!bad_device_type_%d", dt)
|
||||
}
|
68
internal/agd/devicetype_test.go
Normal file
68
internal/agd/devicetype_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package agd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeviceTypeFromDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrMsg string
|
||||
want agd.DeviceType
|
||||
}{{
|
||||
name: "success",
|
||||
in: "adr",
|
||||
wantErrMsg: "",
|
||||
want: agd.DeviceTypeAndroid,
|
||||
}, {
|
||||
name: "success_case",
|
||||
in: "Adr",
|
||||
wantErrMsg: "",
|
||||
want: agd.DeviceTypeAndroid,
|
||||
}, {
|
||||
name: "too_long",
|
||||
in: "windows",
|
||||
wantErrMsg: `bad device type "windows": too long: got 7 bytes, max 3`,
|
||||
want: agd.DeviceTypeNone,
|
||||
}, {
|
||||
name: "too_small",
|
||||
in: "x",
|
||||
wantErrMsg: `bad device type "x": too short: got 1 bytes, min 3`,
|
||||
want: agd.DeviceTypeNone,
|
||||
}, {
|
||||
name: "none",
|
||||
in: "(none)",
|
||||
wantErrMsg: `bad device type "(none)": too long: got 6 bytes, max 3`,
|
||||
want: agd.DeviceTypeNone,
|
||||
}, {
|
||||
name: "unknown",
|
||||
in: "xxx",
|
||||
wantErrMsg: `bad device type "xxx": unknown device type`,
|
||||
want: agd.DeviceTypeNone,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := agd.DeviceTypeFromDNS(tc.in)
|
||||
assert.Equal(t, tc.want, got)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeviceType_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, "(none)", agd.DeviceTypeNone.String())
|
||||
assert.Equal(t, "adr", agd.DeviceTypeAndroid.String())
|
||||
assert.Equal(t, "!bad_device_type_42", agd.DeviceType(42).String())
|
||||
}
|
@ -12,8 +12,6 @@ import (
|
||||
func TestNewFilterListID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tooLong := strings.Repeat("a", agd.MaxFilterListIDLen+1)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
@ -28,8 +26,8 @@ func TestNewFilterListID(t *testing.T) {
|
||||
wantErrMsg: `bad filter list id "": too short: got 0 bytes, min 1`,
|
||||
}, {
|
||||
name: "too_long",
|
||||
in: tooLong,
|
||||
wantErrMsg: `bad filter list id "` + tooLong + `": too long: got 129 bytes, max 128`,
|
||||
in: testLongStr,
|
||||
wantErrMsg: `bad filter list id "` + testLongStr + `": too long: got 200 bytes, max 128`,
|
||||
}, {
|
||||
name: "bad",
|
||||
in: "bad/name",
|
||||
|
279
internal/agd/humanid.go
Normal file
279
internal/agd/humanid.go
Normal file
@ -0,0 +1,279 @@
|
||||
package agd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/syncutil"
|
||||
)
|
||||
|
||||
// HumanID is a more human-readable identifier of a device.
|
||||
type HumanID string
|
||||
|
||||
const (
|
||||
// MaxHumanIDLen is the maximum length of a human-readable device ID.
|
||||
MaxHumanIDLen = netutil.MaxDomainLabelLen
|
||||
|
||||
// MinHumanIDLen is the minimum length of a human-readable device ID.
|
||||
MinHumanIDLen = 1
|
||||
)
|
||||
|
||||
// NewHumanID converts a simple string into a HumanID and makes sure that it's
|
||||
// valid. This should be preferred to a simple type conversion.
|
||||
//
|
||||
// TODO(a.garipov): Remove if it remains unused.
|
||||
func NewHumanID(s string) (id HumanID, err error) {
|
||||
// Do not use [errors.Annotate] here, because it allocates even when the
|
||||
// error is nil.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bad human id %q: %w", s, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return newHumanID(s)
|
||||
}
|
||||
|
||||
// newHumanID converts a simple string into a HumanID and makes sure that it's
|
||||
// valid. It does not wrap the error to be used in places where that could
|
||||
// create additional allocations.
|
||||
func newHumanID(s string) (id HumanID, err error) {
|
||||
err = ValidateInclusion(len(s), MaxHumanIDLen, MinHumanIDLen, UnitByte)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because the caller should do that.
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Add boolean versions to netutil to avoid allocations of
|
||||
// errors that aren't used.
|
||||
err = netutil.ValidateHostnameLabel(s)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because the caller should do that.
|
||||
return "", err
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "---"); i >= 0 {
|
||||
return "", fmt.Errorf("at index %d: max 2 consecutive hyphens are allowed", i)
|
||||
}
|
||||
|
||||
return HumanID(s), nil
|
||||
}
|
||||
|
||||
// HumanIDLower is the type for [HumanID] values that must be lowercase.
|
||||
type HumanIDLower string
|
||||
|
||||
// NewHumanIDLower converts a simple string into a HumanIDLower and makes sure
|
||||
// that it's valid and lowercased. This should be preferred to a simple type
|
||||
// conversion.
|
||||
func NewHumanIDLower(s string) (id HumanIDLower, err error) {
|
||||
// Do not use [errors.Annotate] here, because it allocates even when the
|
||||
// error is nil.
|
||||
|
||||
humanID, err := newHumanID(s)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("bad lowercase human id %q: %w", s, err)
|
||||
}
|
||||
|
||||
for i, r := range humanID {
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
return "", fmt.Errorf(
|
||||
"bad lowercase human id %q: at index %d: %q is not lowercase",
|
||||
s,
|
||||
i,
|
||||
r,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return HumanIDLower(s), nil
|
||||
}
|
||||
|
||||
// HumanIDToLower returns a lowercase version of id.
|
||||
func HumanIDToLower(id HumanID) (lower HumanIDLower) {
|
||||
return HumanIDLower(strings.ToLower(string(id)))
|
||||
}
|
||||
|
||||
// HumanIDParser normalizes and parses a HumanID from a string.
|
||||
type HumanIDParser struct {
|
||||
pool *syncutil.Pool[bytes.Buffer]
|
||||
}
|
||||
|
||||
// NewHumanIDParser creates a new HumanIDParser.
|
||||
func NewHumanIDParser() (p *HumanIDParser) {
|
||||
return &HumanIDParser{
|
||||
pool: syncutil.NewPool(func() (buf *bytes.Buffer) {
|
||||
return bytes.NewBuffer(make([]byte, 0, netutil.MaxDomainNameLen))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseNormalized normalizes and parses a HumanID from a string that may have
|
||||
// issues, such as extra symbols that aren't supported. The normalization is
|
||||
// best-effort and may still fail, in which case id is empty and err is not nil.
|
||||
func (p *HumanIDParser) ParseNormalized(s string) (id HumanID, err error) {
|
||||
id, err = newHumanID(s)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Do not use [errors.Annotate] here, because it allocates even when the
|
||||
// error is nil.
|
||||
original := s
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bad non-normalized human id %q: %w", original, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Immediately validate it against the upper DNS hostname-length limit.
|
||||
err = ValidateInclusion(len(s), netutil.MaxDomainNameLen, MinHumanIDLen, UnitByte)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because there is already a deferred wrap, and
|
||||
// the error is informative enough as is.
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := p.pool.Get()
|
||||
defer func() { p.pool.Put(buf) }()
|
||||
|
||||
buf.Reset()
|
||||
n := humanIDNormalizer{
|
||||
buf: buf,
|
||||
}
|
||||
|
||||
for s != "" {
|
||||
r, sz := utf8.DecodeRuneInString(s)
|
||||
s = s[sz:]
|
||||
|
||||
n.next(r)
|
||||
}
|
||||
|
||||
s = n.result()
|
||||
if s == "" || s == "-" {
|
||||
return "", errors.Error("cannot normalize")
|
||||
}
|
||||
|
||||
id, err = newHumanID(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// humanIDNormalizer is a stateful normalizer of human-readable device
|
||||
// identifiers.
|
||||
type humanIDNormalizer struct {
|
||||
buf *bytes.Buffer
|
||||
state uint8
|
||||
prevRune rune
|
||||
prevPrevRune rune
|
||||
}
|
||||
|
||||
// [humanIDNormalizer] states.
|
||||
const (
|
||||
humanIDNormStateInitial uint8 = iota
|
||||
humanIDNormStateInvalid
|
||||
humanIDNormStateValid
|
||||
)
|
||||
|
||||
// next writes r to the buffer, if it is valid.
|
||||
func (p *humanIDNormalizer) next(r rune) {
|
||||
switch p.state {
|
||||
case humanIDNormStateInitial:
|
||||
p.nextInitial(r)
|
||||
case humanIDNormStateValid:
|
||||
p.nextValid(r)
|
||||
case humanIDNormStateInvalid:
|
||||
p.nextInvalid(r)
|
||||
default:
|
||||
panic(fmt.Errorf("bad humanIDNormalizer state %d", p.state))
|
||||
}
|
||||
}
|
||||
|
||||
// nextInitial processes the initial state of the normalizer.
|
||||
func (p *humanIDNormalizer) nextInitial(r rune) {
|
||||
if !netutil.IsValidHostOuterRune(r) {
|
||||
return
|
||||
}
|
||||
|
||||
p.state = humanIDNormStateValid
|
||||
p.write(r)
|
||||
}
|
||||
|
||||
// nextValid processes the valid state of the normalizer.
|
||||
func (p *humanIDNormalizer) nextValid(r rune) {
|
||||
if r == '-' {
|
||||
if p.prevPrevRune == '-' && p.prevRune == '-' {
|
||||
p.buf.Truncate(p.buf.Len() - 2)
|
||||
p.prevPrevRune = utf8.RuneError
|
||||
p.prevRune = utf8.RuneError
|
||||
p.state = humanIDNormStateInvalid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
p.write(r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !netutil.IsValidHostOuterRune(r) {
|
||||
p.truncateHyphens()
|
||||
p.state = humanIDNormStateInvalid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
p.write(r)
|
||||
}
|
||||
|
||||
// truncateHyphens removes the unnecessary hyphens from the buffer if necessary.
|
||||
func (p *humanIDNormalizer) truncateHyphens() {
|
||||
if p.prevRune != '-' {
|
||||
return
|
||||
}
|
||||
|
||||
if p.prevPrevRune == '-' {
|
||||
p.buf.Truncate(p.buf.Len() - 2)
|
||||
p.prevPrevRune = utf8.RuneError
|
||||
} else {
|
||||
p.buf.Truncate(p.buf.Len() - 1)
|
||||
}
|
||||
|
||||
p.prevRune = utf8.RuneError
|
||||
}
|
||||
|
||||
// nextInvalid processes the invalid state of the normalizer.
|
||||
func (p *humanIDNormalizer) nextInvalid(r rune) {
|
||||
if !netutil.IsValidHostOuterRune(r) {
|
||||
return
|
||||
}
|
||||
|
||||
p.state = humanIDNormStateValid
|
||||
if p.prevRune != '-' {
|
||||
p.write('-')
|
||||
}
|
||||
|
||||
p.write(r)
|
||||
}
|
||||
|
||||
// write writes r to the buffer while also updating the previous runes.
|
||||
func (p *humanIDNormalizer) write(r rune) {
|
||||
_, _ = p.buf.WriteRune(r)
|
||||
p.prevPrevRune = p.prevRune
|
||||
p.prevRune = r
|
||||
}
|
||||
|
||||
// result returns the result of the normalization.
|
||||
func (p *humanIDNormalizer) result() (s string) {
|
||||
b := p.buf.Bytes()
|
||||
b = b[:min(len(b), MaxHumanIDLen)]
|
||||
b = bytes.TrimRight(b, "-")
|
||||
|
||||
return string(b)
|
||||
}
|
275
internal/agd/humanid_test.go
Normal file
275
internal/agd/humanid_test.go
Normal file
@ -0,0 +1,275 @@
|
||||
package agd_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewHumanID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "success",
|
||||
in: testHumanIDStr,
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "too_long",
|
||||
in: testLongStr,
|
||||
wantErrMsg: `bad human id "` + testLongStr + `": too long: got 200 bytes, max 63`,
|
||||
}, {
|
||||
name: "too_small",
|
||||
in: "",
|
||||
wantErrMsg: `bad human id "": too short: got 0 bytes, min 1`,
|
||||
}, {
|
||||
name: "bad_start",
|
||||
in: "-My-Device-X--10",
|
||||
wantErrMsg: `bad human id "-My-Device-X--10": bad hostname label "-My-Device-X--10": ` +
|
||||
`bad hostname label rune '-'`,
|
||||
}, {
|
||||
name: "bad_middle",
|
||||
in: "My-Device-X---10",
|
||||
wantErrMsg: `bad human id "My-Device-X---10": at index 11: ` +
|
||||
`max 2 consecutive hyphens are allowed`,
|
||||
}, {
|
||||
name: "bad_rune",
|
||||
in: "My-Device-X--10!",
|
||||
wantErrMsg: `bad human id "My-Device-X--10!": bad hostname label "My-Device-X--10!": ` +
|
||||
`bad hostname label rune '!'`,
|
||||
}, {
|
||||
name: "bad_end",
|
||||
in: "My-Device-X--10-",
|
||||
wantErrMsg: `bad human id "My-Device-X--10-": bad hostname label "My-Device-X--10-": ` +
|
||||
`bad hostname label rune '-'`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := agd.NewHumanID(tc.in)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
if tc.wantErrMsg == "" {
|
||||
assert.Equal(t, tc.in, string(id))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHumanIDLower(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "success",
|
||||
in: testHumanIDLowerStr,
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "bad_case",
|
||||
in: "my-device-X--10",
|
||||
wantErrMsg: `bad lowercase human id "my-device-X--10": at index 10: 'X' is not lowercase`,
|
||||
}, {
|
||||
name: "bad_rune",
|
||||
in: "My-Device-X--10!",
|
||||
wantErrMsg: `bad lowercase human id "My-Device-X--10!": ` +
|
||||
`bad hostname label "My-Device-X--10!": bad hostname label rune '!'`,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := agd.NewHumanIDLower(tc.in)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
if tc.wantErrMsg == "" {
|
||||
assert.Equal(t, tc.in, string(id))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHumanIDParser_ParseNormalized(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrMsg string
|
||||
wantID agd.HumanID
|
||||
}{{
|
||||
name: "success",
|
||||
in: testHumanIDStr,
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "too_long",
|
||||
in: testLongStr,
|
||||
wantErrMsg: "",
|
||||
wantID: agd.HumanID(strings.Repeat("a", agd.MaxHumanIDLen)),
|
||||
}, {
|
||||
name: "too_small",
|
||||
in: "",
|
||||
wantErrMsg: `bad non-normalized human id "": too short: got 0 bytes, min 1`,
|
||||
}, {
|
||||
name: "bad_start",
|
||||
in: "-My-Device-X--10",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "bad_middle",
|
||||
in: "My-Device-X---10",
|
||||
wantErrMsg: "",
|
||||
wantID: "My-Device-X-10",
|
||||
}, {
|
||||
name: "bad_rune",
|
||||
in: "My-Device-X--10!",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "bad_end",
|
||||
in: "My-Device-X--10-",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "bad_chars_start",
|
||||
in: "абв-My-Device-X--10",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "bad_chars_end",
|
||||
in: "My-Device-X--10-абв",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "bad_chars_middle",
|
||||
in: "My-Device-Xабв10",
|
||||
wantErrMsg: "",
|
||||
wantID: "My-Device-X-10",
|
||||
}, {
|
||||
name: "bad_chars_middle_hyphens",
|
||||
in: "My-Device-X-абв-10",
|
||||
wantErrMsg: "",
|
||||
wantID: "My-Device-X-10",
|
||||
}, {
|
||||
name: "bad_chars_middle_two_hyphens",
|
||||
in: "My-Device-X--абв--10",
|
||||
wantErrMsg: "",
|
||||
wantID: "My-Device-X-10",
|
||||
}, {
|
||||
name: "bad_chars_middle_two_hyphens_other",
|
||||
in: "My-DeviceабвX--10",
|
||||
wantErrMsg: "",
|
||||
wantID: testHumanID,
|
||||
}, {
|
||||
name: "one_bad_char",
|
||||
in: "!",
|
||||
wantErrMsg: `bad non-normalized human id "!": cannot normalize`,
|
||||
wantID: "",
|
||||
}, {
|
||||
name: "only_bad_chars",
|
||||
in: "!!!",
|
||||
wantErrMsg: `bad non-normalized human id "!!!": cannot normalize`,
|
||||
wantID: "",
|
||||
}}
|
||||
|
||||
p := agd.NewHumanIDParser()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
id, err := p.ParseNormalized(tc.in)
|
||||
assert.Equalf(t, tc.wantID, id, "original: %q", tc.in)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sinks for benchmarks.
|
||||
var (
|
||||
humanIDSink agd.HumanID
|
||||
errSink error
|
||||
)
|
||||
|
||||
func BenchmarkHumanIDParser_ParseNormalized(b *testing.B) {
|
||||
benchCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrPresent bool
|
||||
}{{
|
||||
name: "valid",
|
||||
in: testHumanIDStr,
|
||||
wantErrPresent: false,
|
||||
}, {
|
||||
name: "normalized",
|
||||
in: testHumanIDStr + "-!!!",
|
||||
wantErrPresent: false,
|
||||
}, {
|
||||
name: "normalized_long",
|
||||
in: testLongStr,
|
||||
wantErrPresent: false,
|
||||
}, {
|
||||
name: "bad",
|
||||
in: "!!!",
|
||||
wantErrPresent: true,
|
||||
}}
|
||||
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
p := agd.NewHumanIDParser()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
humanIDSink, errSink = p.ParseNormalized(bc.in)
|
||||
}
|
||||
|
||||
if bc.wantErrPresent {
|
||||
require.Empty(b, humanIDSink)
|
||||
require.Error(b, errSink)
|
||||
} else {
|
||||
require.NotEmpty(b, humanIDSink)
|
||||
require.NoError(b, errSink)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
|
||||
// goos: linux
|
||||
// goarch: amd64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agd
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkHumanIDParser_ParseNormalized/valid-16 23716176 51.48 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkHumanIDParser_ParseNormalized/normalized-16 1000000 1071 ns/op 88 B/op 3 allocs/op
|
||||
// BenchmarkHumanIDParser_ParseNormalized/normalized_long-16 504042 4086 ns/op 128 B/op 4 allocs/op
|
||||
// BenchmarkHumanIDParser_ParseNormalized/bad-16 752247 1361 ns/op 184 B/op 5 allocs/op
|
||||
}
|
||||
|
||||
func FuzzHumanIDParser_ParseNormalized(f *testing.F) {
|
||||
p := agd.NewHumanIDParser()
|
||||
|
||||
f.Fuzz(func(t *testing.T, input string) {
|
||||
s, err := p.ParseNormalized(input)
|
||||
if err != nil {
|
||||
require.Empty(t, s)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, s)
|
||||
assert.LessOrEqual(t, len(s), agd.MaxHumanIDLen)
|
||||
})
|
||||
}
|
@ -11,8 +11,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// Profiles
|
||||
|
||||
// Profile contains information about an AdGuard DNS profile. In other parts of
|
||||
// the infrastructure, a profile is also called a “DNS server”. We call it
|
||||
// profile, because it's less confusing.
|
||||
@ -139,6 +137,13 @@ type Profile struct {
|
||||
// NOTE: Do not change fields of this structure without incrementing
|
||||
// [internal/profiledb/internal.FileCacheVersion].
|
||||
IPLogEnabled bool
|
||||
|
||||
// AutoDevicesEnabled shows if the automatic creation of devices using
|
||||
// HumanIDs should be enabled for this profile.
|
||||
//
|
||||
// NOTE: Do not change fields of this structure without incrementing
|
||||
// [internal/profiledb/internal.FileCacheVersion].
|
||||
AutoDevicesEnabled bool
|
||||
}
|
||||
|
||||
// ProfileID is the ID of a profile. It is an opaque string.
|
||||
|
@ -82,25 +82,3 @@ func (c *LRU[K, T]) Len() (n int) {
|
||||
|
||||
return c.cache.Len(checkExpired)
|
||||
}
|
||||
|
||||
// Manager manages caches.
|
||||
type Manager struct {
|
||||
caches map[string]Clearer
|
||||
}
|
||||
|
||||
// NewManager returns a new initialized *Manager.
|
||||
func NewManager() (m *Manager) {
|
||||
return &Manager{
|
||||
caches: map[string]Clearer{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds cache by id.
|
||||
func (m *Manager) Add(id string, cache Clearer) {
|
||||
m.caches[id] = cache
|
||||
}
|
||||
|
||||
// ClearByID clears cache by id.
|
||||
func (m *Manager) ClearByID(id string) {
|
||||
m.caches[id].Clear()
|
||||
}
|
||||
|
@ -35,35 +35,3 @@ func TestLRU(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 0, cache.Len())
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
const (
|
||||
key = "key"
|
||||
val = 123
|
||||
id = "cacheID"
|
||||
|
||||
nonExistingKey = "nonExistingKey"
|
||||
)
|
||||
|
||||
cache := agdcache.NewLRU[string, int](&agdcache.LRUConfig{
|
||||
Size: 10,
|
||||
})
|
||||
|
||||
cache.Set(key, val)
|
||||
|
||||
assert.Equal(t, 1, cache.Len())
|
||||
|
||||
v, ok := cache.Get(key)
|
||||
assert.Equal(t, val, v)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, ok = cache.Get(nonExistingKey)
|
||||
assert.Equal(t, 0, v)
|
||||
assert.False(t, ok)
|
||||
|
||||
m := agdcache.NewManager()
|
||||
m.Add(id, cache)
|
||||
m.ClearByID(id)
|
||||
|
||||
assert.Equal(t, 0, cache.Len())
|
||||
}
|
||||
|
89
internal/agdcache/manager.go
Normal file
89
internal/agdcache/manager.go
Normal file
@ -0,0 +1,89 @@
|
||||
package agdcache
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// Manager is the cache manager interface. All methods must be safe for
|
||||
// concurrent use.
|
||||
type Manager interface {
|
||||
// Add adds cache by id. cache must not be nil.
|
||||
//
|
||||
// TODO(s.chzhen): Add Set method that rewrites the cache associated with
|
||||
// id (current Add implementation).
|
||||
//
|
||||
// TODO(s.chzhen): Add panic on adding cache with duplicate id.
|
||||
Add(id string, cache Clearer)
|
||||
|
||||
// ClearByID clears cache by id.
|
||||
ClearByID(id string)
|
||||
}
|
||||
|
||||
// DefaultManager implements the [Manager] interface that stores caches and can
|
||||
// clear them by id
|
||||
type DefaultManager struct {
|
||||
mu *sync.Mutex
|
||||
caches map[string]Clearer
|
||||
}
|
||||
|
||||
// NewDefaultManager returns a new initialized *DefaultManager.
|
||||
func NewDefaultManager() (m *DefaultManager) {
|
||||
return &DefaultManager{
|
||||
mu: &sync.Mutex{},
|
||||
caches: map[string]Clearer{},
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Manager = (*DefaultManager)(nil)
|
||||
|
||||
// Add implements the [Manager] interface for *DefaultManager. Note that it
|
||||
// replaces the saved cache with the same id if there is one.
|
||||
func (m *DefaultManager) Add(id string, cache Clearer) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.caches[id] = cache
|
||||
}
|
||||
|
||||
// ClearByID implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) ClearByID(id string) {
|
||||
cache := m.findByID(id)
|
||||
if cache != nil {
|
||||
cache.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// findByID returns the stored cache by id or nil.
|
||||
func (m *DefaultManager) findByID(id string) (cache Clearer) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
return m.caches[id]
|
||||
}
|
||||
|
||||
// IDs returns a sorted list of stored cache identifiers.
|
||||
func (m *DefaultManager) IDs() (ids []string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
ids = maps.Keys(m.caches)
|
||||
slices.Sort(ids)
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// EmptyManager implements the [Manager] interface that does nothing.
|
||||
type EmptyManager struct{}
|
||||
|
||||
// type check
|
||||
var _ Manager = EmptyManager{}
|
||||
|
||||
// Add implements the [Manager] interface for *EmptyManager.
|
||||
func (EmptyManager) Add(_ string, _ Clearer) {}
|
||||
|
||||
// ClearByID implements the [Manager] interface for *EmptyManager.
|
||||
func (EmptyManager) ClearByID(_ string) {}
|
43
internal/agdcache/manager_test.go
Normal file
43
internal/agdcache/manager_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package agdcache_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
const (
|
||||
cacheID = "cacheID"
|
||||
cacheIDNonExisting = "non_existing_cache_id"
|
||||
)
|
||||
|
||||
isCleared := false
|
||||
mc := &mockClearer{
|
||||
onClear: func() {
|
||||
isCleared = true
|
||||
},
|
||||
}
|
||||
|
||||
m := agdcache.NewDefaultManager()
|
||||
m.Add(cacheID, mc)
|
||||
m.ClearByID(cacheID)
|
||||
|
||||
assert.True(t, isCleared)
|
||||
|
||||
assert.NotPanics(t, func() { m.ClearByID(cacheIDNonExisting) })
|
||||
}
|
||||
|
||||
// mockClearer is the mock implementation of the [agdcache.Clearer] for tests.
|
||||
type mockClearer struct {
|
||||
onClear func()
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ agdcache.Clearer = (*mockClearer)(nil)
|
||||
|
||||
// Clear implements the [agdcache.Clearer] interface for *mockClearer.
|
||||
func (mc *mockClearer) Clear() {
|
||||
mc.onClear()
|
||||
}
|
@ -284,20 +284,55 @@ var _ profiledb.Interface = (*ProfileDB)(nil)
|
||||
|
||||
// ProfileDB is a [profiledb.Interface] for tests.
|
||||
type ProfileDB struct {
|
||||
OnProfileByDeviceID func(
|
||||
OnCreateAutoDevice func(
|
||||
ctx context.Context,
|
||||
id agd.DeviceID,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error)
|
||||
|
||||
OnProfileByDedicatedIP func(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error)
|
||||
|
||||
OnProfileByDeviceID func(
|
||||
ctx context.Context,
|
||||
id agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error)
|
||||
|
||||
OnProfileByHumanID func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error)
|
||||
|
||||
OnProfileByLinkedIP func(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error)
|
||||
}
|
||||
|
||||
// CreateAutoDevice implements the [profiledb.Interface] interface for
|
||||
// *ProfileDB.
|
||||
func (db *ProfileDB) CreateAutoDevice(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return db.OnCreateAutoDevice(ctx, id, humanID, devType)
|
||||
}
|
||||
|
||||
// ProfileByDedicatedIP implements the [profiledb.Interface] interface for
|
||||
// *ProfileDB.
|
||||
func (db *ProfileDB) ProfileByDedicatedIP(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return db.OnProfileByDedicatedIP(ctx, ip)
|
||||
}
|
||||
|
||||
// ProfileByDeviceID implements the [profiledb.Interface] interface for
|
||||
// *ProfileDB.
|
||||
func (db *ProfileDB) ProfileByDeviceID(
|
||||
@ -307,13 +342,14 @@ func (db *ProfileDB) ProfileByDeviceID(
|
||||
return db.OnProfileByDeviceID(ctx, id)
|
||||
}
|
||||
|
||||
// ProfileByDedicatedIP implements the [profiledb.Interface] interface for
|
||||
// ProfileByHumanID implements the [profiledb.Interface] interface for
|
||||
// *ProfileDB.
|
||||
func (db *ProfileDB) ProfileByDedicatedIP(
|
||||
func (db *ProfileDB) ProfileByHumanID(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return db.OnProfileByDedicatedIP(ctx, ip)
|
||||
return db.OnProfileByHumanID(ctx, id, humanID)
|
||||
}
|
||||
|
||||
// ProfileByLinkedIP implements the [profiledb.Interface] interface for
|
||||
@ -328,19 +364,33 @@ func (db *ProfileDB) ProfileByLinkedIP(
|
||||
// type check
|
||||
var _ profiledb.Storage = (*ProfileStorage)(nil)
|
||||
|
||||
// ProfileStorage is a profiledb.Storage for tests.
|
||||
// ProfileStorage is a [profiledb.Storage] implementation for tests.
|
||||
type ProfileStorage struct {
|
||||
OnCreateAutoDevice func(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageCreateAutoDeviceRequest,
|
||||
) (resp *profiledb.StorageCreateAutoDeviceResponse, err error)
|
||||
|
||||
OnProfiles func(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageRequest,
|
||||
) (resp *profiledb.StorageResponse, err error)
|
||||
req *profiledb.StorageProfilesRequest,
|
||||
) (resp *profiledb.StorageProfilesResponse, err error)
|
||||
}
|
||||
|
||||
// CreateAutoDevice implements the [profiledb.Storage] interface for
|
||||
// *ProfileStorage.
|
||||
func (s *ProfileStorage) CreateAutoDevice(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageCreateAutoDeviceRequest,
|
||||
) (resp *profiledb.StorageCreateAutoDeviceResponse, err error) {
|
||||
return s.OnCreateAutoDevice(ctx, req)
|
||||
}
|
||||
|
||||
// Profiles implements the [profiledb.Storage] interface for *ProfileStorage.
|
||||
func (s *ProfileStorage) Profiles(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageRequest,
|
||||
) (resp *profiledb.StorageResponse, err error) {
|
||||
req *profiledb.StorageProfilesRequest,
|
||||
) (resp *profiledb.StorageProfilesResponse, err error) {
|
||||
return s.OnProfiles(ctx, req)
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
||||
// Package backendpb contains the protobuf structures for the backend API.
|
||||
//
|
||||
// TODO(a.garipov): Move the generated code into a separate package.
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
@ -7,9 +9,11 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// newClient returns new properly initialized DNSServiceClient.
|
||||
@ -25,7 +29,7 @@ func newClient(apiURL *url.URL) (client DNSServiceClient, err error) {
|
||||
return nil, fmt.Errorf("bad grpc url scheme %q", s)
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(apiURL.Host, grpc.WithTransportCredentials(creds))
|
||||
conn, err := grpc.NewClient(apiURL.Host, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dialing: %w", err)
|
||||
}
|
||||
@ -41,3 +45,17 @@ func newClient(apiURL *url.URL) (client DNSServiceClient, err error) {
|
||||
func reportf(ctx context.Context, errColl errcoll.Interface, format string, args ...any) {
|
||||
errcoll.Collectf(ctx, errColl, "backendpb: "+format, args...)
|
||||
}
|
||||
|
||||
// ctxWithAuthentication adds the API key authentication header to the outgoing
|
||||
// request context if apiKey is not empty. If it is empty, ctx is parent.
|
||||
func ctxWithAuthentication(parent context.Context, apiKey string) (ctx context.Context) {
|
||||
ctx = parent
|
||||
if apiKey == "" {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Better validations for the key.
|
||||
md := metadata.Pairs(httphdr.Authorization, fmt.Sprintf("Bearer %s", apiKey))
|
||||
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
37
internal/backendpb/backendpb_internal_test.go
Normal file
37
internal/backendpb/backendpb_internal_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// Common IDs for tests and their string forms.
|
||||
//
|
||||
// TODO(a.garipov): Move all or most tests into external and unexport these.
|
||||
const (
|
||||
TestDeviceIDStr = "dev1234"
|
||||
TestHumanIDStr = "My-Device-X--10"
|
||||
TestHumanIDLowerStr = "my-device-x--10"
|
||||
TestProfileIDStr = "prof1234"
|
||||
|
||||
TestDeviceID agd.DeviceID = TestDeviceIDStr
|
||||
TestHumanID agd.HumanID = TestHumanIDStr
|
||||
TestHumanIDLower agd.HumanIDLower = TestHumanIDLowerStr
|
||||
TestProfileID agd.ProfileID = TestProfileIDStr
|
||||
)
|
||||
|
||||
// TestUpdTime is the common update time for tests.
|
||||
var TestUpdTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// TestBind includes any IPv4 address.
|
||||
//
|
||||
// TODO(a.garipov): Add to golibs/netutil.
|
||||
var TestBind = netip.MustParsePrefix("0.0.0.0/0")
|
@ -1,6 +1,14 @@
|
||||
package backendpb_test
|
||||
|
||||
import "github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
|
||||
)
|
||||
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// testDNSServiceServer is the [backendpb.DNSServiceServer] for tests.
|
||||
//
|
||||
@ -8,10 +16,18 @@ import "github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
|
||||
// test.
|
||||
type testDNSServiceServer struct {
|
||||
backendpb.UnimplementedDNSServiceServer
|
||||
|
||||
//lint:ignore ST1003 Keep in sync with the generated code.
|
||||
OnCreateDeviceByHumanId func(
|
||||
ctx context.Context,
|
||||
req *backendpb.CreateDeviceRequest,
|
||||
) (resp *backendpb.CreateDeviceResponse, err error)
|
||||
|
||||
OnGetDNSProfiles func(
|
||||
req *backendpb.DNSProfilesRequest,
|
||||
srv backendpb.DNSService_GetDNSProfilesServer,
|
||||
) (err error)
|
||||
|
||||
OnSaveDevicesBillingStat func(
|
||||
srv backendpb.DNSService_SaveDevicesBillingStatServer,
|
||||
) (err error)
|
||||
@ -20,6 +36,17 @@ type testDNSServiceServer struct {
|
||||
// type check
|
||||
var _ backendpb.DNSServiceServer = (*testDNSServiceServer)(nil)
|
||||
|
||||
// CreateDeviceByHumanId implements the [backendpb.DNSServiceServer] interface
|
||||
// for *testDNSServiceServer.
|
||||
//
|
||||
//lint:ignore ST1003 Keep in sync with the generated code.
|
||||
func (s *testDNSServiceServer) CreateDeviceByHumanId(
|
||||
ctx context.Context,
|
||||
req *backendpb.CreateDeviceRequest,
|
||||
) (resp *backendpb.CreateDeviceResponse, err error) {
|
||||
return s.OnCreateDeviceByHumanId(ctx, req)
|
||||
}
|
||||
|
||||
// GetDNSProfiles implements the [backendpb.DNSServiceServer] interface for
|
||||
// *testDNSServiceServer
|
||||
func (s *testDNSServiceServer) GetDNSProfiles(
|
||||
|
@ -23,21 +23,24 @@ type BillStatConfig struct {
|
||||
// Endpoint is the backend API URL. The scheme should be either "grpc" or
|
||||
// "grpcs".
|
||||
Endpoint *url.URL
|
||||
|
||||
// APIKey is the API key used for authentication, if any.
|
||||
APIKey string
|
||||
}
|
||||
|
||||
// NewBillStat creates a new billing statistics uploader. c must not be nil.
|
||||
func NewBillStat(c *BillStatConfig) (b *BillStat, err error) {
|
||||
b = &BillStat{
|
||||
errColl: c.ErrColl,
|
||||
}
|
||||
|
||||
b.client, err = newClient(c.Endpoint)
|
||||
client, err := newClient(c.Endpoint)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
return &BillStat{
|
||||
errColl: c.ErrColl,
|
||||
client: client,
|
||||
apiKey: c.APIKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BillStat is the implementation of the [billstat.Uploader] interface that
|
||||
@ -48,9 +51,8 @@ func NewBillStat(c *BillStatConfig) (b *BillStat, err error) {
|
||||
// backendpb.Client.
|
||||
type BillStat struct {
|
||||
errColl errcoll.Interface
|
||||
|
||||
// client is the current GRPC client.
|
||||
client DNSServiceClient
|
||||
client DNSServiceClient
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// type check
|
||||
@ -62,9 +64,10 @@ func (b *BillStat) Upload(ctx context.Context, records billstat.Records) (err er
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx = ctxWithAuthentication(ctx, b.apiKey)
|
||||
stream, err := b.client.SaveDevicesBillingStat(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening stream: %w", err)
|
||||
return fmt.Errorf("opening stream: %w", fixGRPCError(err))
|
||||
}
|
||||
|
||||
for deviceID, record := range records {
|
||||
@ -76,7 +79,7 @@ func (b *BillStat) Upload(ctx context.Context, records billstat.Records) (err er
|
||||
|
||||
sendErr := stream.Send(recordToProtobuf(record, deviceID))
|
||||
if sendErr != nil {
|
||||
return fmt.Errorf("uploading device %q record: %w", deviceID, sendErr)
|
||||
return fmt.Errorf("uploading device %q record: %w", deviceID, fixGRPCError(sendErr))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBillStat_Upload(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
wantDeviceID = "test"
|
||||
invalidDeviceID = "invalid"
|
||||
@ -43,6 +45,20 @@ func TestBillStat_Upload(t *testing.T) {
|
||||
}
|
||||
|
||||
srv := &testDNSServiceServer{
|
||||
OnCreateDeviceByHumanId: func(
|
||||
ctx context.Context,
|
||||
req *backendpb.CreateDeviceRequest,
|
||||
) (resp *backendpb.CreateDeviceResponse, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnGetDNSProfiles: func(
|
||||
req *backendpb.DNSProfilesRequest,
|
||||
srv backendpb.DNSService_GetDNSProfilesServer,
|
||||
) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnSaveDevicesBillingStat: func(
|
||||
srv backendpb.DNSService_SaveDevicesBillingStatServer,
|
||||
) (err error) {
|
||||
|
161
internal/backendpb/device.go
Normal file
161
internal/backendpb/device.go
Normal file
@ -0,0 +1,161 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// devicesToInternal is a helper that converts the devices from protobuf to
|
||||
// AdGuard DNS devices.
|
||||
func devicesToInternal(
|
||||
ctx context.Context,
|
||||
ds []*DeviceSettings,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (out []*agd.Device, ids []agd.DeviceID) {
|
||||
l := len(ds)
|
||||
if l == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out = make([]*agd.Device, 0, l)
|
||||
for _, d := range ds {
|
||||
dev, err := d.toInternal(bindSet)
|
||||
if err != nil {
|
||||
var id string
|
||||
if d != nil {
|
||||
id = d.Id
|
||||
}
|
||||
|
||||
reportf(ctx, errColl, "bad device settings for device with id %q: %w", id, err)
|
||||
metrics.DevicesInvalidTotal.Inc()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ids = append(ids, dev.ID)
|
||||
out = append(out, dev)
|
||||
}
|
||||
|
||||
return out, ids
|
||||
}
|
||||
|
||||
// toInternal is a helper that converts device settings from backend protobuf
|
||||
// response to AdGuard DNS device object.
|
||||
func (ds *DeviceSettings) toInternal(bindSet netutil.SubnetSet) (dev *agd.Device, err error) {
|
||||
if ds == nil {
|
||||
return nil, fmt.Errorf("device is nil")
|
||||
}
|
||||
|
||||
var linkedIP netip.Addr
|
||||
err = linkedIP.UnmarshalBinary(ds.LinkedIp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linked ip: %w", err)
|
||||
}
|
||||
|
||||
var dedicatedIPs []netip.Addr
|
||||
dedicatedIPs, err = ds.dedicatedIPsToInternal(bindSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dedicated ips: %w", err)
|
||||
}
|
||||
|
||||
auth, err := ds.Authentication.toInternal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: %w", err)
|
||||
}
|
||||
|
||||
id, err := agd.NewDeviceID(ds.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("device id: %w", err)
|
||||
}
|
||||
|
||||
name, err := agd.NewDeviceName(ds.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("device name: %w", err)
|
||||
}
|
||||
|
||||
var humanID agd.HumanIDLower
|
||||
if humanIDStr := ds.HumanIdLower; humanIDStr != "" {
|
||||
humanID, err = agd.NewHumanIDLower(humanIDStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lowercase human id: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &agd.Device{
|
||||
Auth: auth,
|
||||
ID: id,
|
||||
Name: name,
|
||||
HumanIDLower: humanID,
|
||||
LinkedIP: linkedIP,
|
||||
DedicatedIPs: dedicatedIPs,
|
||||
FilteringEnabled: ds.FilteringEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dedicatedIPsToInternal converts the dedicated IP data while also validating
|
||||
// it against the given bindSet.
|
||||
func (ds *DeviceSettings) dedicatedIPsToInternal(
|
||||
bindSet netutil.SubnetSet,
|
||||
) (dedicatedIPs []netip.Addr, err error) {
|
||||
dedicatedIPs, err = agdprotobuf.ByteSlicesToIPs(ds.DedicatedIps)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(d.kolyshev): Extract business logic validation.
|
||||
for i, addr := range dedicatedIPs {
|
||||
if !bindSet.Contains(addr) {
|
||||
return nil, fmt.Errorf("at index %d: %q is not in bind data", i, addr)
|
||||
}
|
||||
}
|
||||
|
||||
return dedicatedIPs, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf auth settings structure to an internal one.
|
||||
// If x is nil, toInternal returns non-nil settings with enabled field set to
|
||||
// false.
|
||||
func (x *AuthenticationSettings) toInternal() (s *agd.AuthSettings, err error) {
|
||||
if x == nil {
|
||||
return &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
ph, err := dohPasswordToInternal(x.DohPasswordHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("password hash: %w", err)
|
||||
}
|
||||
|
||||
return &agd.AuthSettings{
|
||||
PasswordHash: ph,
|
||||
Enabled: true,
|
||||
DoHAuthOnly: x.DohAuthOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dohPasswordToInternal converts a protobuf DoH password hash sum-type to an
|
||||
// internal one.
|
||||
func dohPasswordToInternal(
|
||||
pbp isAuthenticationSettings_DohPasswordHash,
|
||||
) (p agdpasswd.Authenticator, err error) {
|
||||
switch pbp := pbp.(type) {
|
||||
case nil:
|
||||
return agdpasswd.AllowAuthenticator{}, nil
|
||||
case *AuthenticationSettings_PasswordHashBcrypt:
|
||||
return agdpasswd.NewPasswordHashBcrypt(pbp.PasswordHashBcrypt), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bad pb auth doh password hash %T(%[1]v)", pbp)
|
||||
}
|
||||
}
|
2314
internal/backendpb/dns.pb.go
Normal file
2314
internal/backendpb/dns.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,13 +18,31 @@ service DNSService {
|
||||
|
||||
The trailers headers will include a "sync_time", given in milliseconds,
|
||||
that should be used for subsequent incremental DNS profile synchronization requests.
|
||||
*/
|
||||
|
||||
This method may return the following errors:
|
||||
- RateLimitedError: If too many "full sync" concurrent requests are made.
|
||||
- AuthenticationFailedError: If the authentication failed.
|
||||
*/
|
||||
rpc getDNSProfiles(DNSProfilesRequest) returns (stream DNSProfile);
|
||||
|
||||
/*
|
||||
Stores devices activity.
|
||||
*/
|
||||
|
||||
This method may return the following errors:
|
||||
- AuthenticationFailedError: If the authentication failed.
|
||||
*/
|
||||
rpc saveDevicesBillingStat(stream DeviceBillingStat) returns (google.protobuf.Empty);
|
||||
|
||||
/*
|
||||
Create device by "human_id".
|
||||
|
||||
This method may return the following errors:
|
||||
- RateLimitedError: If the request was made too frequently and the client must wait before retrying.
|
||||
- DeviceQuotaExceededError: If the client has exceeded its quota for creating devices.
|
||||
- BadRequestError: If the request is invalid: DNS server does not exist, creation of auto-devices is disabled or human_id validation failed.
|
||||
- AuthenticationFailedError: If the authentication failed.
|
||||
*/
|
||||
rpc createDeviceByHumanId(CreateDeviceRequest) returns (CreateDeviceResponse);
|
||||
}
|
||||
|
||||
message DNSProfilesRequest {
|
||||
@ -52,6 +70,7 @@ message DNSProfile {
|
||||
}
|
||||
bool ip_log_enabled = 17;
|
||||
AccessSettings access = 18;
|
||||
bool auto_devices_enabled = 19;
|
||||
}
|
||||
|
||||
message SafeBrowsingSettings {
|
||||
@ -67,6 +86,8 @@ message DeviceSettings {
|
||||
bytes linked_ip = 4;
|
||||
repeated bytes dedicated_ips = 5;
|
||||
AuthenticationSettings authentication = 6;
|
||||
// Value in lower case. Will be empty for "ordinary" devices and non-empty for "automatically" created devices.
|
||||
string human_id_lower = 7;
|
||||
}
|
||||
|
||||
message ParentalSettings {
|
||||
@ -144,3 +165,43 @@ message AuthenticationSettings {
|
||||
bytes password_hash_bcrypt = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum DeviceType {
|
||||
INVALID = 0;
|
||||
WINDOWS = 1;
|
||||
ANDROID = 2;
|
||||
MAC = 3;
|
||||
IOS = 4;
|
||||
LINUX = 5;
|
||||
ROUTER = 6;
|
||||
SMART_TV = 7;
|
||||
GAME_CONSOLE = 8;
|
||||
OTHER = 9;
|
||||
}
|
||||
|
||||
message CreateDeviceRequest {
|
||||
string dns_id = 1;
|
||||
string human_id = 2;
|
||||
DeviceType device_type = 3;
|
||||
}
|
||||
|
||||
message CreateDeviceResponse {
|
||||
DeviceSettings device = 1;
|
||||
}
|
||||
|
||||
message RateLimitedError {
|
||||
string message = 1;
|
||||
google.protobuf.Duration retry_delay = 2;
|
||||
}
|
||||
|
||||
message DeviceQuotaExceededError {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message BadRequestError {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message AuthenticationFailedError {
|
||||
string message = 1;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v4.25.3
|
||||
// source: backend.proto
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc v5.27.1
|
||||
// source: dns.proto
|
||||
|
||||
package backendpb
|
||||
|
||||
@ -16,12 +16,13 @@ import (
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
DNSService_GetDNSProfiles_FullMethodName = "/DNSService/getDNSProfiles"
|
||||
DNSService_SaveDevicesBillingStat_FullMethodName = "/DNSService/saveDevicesBillingStat"
|
||||
DNSService_CreateDeviceByHumanId_FullMethodName = "/DNSService/createDeviceByHumanId"
|
||||
)
|
||||
|
||||
// DNSServiceClient is the client API for DNSService service.
|
||||
@ -34,9 +35,24 @@ type DNSServiceClient interface {
|
||||
//
|
||||
// The trailers headers will include a "sync_time", given in milliseconds,
|
||||
// that should be used for subsequent incremental DNS profile synchronization requests.
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - RateLimitedError: If too many "full sync" concurrent requests are made.
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
GetDNSProfiles(ctx context.Context, in *DNSProfilesRequest, opts ...grpc.CallOption) (DNSService_GetDNSProfilesClient, error)
|
||||
// Stores devices activity.
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
SaveDevicesBillingStat(ctx context.Context, opts ...grpc.CallOption) (DNSService_SaveDevicesBillingStatClient, error)
|
||||
// Create device by "human_id".
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - RateLimitedError: If the request was made too frequently and the client must wait before retrying.
|
||||
// - DeviceQuotaExceededError: If the client has exceeded its quota for creating devices.
|
||||
// - BadRequestError: If the request is invalid: DNS server does not exist, creation of auto-devices is disabled or human_id validation failed.
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
CreateDeviceByHumanId(ctx context.Context, in *CreateDeviceRequest, opts ...grpc.CallOption) (*CreateDeviceResponse, error)
|
||||
}
|
||||
|
||||
type dNSServiceClient struct {
|
||||
@ -48,11 +64,12 @@ func NewDNSServiceClient(cc grpc.ClientConnInterface) DNSServiceClient {
|
||||
}
|
||||
|
||||
func (c *dNSServiceClient) GetDNSProfiles(ctx context.Context, in *DNSProfilesRequest, opts ...grpc.CallOption) (DNSService_GetDNSProfilesClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[0], DNSService_GetDNSProfiles_FullMethodName, opts...)
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[0], DNSService_GetDNSProfiles_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &dNSServiceGetDNSProfilesClient{stream}
|
||||
x := &dNSServiceGetDNSProfilesClient{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -80,11 +97,12 @@ func (x *dNSServiceGetDNSProfilesClient) Recv() (*DNSProfile, error) {
|
||||
}
|
||||
|
||||
func (c *dNSServiceClient) SaveDevicesBillingStat(ctx context.Context, opts ...grpc.CallOption) (DNSService_SaveDevicesBillingStatClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[1], DNSService_SaveDevicesBillingStat_FullMethodName, opts...)
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[1], DNSService_SaveDevicesBillingStat_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &dNSServiceSaveDevicesBillingStatClient{stream}
|
||||
x := &dNSServiceSaveDevicesBillingStatClient{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
@ -113,6 +131,16 @@ func (x *dNSServiceSaveDevicesBillingStatClient) CloseAndRecv() (*emptypb.Empty,
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *dNSServiceClient) CreateDeviceByHumanId(ctx context.Context, in *CreateDeviceRequest, opts ...grpc.CallOption) (*CreateDeviceResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(CreateDeviceResponse)
|
||||
err := c.cc.Invoke(ctx, DNSService_CreateDeviceByHumanId_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DNSServiceServer is the server API for DNSService service.
|
||||
// All implementations must embed UnimplementedDNSServiceServer
|
||||
// for forward compatibility
|
||||
@ -123,9 +151,24 @@ type DNSServiceServer interface {
|
||||
//
|
||||
// The trailers headers will include a "sync_time", given in milliseconds,
|
||||
// that should be used for subsequent incremental DNS profile synchronization requests.
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - RateLimitedError: If too many "full sync" concurrent requests are made.
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
GetDNSProfiles(*DNSProfilesRequest, DNSService_GetDNSProfilesServer) error
|
||||
// Stores devices activity.
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
SaveDevicesBillingStat(DNSService_SaveDevicesBillingStatServer) error
|
||||
// Create device by "human_id".
|
||||
//
|
||||
// This method may return the following errors:
|
||||
// - RateLimitedError: If the request was made too frequently and the client must wait before retrying.
|
||||
// - DeviceQuotaExceededError: If the client has exceeded its quota for creating devices.
|
||||
// - BadRequestError: If the request is invalid: DNS server does not exist, creation of auto-devices is disabled or human_id validation failed.
|
||||
// - AuthenticationFailedError: If the authentication failed.
|
||||
CreateDeviceByHumanId(context.Context, *CreateDeviceRequest) (*CreateDeviceResponse, error)
|
||||
mustEmbedUnimplementedDNSServiceServer()
|
||||
}
|
||||
|
||||
@ -139,6 +182,9 @@ func (UnimplementedDNSServiceServer) GetDNSProfiles(*DNSProfilesRequest, DNSServ
|
||||
func (UnimplementedDNSServiceServer) SaveDevicesBillingStat(DNSService_SaveDevicesBillingStatServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method SaveDevicesBillingStat not implemented")
|
||||
}
|
||||
func (UnimplementedDNSServiceServer) CreateDeviceByHumanId(context.Context, *CreateDeviceRequest) (*CreateDeviceResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateDeviceByHumanId not implemented")
|
||||
}
|
||||
func (UnimplementedDNSServiceServer) mustEmbedUnimplementedDNSServiceServer() {}
|
||||
|
||||
// UnsafeDNSServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
@ -157,7 +203,7 @@ func _DNSService_GetDNSProfiles_Handler(srv interface{}, stream grpc.ServerStrea
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(DNSServiceServer).GetDNSProfiles(m, &dNSServiceGetDNSProfilesServer{stream})
|
||||
return srv.(DNSServiceServer).GetDNSProfiles(m, &dNSServiceGetDNSProfilesServer{ServerStream: stream})
|
||||
}
|
||||
|
||||
type DNSService_GetDNSProfilesServer interface {
|
||||
@ -174,7 +220,7 @@ func (x *dNSServiceGetDNSProfilesServer) Send(m *DNSProfile) error {
|
||||
}
|
||||
|
||||
func _DNSService_SaveDevicesBillingStat_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(DNSServiceServer).SaveDevicesBillingStat(&dNSServiceSaveDevicesBillingStatServer{stream})
|
||||
return srv.(DNSServiceServer).SaveDevicesBillingStat(&dNSServiceSaveDevicesBillingStatServer{ServerStream: stream})
|
||||
}
|
||||
|
||||
type DNSService_SaveDevicesBillingStatServer interface {
|
||||
@ -199,13 +245,36 @@ func (x *dNSServiceSaveDevicesBillingStatServer) Recv() (*DeviceBillingStat, err
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _DNSService_CreateDeviceByHumanId_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateDeviceRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DNSServiceServer).CreateDeviceByHumanId(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DNSService_CreateDeviceByHumanId_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DNSServiceServer).CreateDeviceByHumanId(ctx, req.(*CreateDeviceRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// DNSService_ServiceDesc is the grpc.ServiceDesc for DNSService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var DNSService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "DNSService",
|
||||
HandlerType: (*DNSServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "createDeviceByHumanId",
|
||||
Handler: _DNSService_CreateDeviceByHumanId_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "getDNSProfiles",
|
||||
@ -218,5 +287,5 @@ var DNSService_ServiceDesc = grpc.ServiceDesc{
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "backend.proto",
|
||||
Metadata: "dns.proto",
|
||||
}
|
70
internal/backendpb/error.go
Normal file
70
internal/backendpb/error.go
Normal file
@ -0,0 +1,70 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// fixGRPCError converts a gRPC error into an application error, if necessary.
|
||||
// That includes gRPC deadlines, which do not match [context.DeadlineExceeded]
|
||||
// correctly.
|
||||
//
|
||||
// It also updates the backend gRPC metrics depending on the type, see
|
||||
// [metrics.IncGRPCErrorsCounter].
|
||||
func fixGRPCError(err error) (res error) {
|
||||
metricsType := metrics.GRPCErrorTypeOther
|
||||
defer func() { metrics.IncGRPCErrorsCounter(metricsType) }()
|
||||
|
||||
s, ok := status.FromError(err)
|
||||
if !ok {
|
||||
// Return the error as-is.
|
||||
return err
|
||||
}
|
||||
|
||||
// See https://github.com/grpc/grpc-go/issues/4822.
|
||||
//
|
||||
// TODO(d.kolyshev): Remove after the grpc-go issue is fixed.
|
||||
if s.Code() == codes.DeadlineExceeded {
|
||||
metricsType = metrics.GRPCErrorTypeTimeout
|
||||
|
||||
return fmt.Errorf("grpc: %w; original message: %s", context.DeadlineExceeded, err)
|
||||
}
|
||||
|
||||
for _, d := range s.Details() {
|
||||
switch structErr := d.(type) {
|
||||
case *AuthenticationFailedError:
|
||||
metricsType = metrics.GRPCErrorTypeAuthentication
|
||||
|
||||
return &profiledb.AuthenticationFailedError{
|
||||
Message: structErr.Message,
|
||||
}
|
||||
case *BadRequestError:
|
||||
metricsType = metrics.GRPCErrorTypeBadRequest
|
||||
|
||||
return &profiledb.BadRequestError{
|
||||
Message: structErr.Message,
|
||||
}
|
||||
case *DeviceQuotaExceededError:
|
||||
metricsType = metrics.GRPCErrorTypeDeviceQuota
|
||||
|
||||
return &profiledb.DeviceQuotaExceededError{
|
||||
Message: structErr.Message,
|
||||
}
|
||||
case *RateLimitedError:
|
||||
metricsType = metrics.GRPCErrorTypeRateLimit
|
||||
|
||||
return &profiledb.RateLimitedError{
|
||||
Message: structErr.Message,
|
||||
RetryDelay: structErr.RetryDelay.AsDuration(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the error as-is.
|
||||
return err
|
||||
}
|
341
internal/backendpb/profile.go
Normal file
341
internal/backendpb/profile.go
Normal file
@ -0,0 +1,341 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// toInternal converts the protobuf-encoded data into a profile structure and
|
||||
// its device structures.
|
||||
func (x *DNSProfile) toInternal(
|
||||
ctx context.Context,
|
||||
updTime time.Time,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (profile *agd.Profile, devices []*agd.Device, err error) {
|
||||
if x == nil {
|
||||
return nil, nil, fmt.Errorf("profile is nil")
|
||||
}
|
||||
|
||||
parental, err := x.Parental.toInternal(ctx, errColl)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parental: %w", err)
|
||||
}
|
||||
|
||||
m, err := blockingModeToInternal(x.BlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("blocking mode: %w", err)
|
||||
}
|
||||
|
||||
devices, deviceIds := devicesToInternal(ctx, x.Devices, bindSet, errColl)
|
||||
listsEnabled, listIDs := x.RuleLists.toInternal(ctx, errColl)
|
||||
|
||||
profID, err := agd.NewProfileID(x.DnsId)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("id: %w", err)
|
||||
}
|
||||
|
||||
var fltRespTTL time.Duration
|
||||
if respTTL := x.FilteredResponseTtl; respTTL != nil {
|
||||
fltRespTTL = respTTL.AsDuration()
|
||||
}
|
||||
|
||||
return &agd.Profile{
|
||||
Parental: parental,
|
||||
BlockingMode: m,
|
||||
ID: profID,
|
||||
UpdateTime: updTime,
|
||||
DeviceIDs: deviceIds,
|
||||
RuleListIDs: listIDs,
|
||||
CustomRules: rulesToInternal(ctx, x.CustomRules, errColl),
|
||||
FilteredResponseTTL: fltRespTTL,
|
||||
FilteringEnabled: x.FilteringEnabled,
|
||||
SafeBrowsing: x.SafeBrowsing.toInternal(),
|
||||
Access: x.Access.toInternal(ctx, errColl),
|
||||
RuleListsEnabled: listsEnabled,
|
||||
QueryLogEnabled: x.QueryLogEnabled,
|
||||
Deleted: x.Deleted,
|
||||
BlockPrivateRelay: x.BlockPrivateRelay,
|
||||
BlockFirefoxCanary: x.BlockFirefoxCanary,
|
||||
IPLogEnabled: x.IpLogEnabled,
|
||||
AutoDevicesEnabled: x.AutoDevicesEnabled,
|
||||
}, devices, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf parental-settings structure to an internal
|
||||
// one. If x is nil, toInternal returns nil.
|
||||
func (x *ParentalSettings) toInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
) (s *agd.ParentalProtectionSettings, err error) {
|
||||
if x == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
schedule, err := x.Schedule.toInternal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schedule: %w", err)
|
||||
}
|
||||
|
||||
return &agd.ParentalProtectionSettings{
|
||||
Schedule: schedule,
|
||||
BlockedServices: blockedSvcsToInternal(ctx, errColl, x.BlockedServices),
|
||||
Enabled: x.Enabled,
|
||||
BlockAdult: x.BlockAdult,
|
||||
GeneralSafeSearch: x.GeneralSafeSearch,
|
||||
YoutubeSafeSearch: x.YoutubeSafeSearch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toInternal converts protobuf safe-browsing settings to an internal structure.
|
||||
// If x is nil, toInternal returns nil.
|
||||
func (x *SafeBrowsingSettings) toInternal() (sb *agd.SafeBrowsingSettings) {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &agd.SafeBrowsingSettings{
|
||||
Enabled: x.Enabled,
|
||||
BlockDangerousDomains: x.BlockDangerousDomains,
|
||||
BlockNewlyRegisteredDomains: x.BlockNrd,
|
||||
}
|
||||
}
|
||||
|
||||
// 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, "bad cidr at index %d: %v", i, c.Address)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, netip.PrefixFrom(addr, int(c.Prefix)))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// asnToInternal is a helper that converts a slice of ASNs to the slice of
|
||||
// [geoip.ASN].
|
||||
func asnToInternal(asns []uint32) (out []geoip.ASN) {
|
||||
for _, asn := range asns {
|
||||
out = append(out, geoip.ASN(asn))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// blockedSvcsToInternal is a helper that converts the blocked service IDs from
|
||||
// the backend response to AdGuard DNS blocked service IDs.
|
||||
func blockedSvcsToInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
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, "blocked service at index %d: %w", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
svcs = append(svcs, id)
|
||||
}
|
||||
|
||||
return svcs
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf protection-schedule structure to an internal
|
||||
// one. If x is nil, toInternal returns nil.
|
||||
func (x *ScheduleSettings) toInternal() (sch *agd.ParentalProtectionSchedule, err error) {
|
||||
if x == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sch = &agd.ParentalProtectionSchedule{}
|
||||
|
||||
sch.TimeZone, err = agdtime.LoadLocation(x.Tmz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading timezone: %w", err)
|
||||
}
|
||||
|
||||
sch.Week = &agd.WeeklySchedule{}
|
||||
|
||||
w := x.WeeklyRange
|
||||
days := []*DayRange{w.Sun, w.Mon, w.Tue, w.Wed, w.Thu, w.Fri, w.Sat}
|
||||
for i, d := range days {
|
||||
if d == nil {
|
||||
sch.Week[i] = agd.ZeroLengthDayRange()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
sch.Week[i] = agd.DayRange{
|
||||
Start: uint16(d.Start.AsDuration().Minutes()),
|
||||
End: uint16(d.End.AsDuration().Minutes()),
|
||||
}
|
||||
}
|
||||
|
||||
for i, r := range sch.Week {
|
||||
err = r.Validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weekday %s: %w", time.Weekday(i), err)
|
||||
}
|
||||
}
|
||||
|
||||
return sch, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf custom blocking-mode to an internal one.
|
||||
// Assumes that at least one IP address is specified in the result blocking-mode
|
||||
// object.
|
||||
func (pbm *BlockingModeCustomIP) toInternal() (m dnsmsg.BlockingMode, err error) {
|
||||
custom := &dnsmsg.BlockingModeCustomIP{}
|
||||
|
||||
// TODO(a.garipov): Only one IPv4 address is supported on protobuf side.
|
||||
var ipv4Addr netip.Addr
|
||||
err = ipv4Addr.UnmarshalBinary(pbm.Ipv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad custom ipv4: %w", err)
|
||||
} else if ipv4Addr.IsValid() {
|
||||
custom.IPv4 = []netip.Addr{ipv4Addr}
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Only one IPv6 address is supported on protobuf side.
|
||||
var ipv6Addr netip.Addr
|
||||
err = ipv6Addr.UnmarshalBinary(pbm.Ipv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad custom ipv6: %w", err)
|
||||
} else if ipv6Addr.IsValid() {
|
||||
custom.IPv6 = []netip.Addr{ipv6Addr}
|
||||
}
|
||||
|
||||
if len(custom.IPv4)+len(custom.IPv6) == 0 {
|
||||
return nil, errors.Error("no valid custom ips found")
|
||||
}
|
||||
|
||||
return custom, nil
|
||||
}
|
||||
|
||||
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one. If pbm is nil, blockingModeToInternal returns a null-IP
|
||||
// blocking mode.
|
||||
func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_BlockingModeCustomIp:
|
||||
return pbm.BlockingModeCustomIp.toInternal()
|
||||
case *DNSProfile_BlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *DNSProfile_BlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_BlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// rulesToInternal is a helper that converts the filter rules from the backend
|
||||
// response to AdGuard DNS filtering rules.
|
||||
func rulesToInternal(
|
||||
ctx context.Context,
|
||||
respRules []string,
|
||||
errColl errcoll.Interface,
|
||||
) (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, "rule at index %d: %w", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, text)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
// toInternal is a helper that converts the filter lists from the backend
|
||||
// response to AdGuard DNS filter list ids. If x is nil, toInternal returns
|
||||
// false and nil.
|
||||
func (x *RuleListsSettings) toInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
) (enabled bool, filterLists []agd.FilterListID) {
|
||||
if x == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
l := len(x.Ids)
|
||||
if l == 0 {
|
||||
return x.Enabled, nil
|
||||
}
|
||||
|
||||
filterLists = make([]agd.FilterListID, 0, l)
|
||||
for i, f := range x.Ids {
|
||||
id, err := agd.NewFilterListID(f)
|
||||
if err != nil {
|
||||
reportf(ctx, errColl, "filter id: at index %d: %w", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
filterLists = append(filterLists, id)
|
||||
}
|
||||
|
||||
return x.Enabled, filterLists
|
||||
}
|
553
internal/backendpb/profile_internal_test.go
Normal file
553
internal/backendpb/profile_internal_test.go
Normal file
@ -0,0 +1,553 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func TestDNSProfile_ToInternal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
panic(err)
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, gotDevices, err := NewTestDNSProfile(t).toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, newProfile(t), got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
})
|
||||
|
||||
t.Run("success_bad_data", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var errCollErr error
|
||||
savingErrColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
errCollErr = err
|
||||
},
|
||||
}
|
||||
got, gotDevices, err := newDNSProfileWithBadData(t).toInternal(
|
||||
ctx,
|
||||
TestUpdTime,
|
||||
TestBind,
|
||||
savingErrColl,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
`backendpb: bad device settings for device with id "inv-d-ip":`+
|
||||
" dedicated ips: ip at index 0: unexpected slice size",
|
||||
errCollErr,
|
||||
)
|
||||
|
||||
assert.Equal(t, newProfile(t), got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
})
|
||||
|
||||
t.Run("invalid_device_ded_ip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var errCollErr error
|
||||
savingErrColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
errCollErr = err
|
||||
},
|
||||
}
|
||||
|
||||
bindSet := netip.MustParsePrefix("2.2.2.2/32")
|
||||
got, gotDevices, err := NewTestDNSProfile(t).toInternal(
|
||||
ctx,
|
||||
TestUpdTime,
|
||||
bindSet,
|
||||
savingErrColl,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
`backendpb: bad device settings for device with id "`+TestDeviceIDStr+`":`+
|
||||
" dedicated ips: at index 0: \"1.1.1.2\" is not in bind data",
|
||||
errCollErr,
|
||||
)
|
||||
|
||||
assert.NotEqual(t, newProfile(t), got)
|
||||
assert.NotEqual(t, newDevices(t), gotDevices)
|
||||
assert.Len(t, gotDevices, 3)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var emptyDNSProfile *DNSProfile
|
||||
_, _, err := emptyDNSProfile.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "profile is nil", err)
|
||||
})
|
||||
|
||||
t.Run("deleted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := &DNSProfile{
|
||||
DnsId: TestProfileIDStr,
|
||||
Deleted: true,
|
||||
}
|
||||
|
||||
got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
assert.Equal(t, got.ID, TestProfileID)
|
||||
assert.True(t, got.Deleted)
|
||||
assert.Empty(t, gotDevices)
|
||||
})
|
||||
|
||||
t.Run("inv_parental_sch_tmz", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Parental.Schedule.Tmz = "invalid"
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"parental: schedule: loading timezone: unknown time zone invalid",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("inv_parental_sch_day_range", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Parental.Schedule.WeeklyRange.Sun = &DayRange{
|
||||
Start: durationpb.New(1000000000000),
|
||||
End: nil,
|
||||
}
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"parental: schedule: weekday Sunday: bad day range: end 0 less than start 16",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("inv_blocking_mode_v4", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv4 = []byte("1")
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv4: unexpected slice size", err)
|
||||
})
|
||||
|
||||
t.Run("inv_blocking_mode_v6", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv6 = []byte("1")
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv6: unexpected slice size", err)
|
||||
})
|
||||
|
||||
t.Run("nil_ips_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv4 = nil
|
||||
bm.BlockingModeCustomIp.Ipv6 = nil
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: no valid custom ips found", err)
|
||||
})
|
||||
|
||||
t.Run("nil_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.BlockingMode = nil
|
||||
|
||||
got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, TestBind, 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) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Access = nil
|
||||
|
||||
got, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, 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) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Access = &AccessSettings{
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
got, _, err := dp.toInternal(ctx, TestUpdTime, TestBind, 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
|
||||
// devices data for tests.
|
||||
func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
|
||||
tb.Helper()
|
||||
|
||||
invalidDevices := []*DeviceSettings{{
|
||||
Id: "invalid-too-long-device-id",
|
||||
Name: "device_name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "dev-name",
|
||||
Name: "invalid-too-long-device-name-invalid-too-long-device-name-" +
|
||||
"invalid-too-long-device-name-invalid-too-long-device-name-" +
|
||||
"invalid-too-long-device-name-invalid-too-long-device-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "inv-ip",
|
||||
Name: "test-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: []byte("1"),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "inv-d-ip",
|
||||
Name: "test-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: [][]byte{[]byte("1")},
|
||||
}}
|
||||
|
||||
dp = NewTestDNSProfile(tb)
|
||||
dp.Devices = append(dp.Devices, invalidDevices...)
|
||||
|
||||
return dp
|
||||
}
|
||||
|
||||
// NewTestDNSProfile returns a new instance of *DNSProfile for tests.
|
||||
func NewTestDNSProfile(tb testing.TB) (dp *DNSProfile) {
|
||||
tb.Helper()
|
||||
|
||||
dayRange := &DayRange{
|
||||
Start: durationpb.New(0),
|
||||
End: durationpb.New(59 * time.Minute),
|
||||
}
|
||||
|
||||
devices := []*DeviceSettings{{
|
||||
Id: TestDeviceIDStr,
|
||||
Name: "1111aaaa-name",
|
||||
FilteringEnabled: false,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: [][]byte{ipToBytes(tb, netip.MustParseAddr("1.1.1.2"))},
|
||||
}, {
|
||||
Id: "2222bbbb",
|
||||
Name: "2222bbbb-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")),
|
||||
DedicatedIps: nil,
|
||||
Authentication: &AuthenticationSettings{
|
||||
DohAuthOnly: true,
|
||||
DohPasswordHash: &AuthenticationSettings_PasswordHashBcrypt{
|
||||
PasswordHashBcrypt: []byte("test-hash"),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Id: "3333cccc",
|
||||
Name: "3333cccc-name",
|
||||
FilteringEnabled: false,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("3.3.3.3")),
|
||||
DedicatedIps: nil,
|
||||
Authentication: &AuthenticationSettings{
|
||||
DohAuthOnly: false,
|
||||
DohPasswordHash: nil,
|
||||
},
|
||||
}, {
|
||||
Id: "4444dddd",
|
||||
Name: "My Auto-Device",
|
||||
HumanIdLower: "my-auto--device",
|
||||
FilteringEnabled: true,
|
||||
}}
|
||||
|
||||
return &DNSProfile{
|
||||
DnsId: TestProfileIDStr,
|
||||
FilteringEnabled: true,
|
||||
QueryLogEnabled: true,
|
||||
Deleted: false,
|
||||
SafeBrowsing: &SafeBrowsingSettings{
|
||||
Enabled: true,
|
||||
BlockDangerousDomains: true,
|
||||
BlockNrd: false,
|
||||
},
|
||||
Parental: &ParentalSettings{
|
||||
Enabled: false,
|
||||
BlockAdult: false,
|
||||
GeneralSafeSearch: false,
|
||||
YoutubeSafeSearch: false,
|
||||
BlockedServices: []string{"youtube"},
|
||||
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"},
|
||||
},
|
||||
Devices: devices,
|
||||
CustomRules: []string{"||example.org^"},
|
||||
FilteredResponseTtl: durationpb.New(10 * time.Second),
|
||||
BlockPrivateRelay: true,
|
||||
BlockFirefoxCanary: true,
|
||||
IpLogEnabled: true,
|
||||
AutoDevicesEnabled: true,
|
||||
BlockingMode: &DNSProfile_BlockingModeCustomIp{
|
||||
BlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")),
|
||||
Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")),
|
||||
},
|
||||
},
|
||||
Access: &AccessSettings{
|
||||
AllowlistCidr: []*CidrRange{{
|
||||
Address: netip.MustParseAddr("1.1.1.0").AsSlice(),
|
||||
Prefix: 24,
|
||||
}},
|
||||
BlocklistCidr: []*CidrRange{{
|
||||
Address: netip.MustParseAddr("2.2.2.0").AsSlice(),
|
||||
Prefix: 24,
|
||||
}},
|
||||
AllowlistAsn: []uint32{1},
|
||||
BlocklistAsn: []uint32{2},
|
||||
BlocklistDomainRules: []string{"block.test"},
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ipToBytes is a wrapper around netip.Addr.MarshalBinary.
|
||||
func ipToBytes(tb testing.TB, ip netip.Addr) (b []byte) {
|
||||
tb.Helper()
|
||||
|
||||
b, err := ip.MarshalBinary()
|
||||
require.NoError(tb, err)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// newProfile returns a new profile for tests.
|
||||
func newProfile(tb testing.TB) (p *agd.Profile) {
|
||||
tb.Helper()
|
||||
|
||||
wantLoc, err := agdtime.LoadLocation("GMT")
|
||||
require.NoError(tb, 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,
|
||||
}
|
||||
|
||||
wantBlockingMode := &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
|
||||
IPv6: []netip.Addr{netip.MustParseAddr("1234::cdef")},
|
||||
}
|
||||
|
||||
wantAccess := access.NewDefaultProfile(&access.ProfileConfig{
|
||||
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
|
||||
BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
|
||||
AllowedASN: []geoip.ASN{1},
|
||||
BlockedASN: []geoip.ASN{2},
|
||||
BlocklistDomainRules: []string{"block.test"},
|
||||
})
|
||||
|
||||
return &agd.Profile{
|
||||
Parental: wantParental,
|
||||
BlockingMode: wantBlockingMode,
|
||||
ID: TestProfileID,
|
||||
UpdateTime: TestUpdTime,
|
||||
DeviceIDs: []agd.DeviceID{
|
||||
TestDeviceID,
|
||||
"2222bbbb",
|
||||
"3333cccc",
|
||||
"4444dddd",
|
||||
},
|
||||
RuleListIDs: []agd.FilterListID{"1"},
|
||||
CustomRules: []agd.FilterRuleText{"||example.org^"},
|
||||
FilteredResponseTTL: 10 * time.Second,
|
||||
SafeBrowsing: wantSafeBrowsing,
|
||||
Access: wantAccess,
|
||||
RuleListsEnabled: true,
|
||||
FilteringEnabled: true,
|
||||
QueryLogEnabled: true,
|
||||
Deleted: false,
|
||||
BlockPrivateRelay: true,
|
||||
BlockFirefoxCanary: true,
|
||||
IPLogEnabled: true,
|
||||
AutoDevicesEnabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// newDevices returns a slice of test devices.
|
||||
func newDevices(t *testing.T) (d []*agd.Device) {
|
||||
t.Helper()
|
||||
|
||||
return []*agd.Device{{
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
DoHAuthOnly: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
},
|
||||
ID: TestDeviceID,
|
||||
LinkedIP: netip.MustParseAddr("1.1.1.1"),
|
||||
Name: "1111aaaa-name",
|
||||
DedicatedIPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")},
|
||||
FilteringEnabled: false,
|
||||
}, {
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: true,
|
||||
DoHAuthOnly: true,
|
||||
PasswordHash: agdpasswd.NewPasswordHashBcrypt([]byte("test-hash")),
|
||||
},
|
||||
ID: "2222bbbb",
|
||||
LinkedIP: netip.MustParseAddr("2.2.2.2"),
|
||||
Name: "2222bbbb-name",
|
||||
DedicatedIPs: nil,
|
||||
FilteringEnabled: true,
|
||||
}, {
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: true,
|
||||
DoHAuthOnly: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
},
|
||||
ID: "3333cccc",
|
||||
LinkedIP: netip.MustParseAddr("3.3.3.3"),
|
||||
Name: "3333cccc-name",
|
||||
DedicatedIPs: nil,
|
||||
FilteringEnabled: false,
|
||||
}, {
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
DoHAuthOnly: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
},
|
||||
ID: "4444dddd",
|
||||
Name: "My Auto-Device",
|
||||
HumanIDLower: "my-auto--device",
|
||||
FilteringEnabled: true,
|
||||
}}
|
||||
}
|
||||
|
||||
var (
|
||||
errSink error
|
||||
profSink *agd.Profile
|
||||
)
|
||||
|
||||
func BenchmarkDNSProfile_ToInternal(b *testing.B) {
|
||||
dp := NewTestDNSProfile(b)
|
||||
ctx := context.Background()
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
panic(err)
|
||||
},
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
profSink, _, errSink = dp.toInternal(ctx, TestUpdTime, TestBind, errColl)
|
||||
}
|
||||
|
||||
require.NotNil(b, profSink)
|
||||
require.NoError(b, errSink)
|
||||
|
||||
// Most recent result, on a ThinkPad X13:
|
||||
// goos: linux
|
||||
// goarch: amd64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkDNSProfile_ToInternal
|
||||
// BenchmarkDNSProfile_ToInternal-16 93568 19203 ns/op 1976 B/op 45 allocs/op
|
||||
}
|
@ -4,26 +4,16 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
@ -40,6 +30,9 @@ type ProfileStorageConfig struct {
|
||||
// Endpoint is the backend API URL. The scheme should be either "grpc" or
|
||||
// "grpcs".
|
||||
Endpoint *url.URL
|
||||
|
||||
// APIKey is the API key used for authentication, if any.
|
||||
APIKey string
|
||||
}
|
||||
|
||||
// ProfileStorage is the implementation of the [profiledb.Storage] interface
|
||||
@ -48,9 +41,8 @@ type ProfileStorageConfig struct {
|
||||
type ProfileStorage struct {
|
||||
bindSet netutil.SubnetSet
|
||||
errColl errcoll.Interface
|
||||
|
||||
// client is the current GRPC client.
|
||||
client DNSServiceClient
|
||||
client DNSServiceClient
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// NewProfileStorage returns a new [ProfileStorage] that retrieves information
|
||||
@ -64,26 +56,63 @@ func NewProfileStorage(c *ProfileStorageConfig) (s *ProfileStorage, err error) {
|
||||
|
||||
return &ProfileStorage{
|
||||
bindSet: c.BindSet,
|
||||
client: client,
|
||||
errColl: c.ErrColl,
|
||||
client: client,
|
||||
apiKey: c.APIKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ profiledb.Storage = (*ProfileStorage)(nil)
|
||||
|
||||
// CreateAutoDevice implements the [profile.Storage] interface for
|
||||
// *ProfileStorage.
|
||||
func (s *ProfileStorage) CreateAutoDevice(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageCreateAutoDeviceRequest,
|
||||
) (resp *profiledb.StorageCreateAutoDeviceResponse, err error) {
|
||||
defer func() {
|
||||
err = errors.Annotate(
|
||||
err,
|
||||
"creating auto device for profile %q and human id %q: %w",
|
||||
req.ProfileID,
|
||||
req.HumanID,
|
||||
)
|
||||
}()
|
||||
|
||||
ctx = ctxWithAuthentication(ctx, s.apiKey)
|
||||
backendResp, err := s.client.CreateDeviceByHumanId(ctx, &CreateDeviceRequest{
|
||||
DnsId: string(req.ProfileID),
|
||||
HumanId: string(req.HumanID),
|
||||
DeviceType: DeviceType(req.DeviceType),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calling backend: %w", fixGRPCError(err))
|
||||
}
|
||||
|
||||
d, err := backendResp.Device.toInternal(s.bindSet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting device: %w", err)
|
||||
}
|
||||
|
||||
return &profiledb.StorageCreateAutoDeviceResponse{
|
||||
Device: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Profiles implements the [profiledb.Storage] interface for *ProfileStorage.
|
||||
func (s *ProfileStorage) Profiles(
|
||||
ctx context.Context,
|
||||
req *profiledb.StorageRequest,
|
||||
) (resp *profiledb.StorageResponse, err error) {
|
||||
req *profiledb.StorageProfilesRequest,
|
||||
) (resp *profiledb.StorageProfilesResponse, err error) {
|
||||
ctx = ctxWithAuthentication(ctx, s.apiKey)
|
||||
stream, err := s.client.GetDNSProfiles(ctx, toProtobuf(req))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading profiles: %w", fixGRPCError(err))
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, stream.CloseSend()) }()
|
||||
|
||||
resp = &profiledb.StorageResponse{
|
||||
resp = &profiledb.StorageProfilesResponse{
|
||||
Profiles: []*agd.Profile{},
|
||||
Devices: []*agd.Device{},
|
||||
}
|
||||
@ -92,7 +121,7 @@ func (s *ProfileStorage) Profiles(
|
||||
isFullSync: req.SyncTime.IsZero(),
|
||||
}
|
||||
|
||||
for {
|
||||
for n := 1; ; n++ {
|
||||
stats.startRecv()
|
||||
profile, profErr := stream.Recv()
|
||||
if profErr != nil {
|
||||
@ -100,7 +129,7 @@ func (s *ProfileStorage) Profiles(
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("receiving profile: %w", fixGRPCError(profErr))
|
||||
return nil, fmt.Errorf("receiving profile #%d: %w", n, fixGRPCError(profErr))
|
||||
}
|
||||
stats.endRecv()
|
||||
|
||||
@ -128,465 +157,8 @@ func (s *ProfileStorage) Profiles(
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// fixGRPCError wraps GRPC error if needed. As the GRPC deadline error is not
|
||||
// correctly wrapped, this helper detects it by the status code and replaces it
|
||||
// with a simple DeadlineExceeded error.
|
||||
//
|
||||
// See https://github.com/grpc/grpc-go/issues/4822.
|
||||
//
|
||||
// TODO(d.kolyshev): Remove after the grpc-go issue is fixed.
|
||||
func fixGRPCError(err error) (wErr error) {
|
||||
st, ok := status.FromError(err)
|
||||
if ok && st.Code() == codes.DeadlineExceeded {
|
||||
err = fmt.Errorf("grpc: %w", context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// toInternal converts the protobuf-encoded data into a profile structure.
|
||||
func (x *DNSProfile) toInternal(
|
||||
ctx context.Context,
|
||||
updTime time.Time,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (profile *agd.Profile, devices []*agd.Device, err error) {
|
||||
if x == nil {
|
||||
return nil, nil, fmt.Errorf("profile is nil")
|
||||
}
|
||||
|
||||
parental, err := x.Parental.toInternal(ctx, errColl)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parental: %w", err)
|
||||
}
|
||||
|
||||
m, err := blockingModeToInternal(x.BlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("blocking mode: %w", err)
|
||||
}
|
||||
|
||||
devices, deviceIds := devicesToInternal(ctx, x.Devices, bindSet, errColl)
|
||||
listsEnabled, listIDs := x.RuleLists.toInternal(ctx, errColl)
|
||||
|
||||
profID, err := agd.NewProfileID(x.DnsId)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("id: %w", err)
|
||||
}
|
||||
|
||||
var fltRespTTL time.Duration
|
||||
if respTTL := x.FilteredResponseTtl; respTTL != nil {
|
||||
fltRespTTL = respTTL.AsDuration()
|
||||
}
|
||||
|
||||
return &agd.Profile{
|
||||
Parental: parental,
|
||||
BlockingMode: m,
|
||||
ID: profID,
|
||||
UpdateTime: updTime,
|
||||
DeviceIDs: deviceIds,
|
||||
RuleListIDs: listIDs,
|
||||
CustomRules: rulesToInternal(ctx, x.CustomRules, errColl),
|
||||
FilteredResponseTTL: fltRespTTL,
|
||||
FilteringEnabled: x.FilteringEnabled,
|
||||
SafeBrowsing: x.SafeBrowsing.toInternal(),
|
||||
Access: x.Access.toInternal(ctx, errColl),
|
||||
RuleListsEnabled: listsEnabled,
|
||||
QueryLogEnabled: x.QueryLogEnabled,
|
||||
Deleted: x.Deleted,
|
||||
BlockPrivateRelay: x.BlockPrivateRelay,
|
||||
BlockFirefoxCanary: x.BlockFirefoxCanary,
|
||||
IPLogEnabled: x.IpLogEnabled,
|
||||
}, devices, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf parental-settings structure to an internal
|
||||
// one. If x is nil, toInternal returns nil.
|
||||
func (x *ParentalSettings) toInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
) (s *agd.ParentalProtectionSettings, err error) {
|
||||
if x == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
schedule, err := x.Schedule.toInternal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schedule: %w", err)
|
||||
}
|
||||
|
||||
return &agd.ParentalProtectionSettings{
|
||||
Schedule: schedule,
|
||||
BlockedServices: blockedSvcsToInternal(ctx, errColl, x.BlockedServices),
|
||||
Enabled: x.Enabled,
|
||||
BlockAdult: x.BlockAdult,
|
||||
GeneralSafeSearch: x.GeneralSafeSearch,
|
||||
YoutubeSafeSearch: x.YoutubeSafeSearch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toInternal converts protobuf safe-browsing settings to an internal structure.
|
||||
// If x is nil, toInternal returns nil.
|
||||
func (x *SafeBrowsingSettings) toInternal() (sb *agd.SafeBrowsingSettings) {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &agd.SafeBrowsingSettings{
|
||||
Enabled: x.Enabled,
|
||||
BlockDangerousDomains: x.BlockDangerousDomains,
|
||||
BlockNewlyRegisteredDomains: x.BlockNrd,
|
||||
}
|
||||
}
|
||||
|
||||
// toInternal converts protobuf access settings to an internal structure. If x
|
||||
// is nil, toInternal returns [access.EmptyProfile].
|
||||
func (x *AccessSettings) toInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
) (a access.Profile) {
|
||||
if x == nil || !x.Enabled {
|
||||
return access.EmptyProfile{}
|
||||
}
|
||||
|
||||
return access.NewDefaultProfile(&access.ProfileConfig{
|
||||
AllowedNets: cidrRangeToInternal(ctx, errColl, x.AllowlistCidr),
|
||||
BlockedNets: cidrRangeToInternal(ctx, errColl, x.BlocklistCidr),
|
||||
AllowedASN: asnToInternal(x.AllowlistAsn),
|
||||
BlockedASN: asnToInternal(x.BlocklistAsn),
|
||||
BlocklistDomainRules: x.BlocklistDomainRules,
|
||||
})
|
||||
}
|
||||
|
||||
// cidrRangeToInternal is a helper that converts a slice of CidrRange to the
|
||||
// slice of [netip.Prefix].
|
||||
func cidrRangeToInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
cidrs []*CidrRange,
|
||||
) (out []netip.Prefix) {
|
||||
for i, c := range cidrs {
|
||||
addr, ok := netip.AddrFromSlice(c.Address)
|
||||
if !ok {
|
||||
reportf(ctx, errColl, "invalid cidr at index %d: %w", i)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, netip.PrefixFrom(addr, int(c.Prefix)))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// asnToInternal is a helper that converts a slice of ASNs to the slice of
|
||||
// [geoip.ASN].
|
||||
func asnToInternal(asns []uint32) (out []geoip.ASN) {
|
||||
for _, asn := range asns {
|
||||
out = append(out, geoip.ASN(asn))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// blockedSvcsToInternal is a helper that converts the blocked service IDs from
|
||||
// the backend response to AdGuard DNS blocked service IDs.
|
||||
func blockedSvcsToInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
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, "blocked service at index %d: %w", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
svcs = append(svcs, id)
|
||||
}
|
||||
|
||||
return svcs
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf protection-schedule structure to an internal
|
||||
// one. If x is nil, toInternal returns nil.
|
||||
func (x *ScheduleSettings) toInternal() (sch *agd.ParentalProtectionSchedule, err error) {
|
||||
if x == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sch = &agd.ParentalProtectionSchedule{}
|
||||
|
||||
sch.TimeZone, err = agdtime.LoadLocation(x.Tmz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading timezone: %w", err)
|
||||
}
|
||||
|
||||
sch.Week = &agd.WeeklySchedule{}
|
||||
|
||||
w := x.WeeklyRange
|
||||
days := []*DayRange{w.Sun, w.Mon, w.Tue, w.Wed, w.Thu, w.Fri, w.Sat}
|
||||
for i, d := range days {
|
||||
if d == nil {
|
||||
sch.Week[i] = agd.ZeroLengthDayRange()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
sch.Week[i] = agd.DayRange{
|
||||
Start: uint16(d.Start.AsDuration().Minutes()),
|
||||
End: uint16(d.End.AsDuration().Minutes()),
|
||||
}
|
||||
}
|
||||
|
||||
for i, r := range sch.Week {
|
||||
err = r.Validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weekday %s: %w", time.Weekday(i), err)
|
||||
}
|
||||
}
|
||||
|
||||
return sch, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf custom blocking-mode to an internal one.
|
||||
// Assumes that at least one IP address is specified in the result blocking-mode
|
||||
// object.
|
||||
func (pbm *BlockingModeCustomIP) toInternal() (m dnsmsg.BlockingMode, err error) {
|
||||
custom := &dnsmsg.BlockingModeCustomIP{}
|
||||
|
||||
// TODO(a.garipov): Only one IPv4 address is supported on protobuf side.
|
||||
var ipv4Addr netip.Addr
|
||||
err = ipv4Addr.UnmarshalBinary(pbm.Ipv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad custom ipv4: %w", err)
|
||||
} else if ipv4Addr.IsValid() {
|
||||
custom.IPv4 = []netip.Addr{ipv4Addr}
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Only one IPv6 address is supported on protobuf side.
|
||||
var ipv6Addr netip.Addr
|
||||
err = ipv6Addr.UnmarshalBinary(pbm.Ipv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad custom ipv6: %w", err)
|
||||
} else if ipv6Addr.IsValid() {
|
||||
custom.IPv6 = []netip.Addr{ipv6Addr}
|
||||
}
|
||||
|
||||
if len(custom.IPv4)+len(custom.IPv6) == 0 {
|
||||
return nil, errors.Error("no valid custom ips found")
|
||||
}
|
||||
|
||||
return custom, nil
|
||||
}
|
||||
|
||||
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one. If pbm is nil, blockingModeToInternal returns a null-IP
|
||||
// blocking mode.
|
||||
func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_BlockingModeCustomIp:
|
||||
return pbm.BlockingModeCustomIp.toInternal()
|
||||
case *DNSProfile_BlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *DNSProfile_BlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_BlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// devicesToInternal is a helper that converts the devices from protobuf to
|
||||
// AdGuard DNS devices.
|
||||
func devicesToInternal(
|
||||
ctx context.Context,
|
||||
ds []*DeviceSettings,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (out []*agd.Device, ids []agd.DeviceID) {
|
||||
l := len(ds)
|
||||
if l == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out = make([]*agd.Device, 0, l)
|
||||
for _, d := range ds {
|
||||
dev, err := d.toInternal(bindSet)
|
||||
if err != nil {
|
||||
reportf(ctx, errColl, "invalid device settings: %w", err)
|
||||
metrics.DevicesInvalidTotal.Inc()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
ids = append(ids, dev.ID)
|
||||
out = append(out, dev)
|
||||
}
|
||||
|
||||
return out, ids
|
||||
}
|
||||
|
||||
// toInternal is a helper that converts device settings from backend protobuf
|
||||
// response to AdGuard DNS device object.
|
||||
func (ds *DeviceSettings) toInternal(bindSet netutil.SubnetSet) (dev *agd.Device, err error) {
|
||||
if ds == nil {
|
||||
return nil, fmt.Errorf("device is nil")
|
||||
}
|
||||
|
||||
var linkedIP netip.Addr
|
||||
err = linkedIP.UnmarshalBinary(ds.LinkedIp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linked ip: %w", err)
|
||||
}
|
||||
|
||||
var dedicatedIPs []netip.Addr
|
||||
dedicatedIPs, err = agdprotobuf.ByteSlicesToIPs(ds.DedicatedIps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dedicated ips: %w", err)
|
||||
}
|
||||
|
||||
// TODO(d.kolyshev): Extract business logic validation.
|
||||
for _, addr := range dedicatedIPs {
|
||||
if !bindSet.Contains(addr) {
|
||||
return nil, fmt.Errorf("dedicated ip %q is not in bind data", addr)
|
||||
}
|
||||
}
|
||||
|
||||
auth, err := ds.Authentication.toInternal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: %s: %w", ds.Id, err)
|
||||
}
|
||||
|
||||
id, err := agd.NewDeviceID(ds.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("device id: %s: %w", ds.Id, err)
|
||||
}
|
||||
|
||||
name, err := agd.NewDeviceName(ds.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("device name: %s: %w", ds.Name, err)
|
||||
}
|
||||
|
||||
return &agd.Device{
|
||||
Auth: auth,
|
||||
ID: id,
|
||||
Name: name,
|
||||
LinkedIP: linkedIP,
|
||||
DedicatedIPs: dedicatedIPs,
|
||||
FilteringEnabled: ds.FilteringEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toInternal converts a protobuf auth settings structure to an internal one.
|
||||
// If x is nil, toInternal returns non-nil settings with enabled field set to
|
||||
// false.
|
||||
func (x *AuthenticationSettings) toInternal() (s *agd.AuthSettings, err error) {
|
||||
if x == nil {
|
||||
return &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
ph, err := dohPasswordToInternal(x.DohPasswordHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("password hash: %w", err)
|
||||
}
|
||||
|
||||
return &agd.AuthSettings{
|
||||
PasswordHash: ph,
|
||||
Enabled: true,
|
||||
DoHAuthOnly: x.DohAuthOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dohPasswordToInternal converts a protobuf DoH password hash sum-type to an
|
||||
// internal one.
|
||||
func dohPasswordToInternal(
|
||||
pbp isAuthenticationSettings_DohPasswordHash,
|
||||
) (p agdpasswd.Authenticator, err error) {
|
||||
switch pbp := pbp.(type) {
|
||||
case nil:
|
||||
return agdpasswd.AllowAuthenticator{}, nil
|
||||
case *AuthenticationSettings_PasswordHashBcrypt:
|
||||
return agdpasswd.NewPasswordHashBcrypt(pbp.PasswordHashBcrypt), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bad pb auth doh password hash %T(%[1]v)", pbp)
|
||||
}
|
||||
}
|
||||
|
||||
// rulesToInternal is a helper that converts the filter rules from the backend
|
||||
// response to AdGuard DNS filtering rules.
|
||||
func rulesToInternal(
|
||||
ctx context.Context,
|
||||
respRules []string,
|
||||
errColl errcoll.Interface,
|
||||
) (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, "rule at index %d: %w", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, text)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
// toInternal is a helper that converts the filter lists from the backend
|
||||
// response to AdGuard DNS filter list ids. If x is nil, toInternal returns
|
||||
// false and nil.
|
||||
func (x *RuleListsSettings) toInternal(
|
||||
ctx context.Context,
|
||||
errColl errcoll.Interface,
|
||||
) (enabled bool, filterLists []agd.FilterListID) {
|
||||
if x == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
l := len(x.Ids)
|
||||
if l == 0 {
|
||||
return x.Enabled, nil
|
||||
}
|
||||
|
||||
filterLists = make([]agd.FilterListID, 0, l)
|
||||
for _, f := range x.Ids {
|
||||
id, err := agd.NewFilterListID(f)
|
||||
if err != nil {
|
||||
reportf(ctx, errColl, "invalid filter id: %s: %w", f, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
filterLists = append(filterLists, id)
|
||||
}
|
||||
|
||||
return x.Enabled, filterLists
|
||||
}
|
||||
|
||||
// toProtobuf converts a storage request structure into the protobuf structure.
|
||||
func toProtobuf(r *profiledb.StorageRequest) (req *DNSProfilesRequest) {
|
||||
func toProtobuf(r *profiledb.StorageProfilesRequest) (req *DNSProfilesRequest) {
|
||||
return &DNSProfilesRequest{
|
||||
SyncTime: timestamppb.New(r.SyncTime),
|
||||
}
|
||||
@ -603,7 +175,7 @@ func syncTimeFromTrailer(trailer metadata.MD) (syncTime time.Time, err error) {
|
||||
|
||||
syncTimeMs, err := strconv.ParseInt(st[0], 10, 64)
|
||||
if err != nil {
|
||||
return syncTime, fmt.Errorf("invalid value: %w", err)
|
||||
return syncTime, fmt.Errorf("bad value: %w", err)
|
||||
}
|
||||
|
||||
return time.Unix(0, syncTimeMs*time.Millisecond.Nanoseconds()), nil
|
||||
|
@ -1,478 +1,18 @@
|
||||
package backendpb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testProfileID is the common profile ID for tests.
|
||||
const testProfileID agd.ProfileID = "prof1234"
|
||||
|
||||
// TestUpdTime is the common update time for tests.
|
||||
var TestUpdTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// testBind includes any IPv4 address.
|
||||
var testBind = netip.MustParsePrefix("0.0.0.0/0")
|
||||
|
||||
func TestDNSProfile_ToInternal(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
panic(err)
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
got, gotDevices, err := NewTestDNSProfile(t).toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, newProfile(t), got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
})
|
||||
|
||||
t.Run("success_bad_data", func(t *testing.T) {
|
||||
var errCollErr error
|
||||
savingErrColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
errCollErr = err
|
||||
},
|
||||
}
|
||||
got, gotDevices, err := newDNSProfileWithBadData(t).toInternal(
|
||||
ctx,
|
||||
TestUpdTime,
|
||||
testBind,
|
||||
savingErrColl,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
testutil.AssertErrorMsg(t, "backendpb: invalid device settings:"+
|
||||
" dedicated ips: ip at index 0: unexpected slice size", errCollErr)
|
||||
|
||||
assert.Equal(t, newProfile(t), got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
})
|
||||
|
||||
t.Run("invalid_device_ded_ip", func(t *testing.T) {
|
||||
var errCollErr error
|
||||
savingErrColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
errCollErr = err
|
||||
},
|
||||
}
|
||||
|
||||
bindSet := netip.MustParsePrefix("2.2.2.2/32")
|
||||
got, gotDevices, err := NewTestDNSProfile(t).toInternal(
|
||||
ctx,
|
||||
TestUpdTime,
|
||||
bindSet,
|
||||
savingErrColl,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
testutil.AssertErrorMsg(t, "backendpb: invalid device settings:"+
|
||||
" dedicated ip \"1.1.1.2\" is not in bind data", errCollErr)
|
||||
|
||||
assert.NotEqual(t, newProfile(t), got)
|
||||
assert.NotEqual(t, newDevices(t), gotDevices)
|
||||
assert.Len(t, gotDevices, 2)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
var emptyDNSProfile *DNSProfile
|
||||
_, _, err := emptyDNSProfile.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "profile is nil", err)
|
||||
})
|
||||
|
||||
t.Run("deleted", func(t *testing.T) {
|
||||
dp := &DNSProfile{
|
||||
DnsId: string(testProfileID),
|
||||
Deleted: true,
|
||||
}
|
||||
|
||||
got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
assert.Equal(t, got.ID, testProfileID)
|
||||
assert.True(t, got.Deleted)
|
||||
assert.Empty(t, gotDevices)
|
||||
})
|
||||
|
||||
t.Run("inv_parental_sch_tmz", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Parental.Schedule.Tmz = "invalid"
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "parental: schedule: loading timezone: unknown time zone invalid", err)
|
||||
})
|
||||
|
||||
t.Run("inv_parental_sch_day_range", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.Parental.Schedule.WeeklyRange.Sun = &DayRange{
|
||||
Start: durationpb.New(1000000000000),
|
||||
End: nil,
|
||||
}
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "parental: schedule: weekday Sunday: bad day range: end 0 less than start 16", err)
|
||||
})
|
||||
|
||||
t.Run("inv_blocking_mode_v4", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv4 = []byte("1")
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv4: unexpected slice size", err)
|
||||
})
|
||||
|
||||
t.Run("inv_blocking_mode_v6", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv6 = []byte("1")
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv6: unexpected slice size", err)
|
||||
})
|
||||
|
||||
t.Run("nil_ips_blocking_mode", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm.BlockingModeCustomIp.Ipv4 = nil
|
||||
bm.BlockingModeCustomIp.Ipv6 = nil
|
||||
|
||||
_, _, err := dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
testutil.AssertErrorMsg(t, "blocking mode: no valid custom ips found", err)
|
||||
})
|
||||
|
||||
t.Run("nil_blocking_mode", func(t *testing.T) {
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.BlockingMode = nil
|
||||
|
||||
got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, testBind, 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, testBind, 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, testBind, 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
|
||||
// devices data for tests.
|
||||
func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) {
|
||||
tb.Helper()
|
||||
|
||||
invalidDevices := []*DeviceSettings{{
|
||||
Id: "invalid-too-long-device-id",
|
||||
Name: "device_name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "dev-name",
|
||||
Name: "invalid-too-long-device-name-invalid-too-long-device-name-" +
|
||||
"invalid-too-long-device-name-invalid-too-long-device-name-" +
|
||||
"invalid-too-long-device-name-invalid-too-long-device-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "inv-ip",
|
||||
Name: "test-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: []byte("1"),
|
||||
DedicatedIps: nil,
|
||||
}, {
|
||||
Id: "inv-d-ip",
|
||||
Name: "test-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: [][]byte{[]byte("1")},
|
||||
}}
|
||||
|
||||
dp = NewTestDNSProfile(tb)
|
||||
dp.Devices = append(dp.Devices, invalidDevices...)
|
||||
|
||||
return dp
|
||||
}
|
||||
|
||||
// NewTestDNSProfile returns a new instance of *DNSProfile for tests.
|
||||
func NewTestDNSProfile(tb testing.TB) (dp *DNSProfile) {
|
||||
tb.Helper()
|
||||
|
||||
dayRange := &DayRange{
|
||||
Start: durationpb.New(0),
|
||||
End: durationpb.New(59 * time.Minute),
|
||||
}
|
||||
|
||||
devices := []*DeviceSettings{{
|
||||
Id: "1111aaaa",
|
||||
Name: "1111aaaa-name",
|
||||
FilteringEnabled: false,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
DedicatedIps: [][]byte{ipToBytes(tb, netip.MustParseAddr("1.1.1.2"))},
|
||||
}, {
|
||||
Id: "2222bbbb",
|
||||
Name: "2222bbbb-name",
|
||||
FilteringEnabled: true,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")),
|
||||
DedicatedIps: nil,
|
||||
Authentication: &AuthenticationSettings{
|
||||
DohAuthOnly: true,
|
||||
DohPasswordHash: &AuthenticationSettings_PasswordHashBcrypt{
|
||||
PasswordHashBcrypt: []byte("test-hash"),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Id: "3333cccc",
|
||||
Name: "3333cccc-name",
|
||||
FilteringEnabled: false,
|
||||
LinkedIp: ipToBytes(tb, netip.MustParseAddr("3.3.3.3")),
|
||||
DedicatedIps: nil,
|
||||
Authentication: &AuthenticationSettings{
|
||||
DohAuthOnly: false,
|
||||
DohPasswordHash: nil,
|
||||
},
|
||||
}}
|
||||
|
||||
return &DNSProfile{
|
||||
DnsId: string(testProfileID),
|
||||
FilteringEnabled: true,
|
||||
QueryLogEnabled: true,
|
||||
Deleted: false,
|
||||
SafeBrowsing: &SafeBrowsingSettings{
|
||||
Enabled: true,
|
||||
BlockDangerousDomains: true,
|
||||
BlockNrd: false,
|
||||
},
|
||||
Parental: &ParentalSettings{
|
||||
Enabled: false,
|
||||
BlockAdult: false,
|
||||
GeneralSafeSearch: false,
|
||||
YoutubeSafeSearch: false,
|
||||
BlockedServices: []string{"youtube"},
|
||||
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"},
|
||||
},
|
||||
Devices: devices,
|
||||
CustomRules: []string{"||example.org^"},
|
||||
FilteredResponseTtl: durationpb.New(10 * time.Second),
|
||||
BlockPrivateRelay: true,
|
||||
BlockFirefoxCanary: true,
|
||||
IpLogEnabled: true,
|
||||
BlockingMode: &DNSProfile_BlockingModeCustomIp{
|
||||
BlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")),
|
||||
Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")),
|
||||
},
|
||||
},
|
||||
Access: &AccessSettings{
|
||||
AllowlistCidr: []*CidrRange{{
|
||||
Address: netip.MustParseAddr("1.1.1.0").AsSlice(),
|
||||
Prefix: 24,
|
||||
}},
|
||||
BlocklistCidr: []*CidrRange{{
|
||||
Address: netip.MustParseAddr("2.2.2.0").AsSlice(),
|
||||
Prefix: 24,
|
||||
}},
|
||||
AllowlistAsn: []uint32{1},
|
||||
BlocklistAsn: []uint32{2},
|
||||
BlocklistDomainRules: []string{"block.test"},
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newProfile returns a new profile for tests.
|
||||
func newProfile(tb testing.TB) (p *agd.Profile) {
|
||||
tb.Helper()
|
||||
|
||||
wantLoc, err := agdtime.LoadLocation("GMT")
|
||||
require.NoError(tb, 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,
|
||||
}
|
||||
|
||||
wantBlockingMode := &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
|
||||
IPv6: []netip.Addr{netip.MustParseAddr("1234::cdef")},
|
||||
}
|
||||
|
||||
wantAccess := access.NewDefaultProfile(&access.ProfileConfig{
|
||||
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
|
||||
BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
|
||||
AllowedASN: []geoip.ASN{1},
|
||||
BlockedASN: []geoip.ASN{2},
|
||||
BlocklistDomainRules: []string{"block.test"},
|
||||
})
|
||||
|
||||
return &agd.Profile{
|
||||
Parental: wantParental,
|
||||
BlockingMode: wantBlockingMode,
|
||||
ID: testProfileID,
|
||||
UpdateTime: TestUpdTime,
|
||||
DeviceIDs: []agd.DeviceID{
|
||||
"1111aaaa",
|
||||
"2222bbbb",
|
||||
"3333cccc",
|
||||
},
|
||||
RuleListIDs: []agd.FilterListID{"1"},
|
||||
CustomRules: []agd.FilterRuleText{"||example.org^"},
|
||||
FilteredResponseTTL: 10 * time.Second,
|
||||
SafeBrowsing: wantSafeBrowsing,
|
||||
Access: wantAccess,
|
||||
RuleListsEnabled: true,
|
||||
FilteringEnabled: true,
|
||||
QueryLogEnabled: true,
|
||||
Deleted: false,
|
||||
BlockPrivateRelay: true,
|
||||
BlockFirefoxCanary: true,
|
||||
IPLogEnabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// newDevices returns a slice of test devices.
|
||||
func newDevices(t *testing.T) (d []*agd.Device) {
|
||||
t.Helper()
|
||||
|
||||
return []*agd.Device{{
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
DoHAuthOnly: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
},
|
||||
ID: "1111aaaa",
|
||||
LinkedIP: netip.MustParseAddr("1.1.1.1"),
|
||||
Name: "1111aaaa-name",
|
||||
DedicatedIPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")},
|
||||
FilteringEnabled: false,
|
||||
}, {
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: true,
|
||||
DoHAuthOnly: true,
|
||||
PasswordHash: agdpasswd.NewPasswordHashBcrypt([]byte("test-hash")),
|
||||
},
|
||||
ID: "2222bbbb",
|
||||
LinkedIP: netip.MustParseAddr("2.2.2.2"),
|
||||
Name: "2222bbbb-name",
|
||||
DedicatedIPs: nil,
|
||||
FilteringEnabled: true,
|
||||
}, {
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: true,
|
||||
DoHAuthOnly: false,
|
||||
PasswordHash: agdpasswd.AllowAuthenticator{},
|
||||
},
|
||||
ID: "3333cccc",
|
||||
LinkedIP: netip.MustParseAddr("3.3.3.3"),
|
||||
Name: "3333cccc-name",
|
||||
DedicatedIPs: nil,
|
||||
FilteringEnabled: false,
|
||||
}}
|
||||
}
|
||||
|
||||
// ipToBytes is a wrapper around netip.Addr.MarshalBinary.
|
||||
func ipToBytes(tb testing.TB, ip netip.Addr) (b []byte) {
|
||||
tb.Helper()
|
||||
|
||||
b, err := ip.MarshalBinary()
|
||||
require.NoError(tb, err)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestSyncTimeFromTrailer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10)
|
||||
|
||||
testCases := []struct {
|
||||
@ -492,7 +32,7 @@ func TestSyncTimeFromTrailer(t *testing.T) {
|
||||
name: "empty_key",
|
||||
}, {
|
||||
in: metadata.MD{"sync_time": []string{""}},
|
||||
wantError: `invalid value: strconv.ParseInt: parsing "": invalid syntax`,
|
||||
wantError: `bad value: strconv.ParseInt: parsing "": invalid syntax`,
|
||||
want: time.Time{},
|
||||
name: "empty_value",
|
||||
}, {
|
||||
@ -510,36 +50,3 @@ func TestSyncTimeFromTrailer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errSink error
|
||||
profSink *agd.Profile
|
||||
)
|
||||
|
||||
func BenchmarkDNSProfile_ToInternal(b *testing.B) {
|
||||
dp := NewTestDNSProfile(b)
|
||||
ctx := context.Background()
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
panic(err)
|
||||
},
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
profSink, _, errSink = dp.toInternal(ctx, TestUpdTime, testBind, errColl)
|
||||
}
|
||||
|
||||
require.NotNil(b, profSink)
|
||||
require.NoError(b, errSink)
|
||||
|
||||
// Most recent result, on a ThinkPad X13:
|
||||
// goos: linux
|
||||
// goarch: amd64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkDNSProfile_ToInternal
|
||||
// BenchmarkDNSProfile_ToInternal-16 157513 10340 ns/op 1148 B/op 27 allocs/op
|
||||
}
|
||||
|
@ -9,19 +9,117 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func TestProfileStorage_CreateAutoDevice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const devType = agd.DeviceTypeOther
|
||||
|
||||
gotReqCh := make(chan *backendpb.CreateDeviceRequest, 1)
|
||||
|
||||
srv := &testDNSServiceServer{
|
||||
OnCreateDeviceByHumanId: func(
|
||||
ctx context.Context,
|
||||
req *backendpb.CreateDeviceRequest,
|
||||
) (resp *backendpb.CreateDeviceResponse, err error) {
|
||||
defer func() {
|
||||
pt := testutil.PanicT{}
|
||||
testutil.RequireSend(pt, gotReqCh, req, testTimeout)
|
||||
}()
|
||||
|
||||
return &backendpb.CreateDeviceResponse{
|
||||
Device: &backendpb.DeviceSettings{
|
||||
Id: backendpb.TestDeviceIDStr,
|
||||
HumanIdLower: backendpb.TestHumanIDLowerStr,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
|
||||
OnGetDNSProfiles: func(
|
||||
req *backendpb.DNSProfilesRequest,
|
||||
srv backendpb.DNSService_GetDNSProfilesServer,
|
||||
) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnSaveDevicesBillingStat: func(
|
||||
srv backendpb.DNSService_SaveDevicesBillingStatServer,
|
||||
) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {
|
||||
panic(err)
|
||||
},
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
s, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
|
||||
BindSet: backendpb.TestBind,
|
||||
ErrColl: errColl,
|
||||
Endpoint: &url.URL{
|
||||
Scheme: "grpc",
|
||||
Host: l.Addr().String(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
grpcSrv := grpc.NewServer(
|
||||
grpc.ConnectionTimeout(1*time.Second),
|
||||
grpc.Creds(insecure.NewCredentials()),
|
||||
)
|
||||
backendpb.RegisterDNSServiceServer(grpcSrv, srv)
|
||||
|
||||
go func() {
|
||||
pt := &testutil.PanicT{}
|
||||
|
||||
srvErr := grpcSrv.Serve(l)
|
||||
require.NoError(pt, srvErr)
|
||||
}()
|
||||
t.Cleanup(grpcSrv.GracefulStop)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
resp, err := s.CreateAutoDevice(ctx, &profiledb.StorageCreateAutoDeviceRequest{
|
||||
ProfileID: backendpb.TestProfileID,
|
||||
HumanID: backendpb.TestHumanID,
|
||||
DeviceType: devType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotReq, ok := testutil.RequireReceive(t, gotReqCh, testTimeout)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, gotReq)
|
||||
|
||||
assert.Equal(t, backendpb.TestProfileIDStr, gotReq.DnsId)
|
||||
assert.Equal(t, backendpb.TestHumanIDStr, gotReq.HumanId)
|
||||
assert.Equal(t, backendpb.DeviceType(devType), gotReq.DeviceType)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Device)
|
||||
|
||||
assert.Equal(t, backendpb.TestDeviceID, resp.Device.ID)
|
||||
assert.Equal(t, backendpb.TestHumanIDLower, resp.Device.HumanIDLower)
|
||||
}
|
||||
|
||||
var (
|
||||
errSink error
|
||||
respSink *profiledb.StorageResponse
|
||||
respSink *profiledb.StorageProfilesResponse
|
||||
)
|
||||
|
||||
func BenchmarkProfileStorage_Profiles(b *testing.B) {
|
||||
@ -32,6 +130,13 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
|
||||
}
|
||||
|
||||
srv := &testDNSServiceServer{
|
||||
OnCreateDeviceByHumanId: func(
|
||||
ctx context.Context,
|
||||
req *backendpb.CreateDeviceRequest,
|
||||
) (resp *backendpb.CreateDeviceResponse, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnGetDNSProfiles: func(
|
||||
req *backendpb.DNSProfilesRequest,
|
||||
srv backendpb.DNSService_GetDNSProfilesServer,
|
||||
@ -41,6 +146,12 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
|
||||
|
||||
return sendErr
|
||||
},
|
||||
|
||||
OnSaveDevicesBillingStat: func(
|
||||
srv backendpb.DNSService_SaveDevicesBillingStatServer,
|
||||
) (err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
|
||||
errColl := &agdtest.ErrorCollector{
|
||||
@ -77,7 +188,7 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
|
||||
b.Cleanup(grpcSrv.GracefulStop)
|
||||
|
||||
ctx := context.Background()
|
||||
req := &profiledb.StorageRequest{}
|
||||
req := &profiledb.StorageProfilesRequest{}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
@ -94,5 +205,5 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkProfileStorage_Profiles
|
||||
// BenchmarkProfileStorage_Profiles-16 5347 245341 ns/op 15129 B/op 265 allocs/op
|
||||
// BenchmarkProfileStorage_Profiles-16 4128 291646 ns/op 18578 B/op 341 allocs/op
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
@ -103,8 +104,7 @@ func setupBillStat(
|
||||
sigHdlr *service.SignalHandler,
|
||||
errColl errcoll.Interface,
|
||||
) (rec *billstat.RuntimeRecorder, err error) {
|
||||
apiURL := netutil.CloneURL(&envs.BillStatURL.URL)
|
||||
billStatUploader, err := setupBillStatUploader(apiURL, errColl)
|
||||
billStatUploader, err := setupBillStatUploader(envs, errColl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating bill stat uploader: %w", err)
|
||||
}
|
||||
@ -146,9 +146,8 @@ func setupProfDB(
|
||||
sigHdlr *service.SignalHandler,
|
||||
errColl errcoll.Interface,
|
||||
) (profDB *profiledb.Default, err error) {
|
||||
apiURL := netutil.CloneURL(&envs.ProfilesURL.URL)
|
||||
bindSet := collectBindSubnetSet(grps)
|
||||
profStrg, err := setupProfStorage(apiURL, bindSet, errColl)
|
||||
profStrg, err := setupProfStorage(envs, bindSet, errColl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating profile storage: %w", err)
|
||||
}
|
||||
@ -159,14 +158,16 @@ func setupProfDB(
|
||||
ErrColl: errColl,
|
||||
FullSyncIvl: conf.FullRefreshIvl.Duration,
|
||||
FullSyncRetryIvl: conf.FullRefreshRetryIvl.Duration,
|
||||
InitialTimeout: timeout,
|
||||
CacheFilePath: envs.ProfilesCachePath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating default profile database: %w", err)
|
||||
}
|
||||
|
||||
refrIvl := conf.RefreshIvl.Duration
|
||||
err = initProfDB(profDB, timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing default profile database: %w", err)
|
||||
}
|
||||
|
||||
profDBRefr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
|
||||
Context: func() (ctx context.Context, cancel context.CancelFunc) {
|
||||
@ -174,7 +175,7 @@ func setupProfDB(
|
||||
},
|
||||
Refresher: profDB,
|
||||
Name: "profiledb",
|
||||
Interval: refrIvl,
|
||||
Interval: conf.RefreshIvl.Duration,
|
||||
RefreshOnShutdown: false,
|
||||
RandomizeStart: true,
|
||||
})
|
||||
@ -188,6 +189,27 @@ func setupProfDB(
|
||||
return profDB, nil
|
||||
}
|
||||
|
||||
// initProfDB refreshes the profile database initially. It logs an error if
|
||||
// it's a timeout, and returns it otherwise.
|
||||
func initProfDB(profDB *profiledb.Default, timeout time.Duration) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
log.Info("main: initial profiledb refresh")
|
||||
|
||||
err = profDB.Refresh(ctx)
|
||||
switch {
|
||||
case err == nil:
|
||||
log.Info("main: initial profiledb refresh succeeded")
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
log.Info("main: warning: initial profiledb refresh timeout: %s", err)
|
||||
default:
|
||||
return fmt.Errorf("initial refresh: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectBindSubnetSet returns a subnet set with IP addresses of servers in the
|
||||
// provided server groups grps.
|
||||
func collectBindSubnetSet(grps []*agd.ServerGroup) (s netutil.SubnetSet) {
|
||||
@ -223,36 +245,40 @@ const (
|
||||
schemeGRPCS = "grpcs"
|
||||
)
|
||||
|
||||
// setupProfStorage creates and returns a profile storage depending on the
|
||||
// provided API URL.
|
||||
func setupProfStorage(
|
||||
apiURL *url.URL,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (s profiledb.Storage, err error) {
|
||||
scheme := apiURL.Scheme
|
||||
if scheme == schemeGRPC || scheme == schemeGRPCS {
|
||||
return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
|
||||
BindSet: bindSet,
|
||||
Endpoint: apiURL,
|
||||
ErrColl: errColl,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
|
||||
}
|
||||
|
||||
// setupBillStatUploader creates and returns a billstat uploader depending on
|
||||
// the provided API URL.
|
||||
func setupBillStatUploader(
|
||||
apiURL *url.URL,
|
||||
envs *environments,
|
||||
errColl errcoll.Interface,
|
||||
) (s billstat.Uploader, err error) {
|
||||
apiURL := netutil.CloneURL(&envs.BillStatURL.URL)
|
||||
scheme := apiURL.Scheme
|
||||
if scheme == schemeGRPC || scheme == schemeGRPCS {
|
||||
return backendpb.NewBillStat(&backendpb.BillStatConfig{
|
||||
ErrColl: errColl,
|
||||
Endpoint: apiURL,
|
||||
APIKey: envs.BillStatAPIKey,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid backend api url: %s", apiURL)
|
||||
}
|
||||
|
||||
// setupProfStorage creates and returns a profile storage depending on the
|
||||
// provided API URL.
|
||||
func setupProfStorage(
|
||||
envs *environments,
|
||||
bindSet netutil.SubnetSet,
|
||||
errColl errcoll.Interface,
|
||||
) (s profiledb.Storage, err error) {
|
||||
apiURL := netutil.CloneURL(&envs.ProfilesURL.URL)
|
||||
scheme := apiURL.Scheme
|
||||
if scheme == schemeGRPC || scheme == schemeGRPCS {
|
||||
return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
|
||||
BindSet: bindSet,
|
||||
ErrColl: errColl,
|
||||
Endpoint: apiURL,
|
||||
APIKey: envs.ProfilesAPIKey,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
|
||||
@ -36,6 +37,9 @@ func Main() {
|
||||
|
||||
agd.InitRequestID()
|
||||
|
||||
// TODO(a.garipov, e.burkov): Consider adding timeouts for initialization.
|
||||
ctx := context.Background()
|
||||
|
||||
// Log only to stdout and let users decide how to process it.
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
@ -58,6 +62,10 @@ func Main() {
|
||||
|
||||
defer collectPanics(errColl)
|
||||
|
||||
// Cache manager
|
||||
|
||||
cacheManager := agdcache.NewDefaultManager()
|
||||
|
||||
// Configuration file
|
||||
|
||||
c, err := readConfig(envs.ConfPath)
|
||||
@ -86,7 +94,7 @@ func Main() {
|
||||
geoIP, geoIPRefr := &geoip.File{}, &agdservice.RefreshWorker{}
|
||||
geoIPErrCh := make(chan error, 1)
|
||||
|
||||
go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl)
|
||||
go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl, cacheManager)
|
||||
|
||||
// Safe-browsing and adult-blocking filters
|
||||
|
||||
@ -98,55 +106,57 @@ func Main() {
|
||||
maxFilterSize := c.Filters.MaxSize.Bytes()
|
||||
|
||||
cloner := dnsmsg.NewCloner(metrics.ClonerStat{})
|
||||
safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter(
|
||||
c.SafeBrowsing,
|
||||
|
||||
sbConf := c.SafeBrowsing.toInternal(
|
||||
errColl,
|
||||
cloner,
|
||||
cacheManager,
|
||||
agd.FilterListIDSafeBrowsing,
|
||||
envs.SafeBrowsingURL,
|
||||
envs.FilterCachePath,
|
||||
maxFilterSize,
|
||||
sigHdlr,
|
||||
errColl,
|
||||
)
|
||||
sbHashes, sbFilter, err := setupHashPrefixFilter(ctx, sbConf, sigHdlr)
|
||||
check(err)
|
||||
|
||||
adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter(
|
||||
c.AdultBlocking,
|
||||
abConf := c.AdultBlocking.toInternal(
|
||||
errColl,
|
||||
cloner,
|
||||
cacheManager,
|
||||
agd.FilterListIDAdultBlocking,
|
||||
envs.AdultBlockingURL,
|
||||
envs.FilterCachePath,
|
||||
maxFilterSize,
|
||||
sigHdlr,
|
||||
errColl,
|
||||
)
|
||||
abHashes, abFilter, err := setupHashPrefixFilter(ctx, abConf, sigHdlr)
|
||||
check(err)
|
||||
|
||||
_, newRegDomainsFilter, err := setupHashPrefixFilter(
|
||||
// Reuse general safe browsing filter configuration.
|
||||
c.SafeBrowsing,
|
||||
// Reuse general safe browsing filter configuration.
|
||||
nrdConf := c.SafeBrowsing.toInternal(
|
||||
errColl,
|
||||
cloner,
|
||||
cacheManager,
|
||||
agd.FilterListIDNewRegDomains,
|
||||
envs.NewRegDomainsURL,
|
||||
envs.FilterCachePath,
|
||||
maxFilterSize,
|
||||
sigHdlr,
|
||||
errColl,
|
||||
)
|
||||
_, nrdFilter, err := setupHashPrefixFilter(ctx, nrdConf, sigHdlr)
|
||||
check(err)
|
||||
|
||||
// Filter storage and filtering groups
|
||||
|
||||
fltStrgConf := c.Filters.toInternal(
|
||||
errColl,
|
||||
cacheManager,
|
||||
envs,
|
||||
safeBrowsingFilter,
|
||||
adultBlockingFilter,
|
||||
newRegDomainsFilter,
|
||||
sbFilter,
|
||||
abFilter,
|
||||
nrdFilter,
|
||||
)
|
||||
|
||||
fltRefrTimeout := c.Filters.RefreshTimeout.Duration
|
||||
fltStrg, err := setupFilterStorage(fltStrgConf, sigHdlr, fltRefrTimeout)
|
||||
fltStrg, err := setupFilterStorage(ctx, fltStrgConf, sigHdlr, fltRefrTimeout)
|
||||
check(err)
|
||||
|
||||
fltGroups, err := c.FilteringGroups.toInternal(fltStrg)
|
||||
@ -176,8 +186,6 @@ func Main() {
|
||||
srvGrps, err := c.ServerGroups.toInternal(messages, btdMgr, fltGroups, c.RateLimit, c.DNS)
|
||||
check(err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Start the bind-to-device manager here, now that no further calls to
|
||||
// btdMgr.ListenConfig are required.
|
||||
err = btdMgr.Start(ctx)
|
||||
@ -220,7 +228,13 @@ func Main() {
|
||||
// Rate limiting
|
||||
|
||||
consulAllowlistURL := &envs.ConsulAllowlistURL.URL
|
||||
rateLimiter, connLimiter, err := setupRateLimiter(c.RateLimit, consulAllowlistURL, sigHdlr, errColl)
|
||||
rateLimiter, connLimiter, err := setupRateLimiter(
|
||||
ctx,
|
||||
c.RateLimit,
|
||||
consulAllowlistURL,
|
||||
sigHdlr,
|
||||
errColl,
|
||||
)
|
||||
check(err)
|
||||
|
||||
// GeoIP database
|
||||
@ -251,15 +265,17 @@ func Main() {
|
||||
// TODO(a.garipov): Consider making these configurable via the configuration
|
||||
// file.
|
||||
hashStorages := map[string]*hashprefix.Storage{
|
||||
filter.GeneralTXTSuffix: safeBrowsingHashes,
|
||||
filter.AdultBlockingTXTSuffix: adultBlockingHashes,
|
||||
filter.GeneralTXTSuffix: sbHashes,
|
||||
filter.AdultBlockingTXTSuffix: abHashes,
|
||||
}
|
||||
|
||||
dnsConf := &dnssvc.Config{
|
||||
Messages: messages,
|
||||
Cloner: cloner,
|
||||
CacheManager: cacheManager,
|
||||
ControlConf: ctrlConf,
|
||||
ConnLimiter: connLimiter,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
AccessManager: accessGlobal,
|
||||
SafeBrowsing: hashprefix.NewMatcher(hashStorages),
|
||||
BillStat: billStatRec,
|
||||
@ -307,7 +323,15 @@ func Main() {
|
||||
|
||||
// Debug HTTP-service
|
||||
|
||||
debugSvc := debugsvc.New(envs.debugConf(dnsDB))
|
||||
debugSvcConf := envs.debugConf(dnsDB)
|
||||
debugSvcConf.Logger = slogLogger.With(slogutil.KeyPrefix, "debugsvc")
|
||||
debugSvcConf.Refreshers = debugsvc.Refreshers{
|
||||
"filter_storage": fltStrg,
|
||||
string(agd.FilterListIDSafeBrowsing): sbFilter,
|
||||
string(agd.FilterListIDAdultBlocking): abFilter,
|
||||
string(agd.FilterListIDNewRegDomains): nrdFilter,
|
||||
}
|
||||
debugSvc := debugsvc.New(debugSvcConf)
|
||||
|
||||
// The debug HTTP service is considered critical, so its Start method panics
|
||||
// instead of returning an error.
|
||||
@ -324,6 +348,9 @@ func Main() {
|
||||
runtime.Version(),
|
||||
)
|
||||
|
||||
// TODO(s.chzhen): Remove it.
|
||||
log.Debug("cache manager ids: %q", cacheManager.IDs())
|
||||
|
||||
os.Exit(sigHdlr.Handle(ctx))
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
|
||||
@ -25,8 +26,6 @@ import (
|
||||
"github.com/getsentry/sentry-go"
|
||||
)
|
||||
|
||||
// Environment configuration
|
||||
|
||||
// environments represents the configuration that is kept in the environment.
|
||||
type environments struct {
|
||||
AdultBlockingURL *urlutil.URL `env:"ADULT_BLOCKING_URL,notEmpty"`
|
||||
@ -44,14 +43,16 @@ type environments struct {
|
||||
SafeBrowsingURL *urlutil.URL `env:"SAFE_BROWSING_URL,notEmpty"`
|
||||
YoutubeSafeSearchURL *urlutil.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"`
|
||||
|
||||
BillStatAPIKey string `env:"BILLSTAT_API_KEY"`
|
||||
ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"`
|
||||
FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"`
|
||||
ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.pb"`
|
||||
GeoIPASNPath string `env:"GEOIP_ASN_PATH" envDefault:"./asn.mmdb"`
|
||||
GeoIPCountryPath string `env:"GEOIP_COUNTRY_PATH" envDefault:"./country.mmdb"`
|
||||
ProfilesAPIKey string `env:"PROFILES_API_KEY"`
|
||||
ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.pb"`
|
||||
QueryLogPath string `env:"QUERYLOG_PATH" envDefault:"./querylog.jsonl"`
|
||||
SentryDSN string `env:"SENTRY_DSN" envDefault:"stderr"`
|
||||
SSLKeyLogFile string `env:"SSL_KEY_LOG_FILE"`
|
||||
SentryDSN string `env:"SENTRY_DSN" envDefault:"stderr"`
|
||||
|
||||
ListenAddr net.IP `env:"LISTEN_ADDR" envDefault:"127.0.0.1"`
|
||||
|
||||
@ -115,10 +116,15 @@ func (envs *environments) buildErrColl() (errColl errcoll.Interface, err error)
|
||||
}
|
||||
|
||||
// geoIP returns an GeoIP database implementation from environment.
|
||||
func (envs *environments) geoIP(c *geoIPConfig) (g *geoip.File, err error) {
|
||||
func (envs *environments) geoIP(
|
||||
ctx context.Context,
|
||||
c *geoIPConfig,
|
||||
cacheManager agdcache.Manager,
|
||||
) (g *geoip.File, err error) {
|
||||
log.Debug("using geoip files %q and %q", envs.GeoIPASNPath, envs.GeoIPCountryPath)
|
||||
|
||||
g, err = geoip.NewFile(&geoip.FileConfig{
|
||||
g = geoip.NewFile(&geoip.FileConfig{
|
||||
CacheManager: cacheManager,
|
||||
ASNPath: envs.GeoIPASNPath,
|
||||
CountryPath: envs.GeoIPCountryPath,
|
||||
HostCacheSize: c.HostCacheSize,
|
||||
@ -126,8 +132,10 @@ func (envs *environments) geoIP(c *geoIPConfig) (g *geoip.File, err error) {
|
||||
AllTopASNs: geoip.DefaultTopASNs,
|
||||
CountryTopASNs: geoip.DefaultCountryTopASNs,
|
||||
})
|
||||
|
||||
err = g.Refresh(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("creating geoip: initial refresh: %w", err)
|
||||
}
|
||||
|
||||
return g, nil
|
||||
@ -155,7 +163,7 @@ func (envs *environments) debugConf(dnsDB dnsdb.Interface) (conf *debugsvc.Confi
|
||||
DNSDBAddr: dnsDBAddr,
|
||||
DNSDBHandler: dnsDBHdlr,
|
||||
|
||||
HealthAddr: addr,
|
||||
APIAddr: addr,
|
||||
PprofAddr: addr,
|
||||
PrometheusAddr: addr,
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
@ -63,6 +64,7 @@ type filtersConfig struct {
|
||||
// cacheDir must exist. c is assumed to be valid.
|
||||
func (c *filtersConfig) toInternal(
|
||||
errColl errcoll.Interface,
|
||||
cacheManager agdcache.Manager,
|
||||
envs *environments,
|
||||
safeBrowsing *hashprefix.Filter,
|
||||
adultBlocking *hashprefix.Filter,
|
||||
@ -78,6 +80,7 @@ func (c *filtersConfig) toInternal(
|
||||
NewRegDomains: newRegDomains,
|
||||
Now: time.Now,
|
||||
ErrColl: errColl,
|
||||
CacheManager: cacheManager,
|
||||
CacheDir: envs.FilterCachePath,
|
||||
CustomFilterCacheSize: c.CustomFilterCacheSize,
|
||||
SafeSearchCacheSize: c.SafeSearchCacheSize,
|
||||
@ -148,11 +151,14 @@ func (c *fltRuleListCache) validate() (err error) {
|
||||
// setupFilterStorage creates and returns a filter storage as well as starts and
|
||||
// registers its refresher in the signal handler.
|
||||
func setupFilterStorage(
|
||||
ctx context.Context,
|
||||
conf *filter.DefaultStorageConfig,
|
||||
sigHdlr *service.SignalHandler,
|
||||
refreshTimeout time.Duration,
|
||||
) (strg *filter.DefaultStorage, err error) {
|
||||
strg, err = filter.NewDefaultStorage(conf)
|
||||
strg = filter.NewDefaultStorage(conf)
|
||||
|
||||
err = strg.RefreshInitial(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating default filter storage: %w", err)
|
||||
}
|
||||
@ -162,12 +168,12 @@ func setupFilterStorage(
|
||||
return context.WithTimeout(context.Background(), refreshTimeout)
|
||||
},
|
||||
Refresher: strg,
|
||||
Name: "filters",
|
||||
Name: "filter_storage",
|
||||
Interval: conf.RefreshIvl,
|
||||
RefreshOnShutdown: false,
|
||||
RandomizeStart: false,
|
||||
})
|
||||
err = refr.Start(context.Background())
|
||||
err = refr.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("starting default filter storage update: %w", err)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
@ -55,8 +56,12 @@ func setupGeoIP(
|
||||
conf *geoIPConfig,
|
||||
envs *environments,
|
||||
errColl errcoll.Interface,
|
||||
cacheManager agdcache.Manager,
|
||||
) {
|
||||
geoIP, err := envs.geoIP(conf)
|
||||
// TODO(e.burkov): Pass the context through arguments.
|
||||
ctx := context.Background()
|
||||
|
||||
geoIP, err := envs.geoIP(ctx, conf, cacheManager)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("creating geoip: %w", err)
|
||||
|
||||
@ -78,7 +83,7 @@ func setupGeoIP(
|
||||
RefreshOnShutdown: false,
|
||||
RandomizeStart: false,
|
||||
})
|
||||
err = refr.Start(context.Background())
|
||||
err = refr.Start(ctx)
|
||||
if err != nil {
|
||||
errCh <- fmt.Errorf("starting geoip refresher: %w", err)
|
||||
|
||||
|
@ -138,6 +138,7 @@ func (c *rateLimitConfig) validate() (err error) {
|
||||
// setupRateLimiter creates and returns a backoff rate limiter as well as starts
|
||||
// and registers its refresher in the signal handler.
|
||||
func setupRateLimiter(
|
||||
ctx context.Context,
|
||||
conf *rateLimitConfig,
|
||||
consulAllowlist *url.URL,
|
||||
sigHdlr *service.SignalHandler,
|
||||
@ -145,9 +146,11 @@ func setupRateLimiter(
|
||||
) (rateLimiter *ratelimit.Backoff, connLimiter *connlimiter.Limiter, err error) {
|
||||
allowSubnets := netutil.UnembedPrefixes(conf.Allowlist.List)
|
||||
allowlist := ratelimit.NewDynamicAllowlist(allowSubnets, nil)
|
||||
refresher, err := consul.NewAllowlistRefresher(allowlist, consulAllowlist, errColl)
|
||||
refresher := consul.NewAllowlistRefresher(allowlist, consulAllowlist, errColl)
|
||||
|
||||
err = refresher.Refresh(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating allowlist refresher: %w", err)
|
||||
return nil, nil, fmt.Errorf("allowlist: initial refresh: %w", err)
|
||||
}
|
||||
|
||||
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
|
||||
@ -159,7 +162,7 @@ func setupRateLimiter(
|
||||
RandomizeStart: false,
|
||||
})
|
||||
|
||||
err = refr.Start(context.Background())
|
||||
err = refr.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("starting allowlist refresher: %w", err)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
@ -46,18 +47,21 @@ type safeBrowsingConfig struct {
|
||||
func (c *safeBrowsingConfig) toInternal(
|
||||
errColl errcoll.Interface,
|
||||
cloner *dnsmsg.Cloner,
|
||||
cacheManager agdcache.Manager,
|
||||
id agd.FilterListID,
|
||||
url *urlutil.URL,
|
||||
cacheDir string,
|
||||
maxSize uint64,
|
||||
) (fltConf *hashprefix.FilterConfig, err error) {
|
||||
) (fltConf *hashprefix.FilterConfig) {
|
||||
hashes, err := hashprefix.NewStorage("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Don't expect errors here because we pass an empty string.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &hashprefix.FilterConfig{
|
||||
Cloner: cloner,
|
||||
CacheManager: cacheManager,
|
||||
Hashes: hashes,
|
||||
URL: netutil.CloneURL(&url.URL),
|
||||
ErrColl: errColl,
|
||||
@ -69,7 +73,7 @@ func (c *safeBrowsingConfig) toInternal(
|
||||
CacheTTL: c.CacheTTL.Duration,
|
||||
CacheSize: c.CacheSize,
|
||||
MaxSize: maxSize,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// validate returns an error if the safe browsing filter configuration is
|
||||
@ -96,23 +100,18 @@ func (c *safeBrowsingConfig) validate() (err error) {
|
||||
// setupHashPrefixFilter creates and returns a hash-prefix filter as well as
|
||||
// starts and registers its refresher in the signal handler.
|
||||
func setupHashPrefixFilter(
|
||||
conf *safeBrowsingConfig,
|
||||
cloner *dnsmsg.Cloner,
|
||||
id agd.FilterListID,
|
||||
url *urlutil.URL,
|
||||
cachePath string,
|
||||
maxSize uint64,
|
||||
ctx context.Context,
|
||||
fltConf *hashprefix.FilterConfig,
|
||||
sigHdlr *service.SignalHandler,
|
||||
errColl errcoll.Interface,
|
||||
) (strg *hashprefix.Storage, flt *hashprefix.Filter, err error) {
|
||||
fltConf, err := conf.toInternal(errColl, cloner, id, url, cachePath, maxSize)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("configuring hash prefix filter %s: %w", id, err)
|
||||
}
|
||||
|
||||
flt, err = hashprefix.NewFilter(fltConf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating hash prefix filter %s: %w", id, err)
|
||||
return nil, nil, fmt.Errorf("creating hash prefix filter %s: %w", fltConf.ID, err)
|
||||
}
|
||||
|
||||
err = flt.RefreshInitial(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("initializing hash prefix filter %s: %w", fltConf.ID, err)
|
||||
}
|
||||
|
||||
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
|
||||
@ -122,14 +121,14 @@ func setupHashPrefixFilter(
|
||||
return context.WithTimeout(context.Background(), fltConf.RefreshTimeout)
|
||||
},
|
||||
Refresher: flt,
|
||||
Name: string(id),
|
||||
Name: string(fltConf.ID),
|
||||
Interval: fltConf.Staleness,
|
||||
RefreshOnShutdown: false,
|
||||
RandomizeStart: false,
|
||||
})
|
||||
err = refr.Start(context.Background())
|
||||
err = refr.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("starting refresher for hash prefix filter %s: %w", id, err)
|
||||
return nil, nil, fmt.Errorf("starting hash prefix filter %s refresher: %w", fltConf.ID, err)
|
||||
}
|
||||
|
||||
sigHdlr.Add(refr)
|
||||
|
@ -184,7 +184,8 @@ func (certs tlsConfigCerts) validate() (err error) {
|
||||
|
||||
// TLS Session Ticket Key Rotator
|
||||
|
||||
// ticketRotator is a refresh worker that rereads and resets TLS session tickets.
|
||||
// ticketRotator is a refresh worker that rereads and resets TLS session
|
||||
// tickets. It should be initially refreshed before use.
|
||||
type ticketRotator struct {
|
||||
errColl errcoll.Interface
|
||||
confs map[*tls.Config][]string
|
||||
@ -197,7 +198,7 @@ type ticketRotator struct {
|
||||
func newTicketRotator(
|
||||
errColl errcoll.Interface,
|
||||
grps []*agd.ServerGroup,
|
||||
) (tr *ticketRotator, err error) {
|
||||
) (tr *ticketRotator) {
|
||||
confs := map[*tls.Config][]string{}
|
||||
|
||||
for _, g := range grps {
|
||||
@ -213,17 +214,10 @@ func newTicketRotator(
|
||||
}
|
||||
}
|
||||
|
||||
tr = &ticketRotator{
|
||||
return &ticketRotator{
|
||||
errColl: errColl,
|
||||
confs: confs,
|
||||
}
|
||||
|
||||
err = tr.Refresh(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initial session ticket refresh: %w", err)
|
||||
}
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// sessTickLen is the length of a single TLS session ticket key in bytes.
|
||||
@ -320,9 +314,11 @@ func setupTicketRotator(
|
||||
sigHdlr *service.SignalHandler,
|
||||
errColl errcoll.Interface,
|
||||
) (err error) {
|
||||
tickRot, err := newTicketRotator(errColl, srvGrps)
|
||||
tickRot := newTicketRotator(errColl, srvGrps)
|
||||
|
||||
err = tickRot.Refresh(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up ticket rotator: %w", err)
|
||||
return fmt.Errorf("setting up ticket rotator: initial session ticket refresh: %w", err)
|
||||
}
|
||||
|
||||
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
|
||||
|
@ -19,7 +19,8 @@ import (
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// AllowlistRefresher is a refresh wrapper that updates the allowlist.
|
||||
// AllowlistRefresher is a refresh wrapper that updates the allowlist. It
|
||||
// should be initially refreshed before use.
|
||||
type AllowlistRefresher struct {
|
||||
allowlist *ratelimit.DynamicAllowlist
|
||||
http *agdhttp.Client
|
||||
@ -32,8 +33,8 @@ func NewAllowlistRefresher(
|
||||
allowlist *ratelimit.DynamicAllowlist,
|
||||
consulURL *url.URL,
|
||||
errColl errcoll.Interface,
|
||||
) (l *AllowlistRefresher, err error) {
|
||||
l = &AllowlistRefresher{
|
||||
) (l *AllowlistRefresher) {
|
||||
return &AllowlistRefresher{
|
||||
allowlist: allowlist,
|
||||
http: agdhttp.NewClient(&agdhttp.ClientConfig{
|
||||
// TODO(a.garipov): Consider making configurable.
|
||||
@ -42,13 +43,6 @@ func NewAllowlistRefresher(
|
||||
url: consulURL,
|
||||
errColl: errColl,
|
||||
}
|
||||
|
||||
err = l.Refresh(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initial refresh: %w", err)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
@ -22,6 +23,9 @@ func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testTimeout is the common timeout for tests and contexts.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// handleWithURL starts the test server with h, finishes it on cleanup, and
|
||||
// returns it's URL.
|
||||
//
|
||||
@ -83,12 +87,15 @@ func TestNewAllowlistRefresher(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
alr := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := alr.Refresh(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, ip := range tc.wantAllow {
|
||||
var ok bool
|
||||
ok, err = al.IsAllowed(context.Background(), ip)
|
||||
ok, err = al.IsAllowed(ctx, ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, ok)
|
||||
@ -96,7 +103,7 @@ func TestNewAllowlistRefresher(t *testing.T) {
|
||||
|
||||
for _, ip := range tc.wantNotAllow {
|
||||
var ok bool
|
||||
ok, err = al.IsAllowed(context.Background(), ip)
|
||||
ok, err = al.IsAllowed(ctx, ip)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, ok)
|
||||
@ -119,7 +126,10 @@ func TestNewAllowlistRefresher(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
alr := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := alr.Refresh(ctx)
|
||||
require.ErrorAs(t, err, &wantErr)
|
||||
|
||||
assert.Equal(t, wantErr.Got, status)
|
||||
@ -143,13 +153,12 @@ func TestAllowlistRefresher_Refresh_deadline(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
c, err := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
require.NoError(t, err)
|
||||
alr := consul.NewAllowlistRefresher(al, u, errColl)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
err = c.Refresh(ctx)
|
||||
err := alr.Refresh(ctx)
|
||||
assert.ErrorIs(t, err, context.Canceled)
|
||||
assert.ErrorIs(t, gotCollErr, context.Canceled)
|
||||
}
|
||||
|
@ -4,11 +4,13 @@ package debugsvc
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/AdguardTeam/golibs/pprofutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
@ -17,16 +19,25 @@ import (
|
||||
// Service is the HTTP service of AdGuard DNS. It serves prometheus metrics,
|
||||
// pprof, health check, DNSDB, and other endpoints..
|
||||
type Service struct {
|
||||
servers map[string]*server
|
||||
dnsDB http.Handler
|
||||
log *slog.Logger
|
||||
refrHdlr *refreshHandler
|
||||
dnsDB http.Handler
|
||||
servers map[string]*server
|
||||
}
|
||||
|
||||
// Config is the AdGuard DNS HTTP service configuration structure.
|
||||
type Config struct {
|
||||
Logger *slog.Logger
|
||||
|
||||
DNSDBAddr string
|
||||
DNSDBHandler http.Handler
|
||||
|
||||
HealthAddr string
|
||||
Refreshers Refreshers
|
||||
|
||||
// TODO(a.garipov): Consider using one address and removing addServer
|
||||
// logic.
|
||||
|
||||
APIAddr string
|
||||
PprofAddr string
|
||||
PrometheusAddr string
|
||||
}
|
||||
@ -34,6 +45,10 @@ type Config struct {
|
||||
// New returns a new properly initialized *Service.
|
||||
func New(c *Config) (svc *Service) {
|
||||
svc = &Service{
|
||||
log: c.Logger,
|
||||
refrHdlr: &refreshHandler{
|
||||
refrs: c.Refreshers,
|
||||
},
|
||||
servers: make(map[string]*server),
|
||||
dnsDB: c.DNSDBHandler,
|
||||
}
|
||||
@ -42,9 +57,9 @@ func New(c *Config) (svc *Service) {
|
||||
svc.addServer(c.PrometheusAddr, "prometheus")
|
||||
svc.addServer(c.PprofAddr, "pprof")
|
||||
|
||||
// The health-check server causes panic if it doesn't start because the
|
||||
// server is needed to check if the dns server is active.
|
||||
svc.addServer(c.HealthAddr, "health-check")
|
||||
// The health-check and API server causes panic if it doesn't start because
|
||||
// the server is needed to check if the dns server is active.
|
||||
svc.addServer(c.APIAddr, "api")
|
||||
|
||||
return svc
|
||||
}
|
||||
@ -56,10 +71,10 @@ type server struct {
|
||||
}
|
||||
|
||||
// startServer starts one server and panics if there is an unexpected error.
|
||||
func startServer(s *server) {
|
||||
defer log.OnPanicAndExit("startServer", 1)
|
||||
func startServer(ctx context.Context, l *slog.Logger, s *server) {
|
||||
defer recoverAndExit(ctx, l)
|
||||
|
||||
log.Info("debugsvc: %s: listen on %s", s.name, s.http.Addr)
|
||||
l.Info("listening", "name", s.name, "addr", s.http.Addr)
|
||||
|
||||
srv := s.http
|
||||
err := srv.ListenAndServe()
|
||||
@ -68,6 +83,29 @@ func startServer(s *server) {
|
||||
}
|
||||
}
|
||||
|
||||
// recoverAndExit recovers a panic, logs it using l, and then exits with
|
||||
// [osutil.ExitCodeFailure].
|
||||
//
|
||||
// TODO(a.garipov): Move to golibs.
|
||||
func recoverAndExit(ctx context.Context, l *slog.Logger) {
|
||||
v := recover()
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var args []any
|
||||
if err, ok := v.(error); ok {
|
||||
args = []any{slogutil.KeyError, err}
|
||||
} else {
|
||||
args = []any{"value", v}
|
||||
}
|
||||
|
||||
l.ErrorContext(ctx, "recovered from panic", args...)
|
||||
slogutil.PrintStack(ctx, l, slog.LevelError)
|
||||
|
||||
os.Exit(osutil.ExitCodeFailure)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ service.Interface = (*Service)(nil)
|
||||
|
||||
@ -78,9 +116,9 @@ var _ service.Interface = (*Service)(nil)
|
||||
// TODO(a.garipov): Wait for the services to go online.
|
||||
//
|
||||
// TODO(a.garipov): Use the context for cancelation.
|
||||
func (svc *Service) Start(_ context.Context) (err error) {
|
||||
func (svc *Service) Start(ctx context.Context) (err error) {
|
||||
for _, srv := range svc.servers {
|
||||
go startServer(srv)
|
||||
go startServer(ctx, svc.log, srv)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -98,10 +136,10 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
|
||||
|
||||
srvNum++
|
||||
|
||||
log.Info("debugsvc: %s: server is shutdown", srv.name)
|
||||
svc.log.Info("server is shutdown", "name", srv.name)
|
||||
}
|
||||
|
||||
log.Info("debugsvc: servers shutdown: %d", srvNum)
|
||||
svc.log.Info("all servers shutdown", "num", srvNum)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -127,7 +165,7 @@ func (svc *Service) addServer(addr, name string) {
|
||||
http: &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
ErrorLog: log.StdLog("debugsvc", log.DEBUG),
|
||||
ErrorLog: slog.NewLogLogger(svc.log.Handler(), slog.LevelDebug),
|
||||
},
|
||||
name: name,
|
||||
}
|
||||
@ -143,35 +181,37 @@ func (svc *Service) addServer(addr, name string) {
|
||||
// addHandler func returns the resMux that combine with the mux from args.
|
||||
func (svc *Service) addHandler(serviceName string, mux *http.ServeMux) {
|
||||
switch serviceName {
|
||||
case "api":
|
||||
svc.apiMux(mux)
|
||||
case "dnsdb":
|
||||
svc.dnsDBMux(mux)
|
||||
case "health-check":
|
||||
healthMux(mux)
|
||||
case "pprof":
|
||||
// TODO(a.garipov): Find ways to wrap pprof handlers.
|
||||
pprofutil.RoutePprof(mux)
|
||||
case "prometheus":
|
||||
promMux(mux)
|
||||
svc.promMux(mux)
|
||||
default:
|
||||
panic(fmt.Errorf("debugsvc: could not find mux for service %q", serviceName))
|
||||
}
|
||||
}
|
||||
|
||||
// apiMux adds the health-check and other debug API handlers to mux.
|
||||
func (svc *Service) apiMux(mux *http.ServeMux) {
|
||||
mux.Handle("GET /health-check", svc.middleware(
|
||||
http.HandlerFunc(serveHealthCheck),
|
||||
slog.LevelDebug,
|
||||
))
|
||||
mux.Handle("POST /debug/api/refresh", svc.middleware(svc.refrHdlr, slog.LevelInfo))
|
||||
}
|
||||
|
||||
// dnsDBMux adds the DNSDB CSV dump handler to mux.
|
||||
//
|
||||
// TODO(a.garipov): Tests.
|
||||
func (svc *Service) dnsDBMux(mux *http.ServeMux) {
|
||||
mux.Handle("/dnsdb/csv", svc.dnsDB)
|
||||
mux.Handle("POST /dnsdb/csv", svc.middleware(svc.dnsDB, slog.LevelInfo))
|
||||
}
|
||||
|
||||
// healthMux adds handler func to the mux from args for the health check service.
|
||||
func healthMux(mux *http.ServeMux) {
|
||||
mux.HandleFunc(
|
||||
"/health-check",
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "OK")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// promMux adds handler func to the mux from args for the prometheus service.
|
||||
func promMux(mux *http.ServeMux) {
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
// promMux adds the prometheus service handler to mux.
|
||||
func (svc *Service) promMux(mux *http.ServeMux) {
|
||||
mux.Handle("GET /metrics", svc.middleware(promhttp.Handler(), slog.LevelDebug))
|
||||
}
|
||||
|
@ -1,21 +1,23 @@
|
||||
package debugsvc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
// TODO(a.garipov): Improve and split tests.
|
||||
|
||||
// testTimeout is a common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
@ -31,10 +33,21 @@ func TestService_Start(t *testing.T) {
|
||||
require.NoError(pt, err)
|
||||
})
|
||||
|
||||
refreshed := false
|
||||
c := &debugsvc.Config{
|
||||
DNSDBAddr: addr,
|
||||
DNSDBHandler: h,
|
||||
HealthAddr: addr,
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
DNSDBAddr: addr,
|
||||
DNSDBHandler: h,
|
||||
Refreshers: debugsvc.Refreshers{
|
||||
"test": &agdtest.Refresher{
|
||||
OnRefresh: func(_ context.Context) (err error) {
|
||||
refreshed = true
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
APIAddr: addr,
|
||||
PprofAddr: addr,
|
||||
PrometheusAddr: addr,
|
||||
}
|
||||
@ -58,15 +71,15 @@ func TestService_Start(t *testing.T) {
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
|
||||
// First check health-check service URL.
|
||||
// As the service could not be ready yet, check for it in periodically.
|
||||
// First check health-check service URL. As the service could not be ready
|
||||
// yet, check for it in periodically.
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err = client.Get(fmt.Sprintf("http://%s/health-check", addr))
|
||||
return err == nil
|
||||
}, 1*time.Second, 100*time.Millisecond)
|
||||
|
||||
body = readRespBody(t, resp)
|
||||
assert.Equal(t, []byte("OK"), body)
|
||||
assert.Equal(t, []byte("OK\n"), body)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// Check pprof service URL.
|
||||
@ -84,6 +97,18 @@ func TestService_Start(t *testing.T) {
|
||||
body = readRespBody(t, resp)
|
||||
assert.True(t, len(body) > 0)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// Check refresh API.
|
||||
reqBody := strings.NewReader(`{"ids":["test"]}`)
|
||||
urlStr := fmt.Sprintf("http://%s/debug/api/refresh", addr)
|
||||
resp, err = client.Post(urlStr, "application/json", reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, refreshed)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body = readRespBody(t, resp)
|
||||
assert.Equal(t, []byte(`{"results":{"test":"ok"}}`+"\n"), body)
|
||||
}
|
||||
|
||||
// readRespBody is a helper function that reads and returns
|
||||
|
24
internal/debugsvc/handler.go
Normal file
24
internal/debugsvc/handler.go
Normal file
@ -0,0 +1,24 @@
|
||||
package debugsvc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
)
|
||||
|
||||
// serveHealthCheck handles the GET /health-check endpoint.
|
||||
//
|
||||
// TODO(a.garipov): Move to golibs.
|
||||
func serveHealthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(httphdr.ContentType, "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err := io.WriteString(w, "OK\n")
|
||||
if err != nil {
|
||||
ctx := r.Context()
|
||||
l := slogutil.MustLoggerFromContext(ctx)
|
||||
l.DebugContext(ctx, "writing health-check response", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
62
internal/debugsvc/middleware.go
Normal file
62
internal/debugsvc/middleware.go
Normal file
@ -0,0 +1,62 @@
|
||||
package debugsvc
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
)
|
||||
|
||||
// middleware is the base middleware for AdGuard DNS debug API that adds a
|
||||
// logger and logs the queries starting and finishing at the given level.
|
||||
func (svc *Service) middleware(h http.Handler, lvl slog.Level) (wrapped http.Handler) {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
respHdr := w.Header()
|
||||
respHdr.Add(httphdr.Server, agdhttp.UserAgent())
|
||||
|
||||
l := svc.log.With(
|
||||
"raddr", r.RemoteAddr,
|
||||
"method", r.Method,
|
||||
"host", r.Host,
|
||||
"request_uri", r.RequestURI,
|
||||
)
|
||||
|
||||
ctx := slogutil.ContextWithLogger(r.Context(), l)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
rw := &codeRecorderResponseWriter{
|
||||
ResponseWriter: w,
|
||||
}
|
||||
|
||||
l.Log(ctx, lvl, "started")
|
||||
defer func() { l.Log(ctx, lvl, "finished", "code", rw.code) }()
|
||||
|
||||
h.ServeHTTP(rw, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(f)
|
||||
}
|
||||
|
||||
// codeRecorderResponseWriter wraps an [http.ResponseWriter] allowing to save
|
||||
// the response code.
|
||||
//
|
||||
// TODO(a.garipov): Process zero code.
|
||||
//
|
||||
// TODO(a.garipov): Move to golibs.
|
||||
type codeRecorderResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
|
||||
code int
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ http.ResponseWriter = (*codeRecorderResponseWriter)(nil)
|
||||
|
||||
// WriteHeader implements [http.ResponseWriter] for *codeRecorderResponseWriter.
|
||||
func (w *codeRecorderResponseWriter) WriteHeader(code int) {
|
||||
w.code = code
|
||||
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
128
internal/debugsvc/refresh.go
Normal file
128
internal/debugsvc/refresh.go
Normal file
@ -0,0 +1,128 @@
|
||||
package debugsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// RefresherID is a type alias for strings that represent IDs of refreshers.
|
||||
//
|
||||
// TODO(a.garipov): Consider a newtype with validations.
|
||||
type RefresherID = string
|
||||
|
||||
// Refreshers is a type alias for maps of refresher IDs to Refreshers
|
||||
// themselves.
|
||||
type Refreshers map[RefresherID]agdservice.Refresher
|
||||
|
||||
// refreshHandler performs debug refreshes.
|
||||
type refreshHandler struct {
|
||||
refrs Refreshers
|
||||
}
|
||||
|
||||
// refreshRequest describes the request to the POST /debug/api/refresh HTTP API.
|
||||
type refreshRequest struct {
|
||||
IDs []RefresherID `json:"ids"`
|
||||
}
|
||||
|
||||
// refreshResponse describes the response to the POST /debug/api/refresh HTTP
|
||||
// API.
|
||||
type refreshResponse struct {
|
||||
Results map[RefresherID]string `json:"results"`
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ http.Handler = (*refreshHandler)(nil)
|
||||
|
||||
// ServeHTTP implements the [http.Handler] interface for *refreshHandler.
|
||||
func (h *refreshHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
l := slogutil.MustLoggerFromContext(ctx)
|
||||
|
||||
req := &refreshRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "decoding request", slogutil.KeyError, err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp := &refreshResponse{
|
||||
Results: map[RefresherID]string{},
|
||||
}
|
||||
|
||||
reqIDs, err := h.idsFromReq(req.IDs)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "validating request", slogutil.KeyError, err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, id := range reqIDs {
|
||||
resp.Results[id] = h.refresh(ctx, l, id)
|
||||
}
|
||||
|
||||
w.Header().Set(httphdr.ContentType, "application/json")
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "writing response", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
|
||||
// idsFromReq validates the form of the request and returns the IDs of
|
||||
// refreshers to refresh.
|
||||
func (h *refreshHandler) idsFromReq(reqIDs []RefresherID) (ids []RefresherID, err error) {
|
||||
l := len(reqIDs)
|
||||
switch l {
|
||||
case 0:
|
||||
return nil, errors.Error("no ids")
|
||||
case 1:
|
||||
if reqIDs[0] != "*" {
|
||||
return reqIDs, nil
|
||||
}
|
||||
|
||||
allIDs := maps.Keys(h.refrs)
|
||||
slices.Sort(allIDs)
|
||||
|
||||
return allIDs, nil
|
||||
default:
|
||||
starIdx := slices.Index(reqIDs, "*")
|
||||
if starIdx == -1 {
|
||||
return reqIDs, nil
|
||||
}
|
||||
|
||||
return nil, errors.Error(`"*" cannot be used with other ids`)
|
||||
}
|
||||
}
|
||||
|
||||
// refresh performs a single refresh and returns the result as a string.
|
||||
func (h *refreshHandler) refresh(ctx context.Context, l *slog.Logger, id RefresherID) (res string) {
|
||||
r, ok := h.refrs[id]
|
||||
if !ok {
|
||||
return "error: refresher not found"
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := r.Refresh(ctx)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "refresher error", "id", id, slogutil.KeyError, err)
|
||||
|
||||
return fmt.Sprintf("error: %s", err)
|
||||
}
|
||||
|
||||
l.InfoContext(ctx, "refresh finished", "id", id, "duration", time.Since(start))
|
||||
|
||||
return "ok"
|
||||
}
|
@ -175,6 +175,7 @@ var clonerTestCases = []clonerTestCase{{
|
||||
&dns.SVCBLocal{KeyCode: dns.SVCBKey(1234), Data: []byte{3, 2, 1, 0}},
|
||||
&dns.SVCBMandatory{Code: []dns.SVCBKey{dns.SVCB_ALPN}},
|
||||
&dns.SVCBNoDefaultAlpn{},
|
||||
&dns.SVCBOhttp{},
|
||||
&dns.SVCBPort{Port: 443},
|
||||
}),
|
||||
name: "resp_https",
|
||||
@ -195,6 +196,14 @@ var clonerTestCases = []clonerTestCase{{
|
||||
name: "resp_https_empty_mandatory",
|
||||
wantFull: assert.True,
|
||||
handledByClone: true,
|
||||
}, {
|
||||
msg: newHTTPSResp([]dns.SVCBKeyValue{
|
||||
&dns.SVCBNoDefaultAlpn{},
|
||||
&dns.SVCBOhttp{},
|
||||
}),
|
||||
name: "resp_https_empty_values",
|
||||
wantFull: assert.True,
|
||||
handledByClone: true,
|
||||
}, {
|
||||
msg: newHTTPSResp(nil),
|
||||
name: "resp_https_nil_hint",
|
||||
@ -338,26 +347,27 @@ func BenchmarkClone(b *testing.B) {
|
||||
|
||||
// Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
|
||||
//
|
||||
// goos: linux
|
||||
// goos: darwin
|
||||
// goarch: amd64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkClone/req_a-16 24691725 250.1 ns/op 168 B/op 2 allocs/op
|
||||
// BenchmarkClone/resp_a-16 12547648 429.8 ns/op 256 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_a_many-16 10174539 602.5 ns/op 344 B/op 7 allocs/op
|
||||
// BenchmarkClone/resp_a_soa-16 10228933 600.0 ns/op 368 B/op 6 allocs/op
|
||||
// BenchmarkClone/req_aaaa-16 24920611 248.6 ns/op 168 B/op 2 allocs/op
|
||||
// BenchmarkClone/resp_aaaa-16 13603160 474.0 ns/op 264 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_cname_a-16 10398249 589.2 ns/op 320 B/op 6 allocs/op
|
||||
// BenchmarkClone/resp_mx-16 15299034 414.9 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_ptr-16 14701116 386.7 ns/op 232 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_txt-16 11148487 495.6 ns/op 296 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_srv-16 16175085 398.3 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_not_full-16 16115785 366.2 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_https-16 2940937 1998 ns/op 880 B/op 24 allocs/op
|
||||
// BenchmarkClone/resp_https_empty_hint-16 8712819 744.4 ns/op 424 B/op 8 allocs/op
|
||||
// BenchmarkClone/resp_https_empty_mandatory-16 8605273 655.8 ns/op 384 B/op 7 allocs/op
|
||||
// BenchmarkClone/resp_a_ecs-16 7337880 777.1 ns/op 384 B/op 8 allocs/op
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg
|
||||
// cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
|
||||
// BenchmarkClone/req_a-12 8987066 123.7 ns/op 168 B/op 2 allocs/op
|
||||
// BenchmarkClone/resp_a-12 5619037 212.7 ns/op 256 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_a_many-12 4385636 273.2 ns/op 344 B/op 7 allocs/op
|
||||
// BenchmarkClone/resp_a_soa-12 4533913 263.1 ns/op 368 B/op 6 allocs/op
|
||||
// BenchmarkClone/req_aaaa-12 10196326 116.9 ns/op 168 B/op 2 allocs/op
|
||||
// BenchmarkClone/resp_aaaa-12 5666234 211.8 ns/op 264 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_cname_a-12 4713193 255.4 ns/op 320 B/op 6 allocs/op
|
||||
// BenchmarkClone/resp_mx-12 6539623 181.8 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_ptr-12 6744601 179.8 ns/op 232 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_txt-12 5120538 230.8 ns/op 296 B/op 5 allocs/op
|
||||
// BenchmarkClone/resp_srv-12 6549402 181.5 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_not_full-12 6529968 181.4 ns/op 248 B/op 4 allocs/op
|
||||
// BenchmarkClone/resp_https-12 1361680 880.8 ns/op 896 B/op 24 allocs/op
|
||||
// BenchmarkClone/resp_https_empty_hint-12 3548672 345.3 ns/op 424 B/op 8 allocs/op
|
||||
// BenchmarkClone/resp_https_empty_mandatory-12 4055365 291.6 ns/op 384 B/op 7 allocs/op
|
||||
// BenchmarkClone/resp_https_empty_values-12 4434608 269.8 ns/op 376 B/op 6 allocs/op
|
||||
// BenchmarkClone/resp_a_ecs-12 3741008 318.5 ns/op 384 B/op 8 allocs/op
|
||||
}
|
||||
|
||||
func BenchmarkCloner_Clone(b *testing.B) {
|
||||
@ -388,28 +398,29 @@ func BenchmarkCloner_Clone(b *testing.B) {
|
||||
|
||||
// Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
|
||||
//
|
||||
// goos: linux
|
||||
// goos: darwin
|
||||
// goarch: amd64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkCloner_Clone/req_a-16 167307522 36.48 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a-16 92398767 66.01 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_many-16 60790945 111.8 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_soa-16 61474227 91.50 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/req_aaaa-16 158363983 39.89 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_aaaa-16 72113028 76.83 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_cname_a-16 67518502 89.24 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_mx-16 89713944 70.96 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_ptr-16 87175648 67.42 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_txt-16 80373494 75.37 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_srv-16 85734901 70.36 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_not_full-16 28868667 211.3 ns/op 64 B/op 1 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https-16 13196191 402.3 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_empty_hint-16 48459688 125.6 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_empty_mandatory-16 63759298 96.10 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_nil_hint-16 89683288 66.75 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_ecs-16 53174110 119.6 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_ecs_nil-16 69814755 92.42 ns/op 0 B/op 0 allocs/op
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg
|
||||
// cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
|
||||
// BenchmarkCloner_Clone/req_a-12 28363105 39.40 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a-12 17288836 72.96 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_many-12 11043524 105.9 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_soa-12 14058405 84.21 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/req_aaaa-12 31732440 37.61 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_aaaa-12 19054647 65.48 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_cname_a-12 13343605 90.35 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_mx-12 19749904 59.09 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_ptr-12 20109849 60.00 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_txt-12 17270551 64.07 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_srv-12 18197905 66.67 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_not_full-12 12713991 95.55 ns/op 64 B/op 1 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https-12 3037629 395.9 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_empty_hint-12 10258635 119.3 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_empty_mandatory-12 14306239 82.14 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_empty_values-12 15568735 80.12 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_https_nil_hint-12 20536422 59.70 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_ecs-12 10978351 107.8 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkCloner_Clone/resp_a_ecs_nil-12 13626586 93.56 ns/op 0 B/op 0 allocs/op
|
||||
}
|
||||
|
||||
func FuzzCloner_Clone(f *testing.F) {
|
||||
|
@ -23,7 +23,6 @@ type httpsCloner struct {
|
||||
ipv6hint *syncutil.Pool[dns.SVCBIPv6Hint]
|
||||
local *syncutil.Pool[dns.SVCBLocal]
|
||||
mandatory *syncutil.Pool[dns.SVCBMandatory]
|
||||
noDefALPN *syncutil.Pool[dns.SVCBNoDefaultAlpn]
|
||||
port *syncutil.Pool[dns.SVCBPort]
|
||||
|
||||
// Miscellaneous.
|
||||
@ -59,9 +58,6 @@ func newHTTPSCloner() (c *httpsCloner) {
|
||||
mandatory: syncutil.NewPool(func() (v *dns.SVCBMandatory) {
|
||||
return &dns.SVCBMandatory{}
|
||||
}),
|
||||
noDefALPN: syncutil.NewPool(func() (v *dns.SVCBNoDefaultAlpn) {
|
||||
return &dns.SVCBNoDefaultAlpn{}
|
||||
}),
|
||||
port: syncutil.NewPool(func() (v *dns.SVCBPort) {
|
||||
return &dns.SVCBPort{}
|
||||
}),
|
||||
@ -94,8 +90,8 @@ func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) {
|
||||
|
||||
clone.Value = clone.Value[:0]
|
||||
for _, orig := range rr.Value {
|
||||
valClone, valFull := c.cloneKV(orig)
|
||||
if !valFull {
|
||||
valClone := c.cloneKV(orig)
|
||||
if valClone == nil {
|
||||
// This branch is only reached if there is a new SVCB key-value type
|
||||
// in miekg/dns. Give up and just use their copy function.
|
||||
return dns.Copy(rr).(*dns.HTTPS), false
|
||||
@ -107,12 +103,12 @@ func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) {
|
||||
return clone, true
|
||||
}
|
||||
|
||||
// cloneKV returns a deep clone of orig. full is true if orig was recognized.
|
||||
func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue, full bool) {
|
||||
// cloneKV returns a deep clone of orig. clone is nil if orig wasn't
|
||||
// recognized.
|
||||
func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue) {
|
||||
switch orig := orig.(type) {
|
||||
case *dns.SVCBAlpn:
|
||||
v := c.alpn.Get()
|
||||
|
||||
v.Alpn = appendIfNotNil(v.Alpn[:0], orig.Alpn)
|
||||
|
||||
clone = v
|
||||
@ -123,49 +119,57 @@ func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue, fu
|
||||
clone = v
|
||||
case *dns.SVCBECHConfig:
|
||||
v := c.echconfig.Get()
|
||||
|
||||
v.ECH = appendIfNotNil(v.ECH[:0], orig.ECH)
|
||||
|
||||
clone = v
|
||||
case *dns.SVCBIPv4Hint:
|
||||
v := c.ipv4hint.Get()
|
||||
|
||||
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
|
||||
|
||||
clone = v
|
||||
case *dns.SVCBIPv6Hint:
|
||||
v := c.ipv6hint.Get()
|
||||
|
||||
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
|
||||
|
||||
clone = v
|
||||
case *dns.SVCBLocal:
|
||||
v := c.local.Get()
|
||||
v.KeyCode = orig.KeyCode
|
||||
|
||||
v.Data = appendIfNotNil(v.Data[:0], orig.Data)
|
||||
|
||||
clone = v
|
||||
case *dns.SVCBMandatory:
|
||||
v := c.mandatory.Get()
|
||||
|
||||
v.Code = appendIfNotNil(v.Code[:0], orig.Code)
|
||||
|
||||
clone = v
|
||||
case *dns.SVCBNoDefaultAlpn:
|
||||
clone = c.noDefALPN.Get()
|
||||
case *dns.SVCBPort:
|
||||
v := c.port.Get()
|
||||
*v = *orig
|
||||
|
||||
clone = v
|
||||
case
|
||||
*dns.SVCBNoDefaultAlpn,
|
||||
*dns.SVCBOhttp:
|
||||
// Just use the original value since these [dns.SVCBKeyValue] types are
|
||||
// pointers to empty structures, so we're only interested in the actual
|
||||
// type.
|
||||
clone = orig
|
||||
default:
|
||||
// This branch is only reached if there is a new SVCB key-value type
|
||||
// in miekg/dns.
|
||||
return nil, false
|
||||
clone = c.cloneIfHint(orig)
|
||||
}
|
||||
|
||||
return clone, true
|
||||
// This is only nil if there is a new SVCB key-value type in miekg/dns.
|
||||
return clone
|
||||
}
|
||||
|
||||
// cloneIfHint returns a deep clone of orig if it's either an [dns.SVCBIPv4Hint]
|
||||
// or [dns.SVCBIPv6Hint]. Otherwise, it returns nil.
|
||||
func (c *httpsCloner) cloneIfHint(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue) {
|
||||
switch orig := orig.(type) {
|
||||
case *dns.SVCBIPv4Hint:
|
||||
v := c.ipv4hint.Get()
|
||||
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
|
||||
|
||||
return v
|
||||
case *dns.SVCBIPv6Hint:
|
||||
v := c.ipv6hint.Get()
|
||||
v.Hint = c.appendIPs(v.Hint[:0], orig.Hint)
|
||||
|
||||
return v
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// appendIPs appends the clones of IP addresses from orig to hints and returns
|
||||
@ -220,10 +224,12 @@ func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) {
|
||||
c.local.Put(kv)
|
||||
case *dns.SVCBMandatory:
|
||||
c.mandatory.Put(kv)
|
||||
case *dns.SVCBNoDefaultAlpn:
|
||||
c.noDefALPN.Put(kv)
|
||||
case *dns.SVCBPort:
|
||||
c.port.Put(kv)
|
||||
case
|
||||
*dns.SVCBNoDefaultAlpn,
|
||||
*dns.SVCBOhttp:
|
||||
// Don't use pool for empty structures, see comment in [cloneKV].
|
||||
default:
|
||||
// This branch is only reached if there is a new SVCB key-value type
|
||||
// in miekg/dns. Noting to do.
|
||||
@ -234,7 +240,6 @@ func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) {
|
||||
func (c *httpsCloner) putIPs(ips []net.IP) {
|
||||
for _, ip := range ips {
|
||||
if cap(ip) >= 16 {
|
||||
// nolint:looppointer // Slicing is used to get the array pointer.
|
||||
c.ip.Put((*[16]byte)(ip[:16]))
|
||||
}
|
||||
}
|
||||
|
2
internal/dnsserver/cache/cache_test.go
vendored
2
internal/dnsserver/cache/cache_test.go
vendored
@ -39,8 +39,8 @@ func TestMiddleware_Wrap(t *testing.T) {
|
||||
testCases := []struct {
|
||||
req *dns.Msg
|
||||
resp *dns.Msg
|
||||
name string
|
||||
minTTL *time.Duration
|
||||
name string
|
||||
wantNumReq int
|
||||
wantTTL uint32
|
||||
}{{
|
||||
|
@ -133,9 +133,9 @@ type RequestInfo struct {
|
||||
// StartTime is the request's start time. It's never zero value.
|
||||
StartTime time.Time
|
||||
|
||||
// TLSServerName is the server name field of the client's TLS hello request.
|
||||
// It is set only if the protocol of the server is either DoQ, DoT or DoH.
|
||||
// Note, that the original SNI is transformed to lower-case.
|
||||
// TLSServerName is the original, non-lowercased server name field of the
|
||||
// client's TLS hello request. It is set only if the protocol of the server
|
||||
// is either DoQ, DoT or DoH.
|
||||
//
|
||||
// TODO(ameshkov): use r.TLS with DoH3 (see addRequestInfo).
|
||||
TLSServerName string
|
||||
|
@ -1,21 +1,21 @@
|
||||
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
|
||||
|
||||
go 1.22.4
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.23.2
|
||||
github.com/AdguardTeam/golibs v0.24.0
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||
github.com/ameshkov/dnsstamps v1.0.3
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/panjf2000/ants/v2 v2.9.1
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/panjf2000/ants/v2 v2.10.0
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.45.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -24,22 +24,21 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.52.3 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/prometheus/common v0.54.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
|
||||
github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
|
||||
github.com/AdguardTeam/golibs v0.24.0 h1:qAnOq7BQtwSVo7Co9q703/n+nZ2Ap6smkugU9G9MomY=
|
||||
github.com/AdguardTeam/golibs v0.24.0/go.mod h1:9/vJcYznW7RlmCT/Qzi8XNZGj+ZbWfHZJmEXKnRpCAU=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
@ -20,48 +20,45 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 h1:ASJ/LAqdCHOyMYI+dwNxn7Rd8FscNkMyTr1KZU1JI/M=
|
||||
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/panjf2000/ants/v2 v2.9.1 h1:Q5vh5xohbsZXGcD6hhszzGqB7jSSc2/CRr3QKIga8Kw=
|
||||
github.com/panjf2000/ants/v2 v2.9.1/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
|
||||
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
|
||||
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
|
||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
||||
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
@ -69,27 +66,27 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
|
||||
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
@ -18,21 +18,24 @@ const ErrClosed = errors.Error("the pool is closed")
|
||||
// must use the context's deadline if it's specified.
|
||||
type Factory func(ctx context.Context) (conn net.Conn, err error)
|
||||
|
||||
// Pool is a structure that implements a net.Conn pool. Must be initialized
|
||||
// using the NewPool method.
|
||||
// Pool is a structure that implements a net.Conn pool.
|
||||
type Pool struct {
|
||||
// IdleTimeout is the maximum TTL of an idle connection in the pool.
|
||||
// Connections that weren't used for more than the specified duration will
|
||||
// be closed. If set to 0, connections don't expire. Default value is 0.
|
||||
IdleTimeout time.Duration
|
||||
// connsChanMu is used to synchronize the closing of connsChan.
|
||||
connsChanMu *sync.RWMutex
|
||||
|
||||
// connsChan is the storage for our connections.
|
||||
connsChan chan *Conn
|
||||
connsChanMu sync.RWMutex
|
||||
connsChan chan *Conn
|
||||
|
||||
// factory is the Pool's factory method. It is called whenever there are no
|
||||
// factory is the Pool's factory method. It is called whenever there are no
|
||||
// more connections in the pool.
|
||||
factory Factory
|
||||
|
||||
// IdleTimeout is the maximum TTL of an idle connection in the pool.
|
||||
// Connections that weren't used for more than the specified duration will
|
||||
// be closed. If set to 0, connections don't expire.
|
||||
//
|
||||
// TODO(a.garipov): Put into a config.
|
||||
IdleTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewPool creates a new Pool instance. maxCapacity configures the maximum
|
||||
@ -40,8 +43,9 @@ type Pool struct {
|
||||
// Put will close the connection instead of adding it to the pool.
|
||||
func NewPool(maxCapacity int, factory Factory) (p *Pool) {
|
||||
return &Pool{
|
||||
connsChan: make(chan *Conn, maxCapacity),
|
||||
factory: factory,
|
||||
connsChan: make(chan *Conn, maxCapacity),
|
||||
connsChanMu: &sync.RWMutex{},
|
||||
factory: factory,
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +69,7 @@ func (p *Pool) Get(ctx context.Context) (conn *Conn, err error) {
|
||||
|
||||
if isExpired(conn, p.IdleTimeout) {
|
||||
// Close the expired connection immediately and look for a new
|
||||
// one. Ignoring the error here since it's not important what
|
||||
// one. Ignoring the error here since it's not important what
|
||||
// happens with it and I'd like to avoid logging
|
||||
_ = conn.Close()
|
||||
continue
|
||||
|
@ -15,12 +15,6 @@ import (
|
||||
// ConfigBase contains the necessary minimum that every Server needs to
|
||||
// be initialized.
|
||||
type ConfigBase struct {
|
||||
// Network is the network this server listens to. If empty, the server will
|
||||
// listen to all networks that are supposed to be used by the server's
|
||||
// protocol. Note, that it only makes sense for [ServerDNS],
|
||||
// [ServerDNSCrypt], and [ServerHTTPS].
|
||||
Network Network
|
||||
|
||||
// Handler is a handler that processes incoming DNS messages. If not set,
|
||||
// the default handler, which returns error response to any query, is used.
|
||||
Handler Handler
|
||||
@ -41,6 +35,12 @@ type ConfigBase struct {
|
||||
// DNS server. If nil, an appropriate default ListenConfig is used.
|
||||
ListenConfig netext.ListenConfig
|
||||
|
||||
// Network is the network this server listens to. If empty, the server will
|
||||
// listen to all networks that are supposed to be used by the server's
|
||||
// protocol. Note, that it only makes sense for [ServerDNS],
|
||||
// [ServerDNSCrypt], and [ServerHTTPS].
|
||||
Network Network
|
||||
|
||||
// Name is used for logging, and it may be used for perf counters reporting.
|
||||
Name string
|
||||
|
||||
@ -51,19 +51,6 @@ type ConfigBase struct {
|
||||
|
||||
// ServerBase implements base methods that every Server implementation uses.
|
||||
type ServerBase struct {
|
||||
// name is used for logging and it may be used for perf counters reporting.
|
||||
name string
|
||||
|
||||
// addr is the address the server listens to.
|
||||
addr string
|
||||
|
||||
// proto is the server protocol.
|
||||
proto Protocol
|
||||
|
||||
// network is the network to listen to. It only makes sense for the
|
||||
// following protocols: ProtoDNS, ProtoDNSCrypt, ProtoDoH.
|
||||
network Network
|
||||
|
||||
// handler is a handler that processes incoming DNS messages.
|
||||
handler Handler
|
||||
|
||||
@ -79,9 +66,6 @@ type ServerBase struct {
|
||||
// listenConfig is used to set tcpListener and udpListener.
|
||||
listenConfig netext.ListenConfig
|
||||
|
||||
// Server operation
|
||||
// --
|
||||
|
||||
// tcpListener is used to accept new TCP connections. It is nil for servers
|
||||
// that don't use TCP.
|
||||
tcpListener net.Listener
|
||||
@ -90,15 +74,27 @@ type ServerBase struct {
|
||||
// that don't use UDP.
|
||||
udpListener net.PacketConn
|
||||
|
||||
// Shutdown handling
|
||||
// --
|
||||
// mu protects started, tcpListener, and udpListener.
|
||||
mu *sync.RWMutex
|
||||
|
||||
// lock protects started, tcpListener and udpListener.
|
||||
lock sync.RWMutex
|
||||
started bool
|
||||
// wg tracks active workers (listeners or query processing). Shutdown
|
||||
// won't finish until there's at least one active worker.
|
||||
wg sync.WaitGroup
|
||||
wg *sync.WaitGroup
|
||||
|
||||
// name is used for logging and it may be used for perf counters reporting.
|
||||
name string
|
||||
|
||||
// addr is the address the server listens to.
|
||||
addr string
|
||||
|
||||
// network is the network to listen to. It only makes sense for the
|
||||
// following protocols: [ProtoDNS], [ProtoDNSCrypt], [ProtoDoH].
|
||||
network Network
|
||||
|
||||
// proto is the server protocol.
|
||||
proto Protocol
|
||||
|
||||
started bool
|
||||
}
|
||||
|
||||
// type check
|
||||
@ -108,15 +104,17 @@ var _ Server = (*ServerBase)(nil)
|
||||
// some of its internal properties.
|
||||
func newServerBase(proto Protocol, conf ConfigBase) (s *ServerBase) {
|
||||
s = &ServerBase{
|
||||
name: conf.Name,
|
||||
addr: conf.Addr,
|
||||
proto: proto,
|
||||
network: conf.Network,
|
||||
handler: conf.Handler,
|
||||
reqCtx: conf.RequestContext,
|
||||
metrics: conf.Metrics,
|
||||
disposer: conf.Disposer,
|
||||
listenConfig: conf.ListenConfig,
|
||||
reqCtx: conf.RequestContext,
|
||||
mu: &sync.RWMutex{},
|
||||
wg: &sync.WaitGroup{},
|
||||
name: conf.Name,
|
||||
addr: conf.Addr,
|
||||
network: conf.Network,
|
||||
proto: proto,
|
||||
}
|
||||
|
||||
if s.reqCtx == nil {
|
||||
@ -473,8 +471,8 @@ func (s *ServerBase) waitShutdown(ctx context.Context) (err error) {
|
||||
|
||||
// isStarted returns true if the server is started.
|
||||
func (s *ServerBase) isStarted() (started bool) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.started
|
||||
}
|
||||
|
@ -44,6 +44,15 @@ type ConfigDNS struct {
|
||||
// not set it defaults to DefaultWriteTimeout.
|
||||
WriteTimeout time.Duration
|
||||
|
||||
// TCPIdleTimeout is the timeout for waiting between multiple queries. If
|
||||
// not set it defaults to [DefaultTCPIdleTimeout].
|
||||
TCPIdleTimeout time.Duration
|
||||
|
||||
// MaxPipelineCount is the maximum number of simultaneously processing TCP
|
||||
// messages per one connection. If MaxPipelineEnabled is true, it must be
|
||||
// greater than zero.
|
||||
MaxPipelineCount uint
|
||||
|
||||
// UDPSize is the size of the buffers used to read incoming UDP messages.
|
||||
// If not set it defaults to [dns.MinMsgSize], 512 B.
|
||||
UDPSize int
|
||||
@ -55,15 +64,6 @@ type ConfigDNS struct {
|
||||
// MaxUDPRespSize is the maximum size of DNS response over UDP protocol.
|
||||
MaxUDPRespSize uint16
|
||||
|
||||
// TCPIdleTimeout is the timeout for waiting between multiple queries. If
|
||||
// not set it defaults to [DefaultTCPIdleTimeout].
|
||||
TCPIdleTimeout time.Duration
|
||||
|
||||
// MaxPipelineCount is the maximum number of simultaneously processing TCP
|
||||
// messages per one connection. If MaxPipelineEnabled is true, it must be
|
||||
// greater than zero.
|
||||
MaxPipelineCount uint
|
||||
|
||||
// MaxPipelineEnabled, if true, enables TCP pipeline limiting.
|
||||
MaxPipelineEnabled bool
|
||||
}
|
||||
@ -143,8 +143,8 @@ func newServerDNS(proto Protocol, conf ConfigDNS) (s *ServerDNS) {
|
||||
func (s *ServerDNS) Start(ctx context.Context) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "starting dns server: %w") }()
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.started {
|
||||
return ErrServerAlreadyStarted
|
||||
@ -243,8 +243,9 @@ func (s *ServerDNS) startServeTCP(ctx context.Context) {
|
||||
|
||||
// shutdown marks the server as stopped and closes active listeners.
|
||||
func (s *ServerDNS) shutdown() (err error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.started {
|
||||
return ErrServerNotStarted
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ func TestServerDNS_StartShutdown(t *testing.T) {
|
||||
|
||||
func TestServerDNS_integration_query(t *testing.T) {
|
||||
testCases := []struct {
|
||||
handler dnsserver.Handler
|
||||
req *dns.Msg
|
||||
wantMsg func(t *testing.T, m *dns.Msg)
|
||||
name string
|
||||
network dnsserver.Network
|
||||
req *dns.Msg
|
||||
handler dnsserver.Handler
|
||||
wantMsg func(t *testing.T, m *dns.Msg)
|
||||
wantRecordsCount int
|
||||
wantRCode int
|
||||
wantTruncated bool
|
||||
@ -419,10 +419,10 @@ func TestServerDNS_integration_tcpMsgIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
expectedError func(err error)
|
||||
name string
|
||||
buf []byte
|
||||
timeout time.Duration
|
||||
expectedError func(err error)
|
||||
}{
|
||||
{
|
||||
name: "invalid_input_timeout",
|
||||
|
@ -2,6 +2,7 @@ package dnsserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@ -17,23 +18,20 @@ import (
|
||||
type ConfigDNSCrypt struct {
|
||||
ConfigBase
|
||||
|
||||
// DNSCryptProviderName is a DNSCrypt provider name (see DNSCrypt spec).
|
||||
DNSCryptProviderName string
|
||||
|
||||
// DNSCryptResolverCert is a DNSCrypt server certificate.
|
||||
DNSCryptResolverCert *dnscrypt.Cert
|
||||
|
||||
// DNSCryptProviderName is a DNSCrypt provider name (see DNSCrypt spec).
|
||||
DNSCryptProviderName string
|
||||
}
|
||||
|
||||
// ServerDNSCrypt is a DNSCrypt server implementation.
|
||||
type ServerDNSCrypt struct {
|
||||
*ServerBase
|
||||
|
||||
dnsCryptServer *dnscrypt.Server
|
||||
|
||||
conf ConfigDNSCrypt
|
||||
|
||||
// Internal server properties
|
||||
// --
|
||||
|
||||
dnsCryptServer *dnscrypt.Server // dnscrypt server instance
|
||||
}
|
||||
|
||||
// type check
|
||||
@ -55,8 +53,8 @@ func NewServerDNSCrypt(conf ConfigDNSCrypt) (s *ServerDNSCrypt) {
|
||||
func (s *ServerDNSCrypt) Start(ctx context.Context) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "starting dnscrypt server: %w") }()
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// First, validate the protocol.
|
||||
if s.proto != ProtoDNSCrypt {
|
||||
@ -116,24 +114,33 @@ func (s *ServerDNSCrypt) Shutdown(ctx context.Context) (err error) {
|
||||
|
||||
// startServe creates listeners and starts serving DNSCrypt.
|
||||
func (s *ServerDNSCrypt) startServe(ctx context.Context) (err error) {
|
||||
var errs []error
|
||||
|
||||
if s.network.CanUDP() {
|
||||
err = s.listenUDP(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
go s.startServeUDP(ctx)
|
||||
}
|
||||
|
||||
if s.network.CanTCP() {
|
||||
err = s.listenTCP(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
go s.startServeTCP(ctx)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
s.closeListeners()
|
||||
|
||||
return fmt.Errorf("creating listeners: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
go s.startServeUDP(ctx)
|
||||
go s.startServeTCP(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -176,8 +183,9 @@ func (s *ServerDNSCrypt) startServeTCP(ctx context.Context) {
|
||||
|
||||
// shutdown marks the server as stopped and closes active listeners.
|
||||
func (s *ServerDNSCrypt) shutdown() (err error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.started {
|
||||
return ErrServerNotStarted
|
||||
}
|
||||
|
@ -14,11 +14,10 @@ import (
|
||||
|
||||
func TestServerDNSCrypt_integration_query(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
network dnsserver.Network
|
||||
req *dns.Msg
|
||||
// if nil, use DefaultTestHandler
|
||||
handler dnsserver.Handler
|
||||
req *dns.Msg
|
||||
name string
|
||||
network dnsserver.Network
|
||||
expectedRecordsCount int
|
||||
expectedRCode int
|
||||
expectedTruncated bool
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -173,7 +172,7 @@ func (s *ServerDNS) acceptTCPMsg(
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
if cs, ok := conn.(tlsConnectionStater); ok {
|
||||
ri.TLSServerName = strings.ToLower(cs.ConnectionState().ServerName)
|
||||
ri.TLSServerName = cs.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
reqCtx, reqCancel := s.requestContext()
|
||||
|
@ -58,8 +58,6 @@ var nextProtoDoH3 = []string{http3.NextProtoH3, http2.NextProtoTLS, "http/1.1"}
|
||||
// will listen to both HTTP/2 and HTTP/3, but if you set it to NetworkTCP, the
|
||||
// server will only use HTTP/2 and NetworkUDP will mean HTTP/3 only.
|
||||
type ConfigHTTPS struct {
|
||||
ConfigBase
|
||||
|
||||
// TLSConfig is the TLS configuration for HTTPS. If not set and
|
||||
// [ConfigBase.Network] is set to NetworkTCP the server will listen to
|
||||
// plain HTTP.
|
||||
@ -69,6 +67,8 @@ type ConfigHTTPS struct {
|
||||
// If it is empty, the server will return 404 for requests like that.
|
||||
NonDNSHandler http.Handler
|
||||
|
||||
ConfigBase
|
||||
|
||||
// MaxStreamsPerPeer is the maximum number of concurrent streams that a peer
|
||||
// is allowed to open.
|
||||
MaxStreamsPerPeer int
|
||||
@ -124,8 +124,8 @@ func NewServerHTTPS(conf ConfigHTTPS) (s *ServerHTTPS) {
|
||||
func (s *ServerHTTPS) Start(ctx context.Context) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "starting doh server: %w") }()
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.started {
|
||||
return ErrServerAlreadyStarted
|
||||
@ -241,8 +241,9 @@ func (s *ServerHTTPS) startH3Server(ctx context.Context) (err error) {
|
||||
|
||||
// shutdown marks the server as stopped and closes active listeners.
|
||||
func (s *ServerHTTPS) shutdown(ctx context.Context) (err error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.started {
|
||||
return ErrServerNotStarted
|
||||
}
|
||||
@ -570,7 +571,7 @@ func addRequestInfo(parent context.Context, r *http.Request) (ctx context.Contex
|
||||
}
|
||||
|
||||
if r.TLS != nil {
|
||||
ri.TLSServerName = strings.ToLower(r.TLS.ServerName)
|
||||
ri.TLSServerName = r.TLS.ServerName
|
||||
}
|
||||
|
||||
if username, pass, ok := r.BasicAuth(); ok {
|
||||
|
@ -524,7 +524,7 @@ func createDoH3Client(
|
||||
) (c quic.EarlyConnection, e error) {
|
||||
return quic.DialAddrEarly(ctx, httpsAddr.String(), tlsCfg, cfg)
|
||||
},
|
||||
QuicConfig: quicConfig,
|
||||
QUICConfig: quicConfig,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -141,8 +140,8 @@ var _ Server = (*ServerQUIC)(nil)
|
||||
func (s *ServerQUIC) Start(ctx context.Context) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "starting doq server: %w") }()
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.conf.TLSConfig == nil {
|
||||
return errors.Error("tls config is required")
|
||||
@ -202,8 +201,9 @@ func (s *ServerQUIC) Shutdown(ctx context.Context) (err error) {
|
||||
|
||||
// shutdown marks the server as stopped and closes active listeners.
|
||||
func (s *ServerQUIC) shutdown() (err error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.started {
|
||||
return ErrServerNotStarted
|
||||
}
|
||||
@ -371,7 +371,7 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn quic.Connection) (e
|
||||
|
||||
ri := &RequestInfo{
|
||||
StartTime: time.Now(),
|
||||
TLSServerName: strings.ToLower(conn.ConnectionState().TLS.ServerName),
|
||||
TLSServerName: conn.ConnectionState().TLS.ServerName,
|
||||
}
|
||||
|
||||
reqCtx, reqCancel := s.requestContext()
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
// ConfigTLS is a struct that needs to be passed to NewServerTLS to
|
||||
// initialize a new ServerTLS instance.
|
||||
type ConfigTLS struct {
|
||||
ConfigDNS
|
||||
|
||||
// TLSConfig is the TLS configuration for TLS.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
ConfigDNS
|
||||
}
|
||||
|
||||
// ServerTLS implements a DNS-over-TLS server.
|
||||
@ -43,8 +43,8 @@ func NewServerTLS(conf ConfigTLS) (s *ServerTLS) {
|
||||
func (s *ServerTLS) Start(ctx context.Context) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "starting dot server: %w") }()
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.conf.TLSConfig == nil {
|
||||
return errors.Error("tls config is required")
|
||||
|
@ -48,10 +48,10 @@ func TestServerTLS_integration_msgIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
expectedError func(t *testing.T, err error)
|
||||
name string
|
||||
buf []byte
|
||||
timeout time.Duration
|
||||
expectedError func(t *testing.T, err error)
|
||||
}{
|
||||
{
|
||||
name: "invalid_input_timeout",
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
|
||||
@ -48,6 +49,9 @@ type Config struct {
|
||||
// of DNS responses for later reuse.
|
||||
Cloner *dnsmsg.Cloner
|
||||
|
||||
// CacheManager is the global cache manager. CacheManager must not be nil.
|
||||
CacheManager agdcache.Manager
|
||||
|
||||
// ControlConf is the configuration of socket options.
|
||||
ControlConf *netext.ControlConfig
|
||||
|
||||
@ -55,6 +59,10 @@ type Config struct {
|
||||
// active stream-connections.
|
||||
ConnLimiter *connlimiter.Limiter
|
||||
|
||||
// HumanIDParser is used to normalize and parse human-readable device
|
||||
// identifiers.
|
||||
HumanIDParser *agd.HumanIDParser
|
||||
|
||||
// AccessManager is used to block requests.
|
||||
AccessManager access.Interface
|
||||
|
||||
@ -168,6 +176,7 @@ func New(c *Config) (svc *Service, err error) {
|
||||
// groups.
|
||||
preUps := preupstream.New(&preupstream.Config{
|
||||
Cloner: c.Cloner,
|
||||
CacheManager: c.CacheManager,
|
||||
DB: c.DNSDB,
|
||||
GeoIP: c.GeoIP,
|
||||
CacheSize: c.CacheSize,
|
||||
@ -549,6 +558,7 @@ func newDeviceSetter(c *Config, g *agd.ServerGroup, s *agd.Server) (ds deviceset
|
||||
|
||||
return devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: c.ProfileDB,
|
||||
HumanIDParser: c.HumanIDParser,
|
||||
Server: s,
|
||||
DeviceIDWildcards: g.TLS.DeviceIDWildcards,
|
||||
})
|
||||
|
@ -79,6 +79,22 @@ func newTestService(
|
||||
}
|
||||
|
||||
db := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
id agd.DeviceID,
|
||||
@ -87,12 +103,15 @@ func newTestService(
|
||||
|
||||
return prof, dev, nil
|
||||
},
|
||||
OnProfileByDedicatedIP: func(
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
|
@ -2,6 +2,7 @@ package devicesetter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
@ -11,27 +12,35 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/optlog"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// findDevice returns a valid, non-deleted profile and device or nils. err is
|
||||
// only not nil when it's not [profiledb.ErrDeviceNotFound].
|
||||
// only not nil when it's not a not-found error from the profile database.
|
||||
func (ds *Default) findDevice(
|
||||
ctx context.Context,
|
||||
laddr netip.AddrPort,
|
||||
remoteIP netip.Addr,
|
||||
id agd.DeviceID,
|
||||
extID *extHumanID,
|
||||
) (prof *agd.Profile, dev *agd.Device, err error) {
|
||||
// TODO(a.garipov): Add package optslog for optimized slog logging?
|
||||
optlog.Debug3("devicesetter: got dev id %q, raddr %s, and laddr %s", id, remoteIP, laddr)
|
||||
optlog.Debug4(
|
||||
"devicesetter: got dev id %q, ext id present %t, raddr %s, and laddr %s",
|
||||
id,
|
||||
extID != nil,
|
||||
remoteIP,
|
||||
laddr,
|
||||
)
|
||||
|
||||
prof, dev, byWhat, err := ds.deviceFromDB(ctx, laddr, remoteIP, id)
|
||||
prof, dev, byWhat, err := ds.deviceFromDB(ctx, laddr, remoteIP, id, extID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, profiledb.ErrDeviceNotFound) {
|
||||
// Don't wrap the error, because it's likely [errUnknownDedicated].
|
||||
return nil, nil, err
|
||||
}
|
||||
// Don't wrap the error, because it's likely [ErrUnknownDedicated].
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
optlog.Debug1("devicesetter: profile or device not found: %s", err)
|
||||
if prof == nil || dev == nil {
|
||||
log.Debug("devicesetter: profile or device not found")
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
@ -49,37 +58,121 @@ func (ds *Default) findDevice(
|
||||
|
||||
// Constants for the parameter by which a device has been found.
|
||||
const (
|
||||
byDeviceID = "device id"
|
||||
byDedicatedIP = "dedicated ip"
|
||||
byDeviceID = "device id"
|
||||
byHumanID = "human id"
|
||||
byLinkedIP = "linked ip"
|
||||
)
|
||||
|
||||
// deviceFromDB queries the profile DB for the profile and device by the client
|
||||
// data. It also returns the description of how it has found them.
|
||||
// data. It also returns the description of how it has found them. err is only
|
||||
// not nil when it's not a not-found error from the profile database.
|
||||
func (ds *Default) deviceFromDB(
|
||||
ctx context.Context,
|
||||
laddr netip.AddrPort,
|
||||
remoteIP netip.Addr,
|
||||
id agd.DeviceID,
|
||||
extID *extHumanID,
|
||||
) (prof *agd.Profile, dev *agd.Device, byWhat string, err error) {
|
||||
if id != "" {
|
||||
prof, dev, err = ds.db.ProfileByDeviceID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
// Don't wrap the error, as this is a hot path, and an error other
|
||||
// than a not-found one is unlikely.
|
||||
return nil, nil, "", removeNotFound(err)
|
||||
}
|
||||
|
||||
return prof, dev, byDeviceID, nil
|
||||
}
|
||||
|
||||
if extID != nil {
|
||||
prof, dev, err = ds.deviceByExtID(ctx, extID)
|
||||
if err == nil {
|
||||
return prof, dev, byHumanID, nil
|
||||
}
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
if ds.srv.Protocol == agd.ProtoDNS {
|
||||
return ds.deviceByAddrs(ctx, laddr, remoteIP)
|
||||
}
|
||||
|
||||
return nil, nil, "", profiledb.ErrDeviceNotFound
|
||||
return nil, nil, "", nil
|
||||
}
|
||||
|
||||
// removeNotFound returns nil if err is one of the not-found errors from the
|
||||
// profile database.
|
||||
func removeNotFound(err error) (res error) {
|
||||
if isProfileDBNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// deviceByExtID queries the profile DB for the profile and device by the
|
||||
// extended human-readable device identifier. extID must not be nil. err is
|
||||
// only not nil when it's not a not-found error from the profile database.
|
||||
func (ds *Default) deviceByExtID(
|
||||
ctx context.Context,
|
||||
extID *extHumanID,
|
||||
) (prof *agd.Profile, dev *agd.Device, err error) {
|
||||
profID, humanID := extID.ProfileID, extID.HumanID
|
||||
prof, dev, err = ds.db.ProfileByHumanID(ctx, profID, agd.HumanIDToLower(humanID))
|
||||
switch {
|
||||
case err == nil:
|
||||
return prof, dev, nil
|
||||
case errorIsOpt(err, profiledb.ErrProfileNotFound):
|
||||
// No such profile, return immediately.
|
||||
return nil, nil, nil
|
||||
case errorIsOpt(err, profiledb.ErrDeviceNotFound):
|
||||
// Go on and try to create.
|
||||
default:
|
||||
// Unlikely, so wrap.
|
||||
return nil, nil, fmt.Errorf("querying profile db by human id: %w", err)
|
||||
}
|
||||
|
||||
prof, dev, err = ds.db.CreateAutoDevice(ctx, profID, humanID, extID.DeviceType)
|
||||
switch {
|
||||
case err == nil:
|
||||
return prof, dev, nil
|
||||
case errorIsOpt(err, profiledb.ErrProfileNotFound):
|
||||
// A rare case where a profile has been deleted between the check and
|
||||
// the creation.
|
||||
return nil, nil, nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("creating autodevice: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// isProfileDBNotFound returns true if err is one of the not-found errors from
|
||||
// the profile database.
|
||||
func isProfileDBNotFound(err error) (ok bool) {
|
||||
return errorIsOpt(err, profiledb.ErrDeviceNotFound) ||
|
||||
errorIsOpt(err, profiledb.ErrProfileNotFound)
|
||||
}
|
||||
|
||||
// errorIsOpt is an optimized version of [errors.Is] that is faster in cases
|
||||
// where the error is returned directly or with one level of [fmt.Errorf].
|
||||
// target must be comparable.
|
||||
func errorIsOpt(err, target error) (ok bool) {
|
||||
if err == target {
|
||||
return true
|
||||
}
|
||||
|
||||
unwrapped := errors.Unwrap(err)
|
||||
if unwrapped == target {
|
||||
return true
|
||||
}
|
||||
|
||||
return unwrapped != nil && errors.Is(unwrapped, target)
|
||||
}
|
||||
|
||||
// deviceByAddrs finds the profile and the device by the remote and local
|
||||
// addresses depending on the data and the server settings.
|
||||
// addresses depending on the data and the server settings. err is only not nil
|
||||
// when it's not a not-found error from the profile database.
|
||||
func (ds *Default) deviceByAddrs(
|
||||
ctx context.Context,
|
||||
laddr netip.AddrPort,
|
||||
@ -88,6 +181,8 @@ func (ds *Default) deviceByAddrs(
|
||||
if ds.srv.BindsToInterfaces() && !ds.srv.HasAddr(laddr) {
|
||||
prof, dev, err = ds.deviceByLocalAddr(ctx, laddr.Addr())
|
||||
if err != nil {
|
||||
// Don't wrap the error, as this is a hot path, and an error about
|
||||
// an unknown dedicated address might be common here.
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
@ -95,30 +190,36 @@ func (ds *Default) deviceByAddrs(
|
||||
}
|
||||
|
||||
if !ds.srv.LinkedIPEnabled {
|
||||
return nil, nil, "", profiledb.ErrDeviceNotFound
|
||||
return nil, nil, "", nil
|
||||
}
|
||||
|
||||
prof, dev, err = ds.db.ProfileByLinkedIP(ctx, remoteIP)
|
||||
if err != nil {
|
||||
return nil, nil, "", err
|
||||
// Don't wrap the error, as this is a hot path, and an error other than
|
||||
// a not-found one is unlikely.
|
||||
return nil, nil, "", removeNotFound(err)
|
||||
}
|
||||
|
||||
return prof, dev, byLinkedIP, nil
|
||||
}
|
||||
|
||||
// deviceByLocalAddr finds the profile and the device by the local address.
|
||||
// deviceByLocalAddr replaces profiledb's not-found errors with
|
||||
// [ErrUnknownDedicated].
|
||||
func (ds *Default) deviceByLocalAddr(
|
||||
ctx context.Context,
|
||||
localIP netip.Addr,
|
||||
) (prof *agd.Profile, dev *agd.Device, err error) {
|
||||
prof, dev, err = ds.db.ProfileByDedicatedIP(ctx, localIP)
|
||||
if err != nil {
|
||||
if errors.Is(err, profiledb.ErrDeviceNotFound) {
|
||||
if isProfileDBNotFound(err) {
|
||||
optlog.Debug1("devicesetter: unknown dedicated ip for server %s; dropping", ds.srv.Name)
|
||||
|
||||
err = ErrUnknownDedicated
|
||||
}
|
||||
|
||||
// Don't wrap the error, as this is a hot path, and a not-found error is
|
||||
// common here.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package devicesetter_test
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/devicesetter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -44,7 +46,7 @@ func TestDefault_SetDevice_plainAddrs(t *testing.T) {
|
||||
wantDev: devNormal,
|
||||
wantErrMsg: "",
|
||||
laddr: dnssvctest.ServerAddrPort,
|
||||
raddr: linkedAddrPort,
|
||||
raddr: dnssvctest.LinkedAddrPort,
|
||||
name: "linked_ip",
|
||||
}, {
|
||||
req: reqNormal,
|
||||
@ -53,7 +55,7 @@ func TestDefault_SetDevice_plainAddrs(t *testing.T) {
|
||||
wantDev: nil,
|
||||
wantErrMsg: "",
|
||||
laddr: dnssvctest.ServerAddrPort,
|
||||
raddr: linkedAddrPort,
|
||||
raddr: dnssvctest.LinkedAddrPort,
|
||||
name: "linked_ip_not_supported",
|
||||
}, {
|
||||
req: reqNormal,
|
||||
@ -61,7 +63,7 @@ func TestDefault_SetDevice_plainAddrs(t *testing.T) {
|
||||
wantProf: profNormal,
|
||||
wantDev: devNormal,
|
||||
wantErrMsg: "",
|
||||
laddr: dedicatedAddrPort,
|
||||
laddr: dnssvctest.DedicatedAddrPort,
|
||||
raddr: dnssvctest.ClientAddrPort,
|
||||
name: "dedicated",
|
||||
}, {
|
||||
@ -79,7 +81,7 @@ func TestDefault_SetDevice_plainAddrs(t *testing.T) {
|
||||
wantProf: nil,
|
||||
wantDev: nil,
|
||||
wantErrMsg: "",
|
||||
laddr: dedicatedAddrPort,
|
||||
laddr: dnssvctest.DedicatedAddrPort,
|
||||
raddr: dnssvctest.ClientAddrPort,
|
||||
name: "dedicated_not_supported",
|
||||
}}
|
||||
@ -89,18 +91,38 @@ func TestDefault_SetDevice_plainAddrs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnProfileByDedicatedIP: newOnProfileByDedicatedIP(dedicatedAddr),
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: newOnProfileByDedicatedIP(dnssvctest.DedicatedAddr),
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
_ agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
OnProfileByLinkedIP: newOnProfileByLinkedIP(linkedAddr),
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: newOnProfileByLinkedIP(dnssvctest.LinkedAddr),
|
||||
}
|
||||
|
||||
pf := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: tc.srv,
|
||||
DeviceIDWildcards: nil,
|
||||
})
|
||||
@ -171,13 +193,32 @@ func TestDefault_SetDevice_plainEDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: newOnProfileByDeviceID(dnssvctest.DeviceID),
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -188,6 +229,7 @@ func TestDefault_SetDevice_plainEDNS(t *testing.T) {
|
||||
|
||||
pf := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: tc.srv,
|
||||
DeviceIDWildcards: nil,
|
||||
})
|
||||
@ -209,18 +251,37 @@ func TestDefault_SetDevice_deleted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
_ agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -230,11 +291,12 @@ func TestDefault_SetDevice_deleted(t *testing.T) {
|
||||
}
|
||||
|
||||
pf := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
Server: srvPlainWithLinkedIP,
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: srvPlainWithLinkedIP,
|
||||
})
|
||||
|
||||
raddr := linkedAddrPort
|
||||
raddr := dnssvctest.LinkedAddrPort
|
||||
msgCons := agdtest.NewConstructor()
|
||||
ri := &agd.RequestInfo{
|
||||
Messages: msgCons,
|
||||
@ -249,3 +311,71 @@ func TestDefault_SetDevice_deleted(t *testing.T) {
|
||||
assert.Nil(t, ri.Device)
|
||||
assert.Same(t, msgCons, ri.Messages)
|
||||
}
|
||||
|
||||
func TestDefault_SetDevice_byHumanID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Use uppercase versions to make sure that the device setter recognizes the
|
||||
// device-type and profile data regardless of the case.
|
||||
extIDStr := "OTR-" + strings.ToUpper(dnssvctest.ProfileIDStr) + "-" + dnssvctest.HumanIDStr + "-!!!"
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return profNormal, devAuto, nil
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
devID agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return nil, nil, profiledb.ErrDeviceNotFound
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: srvDoT,
|
||||
DeviceIDWildcards: []string{dnssvctest.DomainForDevices},
|
||||
})
|
||||
|
||||
ctx := dnsserver.ContextWithRequestInfo(context.Background(), &dnsserver.RequestInfo{
|
||||
TLSServerName: extIDStr + "." + dnssvctest.DomainForDevices,
|
||||
})
|
||||
ri := &agd.RequestInfo{
|
||||
RemoteIP: dnssvctest.ClientAddr,
|
||||
}
|
||||
|
||||
err := df.SetDevice(ctx, reqNormal, ri, dnssvctest.ServerAddrPort)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, profNormal, ri.Profile)
|
||||
assert.Equal(t, devAuto, ri.Device)
|
||||
}
|
||||
|
@ -28,93 +28,102 @@ func supportsDeviceID(p agd.Protocol) (ok bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// deviceID extracts the device ID from the given parameters. If the device ID
|
||||
// is not found, it returns an empty ID and nil, as the lookup could also be
|
||||
// deviceData extracts the device data from the given parameters. If the device
|
||||
// data are not found, all results will be empty, as the lookup could also be
|
||||
// done later by remote and local addresses.
|
||||
func (ds *Default) deviceID(
|
||||
func (ds *Default) deviceData(
|
||||
req *dns.Msg,
|
||||
srvReqInfo *dnsserver.RequestInfo,
|
||||
) (id agd.DeviceID, err error) {
|
||||
) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
if ds.srv.Protocol.IsStdEncrypted() {
|
||||
return ds.deviceIDFromSrvReqInfo(srvReqInfo)
|
||||
return ds.deviceDataFromSrvReqInfo(srvReqInfo)
|
||||
}
|
||||
|
||||
return deviceIDFromEDNS(req)
|
||||
id, err = deviceIDFromEDNS(req)
|
||||
|
||||
return id, nil, err
|
||||
}
|
||||
|
||||
// deviceIDFromSrvReqInfo extracts device ID from the arguments. The ID is
|
||||
// extracted in the following manner:
|
||||
// deviceDataFromSrvReqInfo extracts device data from the arguments. The data
|
||||
// are extracted in the following manner:
|
||||
//
|
||||
// 1. If applicable, the ID is firstly extracted from the DoH information, such
|
||||
// 1. If applicable, the data is first extracted from the DoH information, such
|
||||
// as the userinfo or URL path.
|
||||
//
|
||||
// 2. Secondly, the TLS Server Name is inspected using ds's device ID
|
||||
// wildcards.
|
||||
// 2. Secondly, the TLS Server Name is inspected using the device-ID wildcards
|
||||
// configured for the device setter.
|
||||
//
|
||||
// Any returned errors will have the underlying type of [*deviceIDError].
|
||||
func (ds *Default) deviceIDFromSrvReqInfo(
|
||||
func (ds *Default) deviceDataFromSrvReqInfo(
|
||||
srvReqInfo *dnsserver.RequestInfo,
|
||||
) (id agd.DeviceID, err error) {
|
||||
) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
if ds.srv.Protocol == agd.ProtoDoH {
|
||||
id, err = deviceIDForDoH(srvReqInfo)
|
||||
if id != "" || err != nil {
|
||||
id, extID, err = ds.deviceDataForDoH(srvReqInfo)
|
||||
if id != "" || extID != nil || err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return id, err
|
||||
return id, extID, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(ds.wildcardDomains) == 0 {
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
id, err = ds.deviceIDFromCliSrvName(srvReqInfo.TLSServerName)
|
||||
id, extID, err = ds.deviceDataFromCliSrvName(srvReqInfo.TLSServerName)
|
||||
if err != nil {
|
||||
return "", newDeviceIDError(err, "tls server name")
|
||||
return "", nil, newDeviceDataError(err, "tls server name")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
return id, extID, nil
|
||||
}
|
||||
|
||||
// deviceIDForDoH extracts a device ID from the DoH request information. The ID
|
||||
// is extracted firstly from the request's userinfo, if any, and then from the
|
||||
// URL path. srvReqInfo must not be nil.
|
||||
// deviceDataForDoH extracts the device data from the DoH request information.
|
||||
// The data are extracted first from the request's userinfo, if any, and then
|
||||
// from the URL path. srvReqInfo must not be nil.
|
||||
//
|
||||
// Any returned errors will have the underlying type of [*deviceIDError].
|
||||
func deviceIDForDoH(srvReqInfo *dnsserver.RequestInfo) (id agd.DeviceID, err error) {
|
||||
// Any returned errors will have the underlying type of [*deviceDataError].
|
||||
func (ds *Default) deviceDataForDoH(
|
||||
srvReqInfo *dnsserver.RequestInfo,
|
||||
) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
if userinfo := srvReqInfo.Userinfo; userinfo != nil {
|
||||
// Don't scan the userinfo for human-readable IDs, since they're not
|
||||
// supported there.
|
||||
id, err = agd.NewDeviceID(userinfo.Username())
|
||||
if err != nil {
|
||||
return "", newDeviceIDError(err, "basic auth")
|
||||
return "", nil, newDeviceDataError(err, "basic auth")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
return id, nil, nil
|
||||
}
|
||||
|
||||
id, err = deviceIDFromDoHURL(srvReqInfo.URL)
|
||||
id, extID, err = ds.deviceDataFromDoHURL(srvReqInfo.URL)
|
||||
if err != nil {
|
||||
return "", newDeviceIDError(err, "http url path")
|
||||
return "", nil, newDeviceDataError(err, "http url path")
|
||||
}
|
||||
|
||||
// In case of empty device ID, we will continue the lookup process.
|
||||
return id, nil
|
||||
// In case of empty device data, will continue the lookup process.
|
||||
return id, extID, nil
|
||||
}
|
||||
|
||||
// deviceIDFromDoHURL extracts the device ID from the path of the DoH request.
|
||||
func deviceIDFromDoHURL(u *url.URL) (id agd.DeviceID, err error) {
|
||||
// deviceDataFromDoHURL extracts the device data from the path of the DoH
|
||||
// request.
|
||||
func (ds *Default) deviceDataFromDoHURL(
|
||||
u *url.URL,
|
||||
) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
parts, err := pathParts(u.Path)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if len(parts) == 2 {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return agd.NewDeviceID(parts[1])
|
||||
return ds.parseDeviceData(parts[1])
|
||||
}
|
||||
|
||||
// pathParts guarantees that if there aren't two parts, there's only one,
|
||||
// and it is a valid DNS path.
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
// pathParts splits and validates urlPath. If err is nil, parts has either one
|
||||
@ -142,35 +151,36 @@ func pathParts(urlPath string) (parts []string, err error) {
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// deviceIDFromCliSrvName extracts and validates a device ID. cliSrvName is the
|
||||
// server name as sent by the client.
|
||||
func (ds *Default) deviceIDFromCliSrvName(cliSrvName string) (id agd.DeviceID, err error) {
|
||||
// deviceDataFromCliSrvName extracts and validates device data. cliSrvName is
|
||||
// the server name as sent by the client.
|
||||
func (ds *Default) deviceDataFromCliSrvName(
|
||||
cliSrvName string,
|
||||
) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
if cliSrvName == "" {
|
||||
// No server name in ClientHello, so the request is probably made on the
|
||||
// IP address.
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
matchedDomain := matchDomain(cliSrvName, ds.wildcardDomains)
|
||||
if matchedDomain == "" {
|
||||
return "", nil
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
optlog.Debug2("devicesetter: device id: matched %q from %q", matchedDomain, ds.wildcardDomains)
|
||||
|
||||
idStr := cliSrvName[:len(cliSrvName)-len(matchedDomain)-1]
|
||||
id, err = agd.NewDeviceID(idStr)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return ds.parseDeviceData(idStr)
|
||||
}
|
||||
|
||||
// matchDomain searches among domains for one the subdomain of which is sub. If
|
||||
// there is no such domain, matchDomain returns an empty string.
|
||||
func matchDomain(sub string, domains []string) (matchedDomain string) {
|
||||
// TODO(a.garipov): Remove once netutil learns how to match domains in a
|
||||
// case-insensitive way.
|
||||
sub = strings.ToLower(sub)
|
||||
for _, domain := range domains {
|
||||
if netutil.IsImmediateSubdomain(sub, domain) {
|
||||
return domain
|
||||
@ -195,7 +205,7 @@ const DnsmasqCPEIDOption uint16 = 65074
|
||||
// dig @94.140.14.49 'edns-id.example' IN A\
|
||||
// +ednsopt=65074:"$( printf 'abcd1234' | od -A n -t x1 | tr -d ' ' )"
|
||||
//
|
||||
// Any returned errors will have the underlying type of [*deviceIDError].
|
||||
// Any returned errors will have the underlying type of [*deviceDataError].
|
||||
func deviceIDFromEDNS(req *dns.Msg) (id agd.DeviceID, err error) {
|
||||
option := req.IsEdns0()
|
||||
if option == nil {
|
||||
@ -227,7 +237,7 @@ func deviceIDFromENDSOPT(opt dns.EDNS0) (id agd.DeviceID, err error) {
|
||||
|
||||
id, err = agd.NewDeviceID(string(o.Data))
|
||||
if err != nil {
|
||||
return "", newDeviceIDError(err, "edns option")
|
||||
return "", newDeviceDataError(err, "edns option")
|
||||
}
|
||||
|
||||
return id, nil
|
@ -89,12 +89,22 @@ func TestDefault_SetDevice_DoHAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
devID agd.DeviceID,
|
||||
@ -105,6 +115,15 @@ func TestDefault_SetDevice_DoHAuth(t *testing.T) {
|
||||
|
||||
return nil, nil, profiledb.ErrDeviceNotFound
|
||||
},
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -115,6 +134,7 @@ func TestDefault_SetDevice_DoHAuth(t *testing.T) {
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: srvDoH,
|
||||
DeviceIDWildcards: []string{},
|
||||
})
|
||||
@ -205,18 +225,37 @@ func TestDefault_SetDevice_DoHAuthOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
devID agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
return profNormal, tc.profDBDev, nil
|
||||
},
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -227,6 +266,7 @@ func TestDefault_SetDevice_DoHAuthOnly(t *testing.T) {
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: tc.srv,
|
||||
DeviceIDWildcards: []string{dnssvctest.DeviceIDWildcard},
|
||||
})
|
||||
@ -311,16 +351,37 @@ func TestDefault_SetDevice_DoH(t *testing.T) {
|
||||
},
|
||||
wantErrMsg: `http url path device id check: bad path "/other": not a dns path`,
|
||||
name: "not_dns_path",
|
||||
}, {
|
||||
wantProf: profNormal,
|
||||
wantDev: devAuto,
|
||||
reqURL: &url.URL{
|
||||
Path: path.Join(dnsserver.PathDoH, dnssvctest.HumanIDPath),
|
||||
},
|
||||
wantErrMsg: "",
|
||||
name: "human_id_path_match",
|
||||
}}
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: newOnProfileByDeviceID(dnssvctest.DeviceID),
|
||||
|
||||
OnProfileByHumanID: newOnProfileByHumanID(dnssvctest.ProfileID, dnssvctest.HumanIDLower),
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -335,6 +396,7 @@ func TestDefault_SetDevice_DoH(t *testing.T) {
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: srvDoH,
|
||||
DeviceIDWildcards: []string{},
|
||||
})
|
||||
@ -401,16 +463,36 @@ func TestDefault_SetDevice_stdEncrypted(t *testing.T) {
|
||||
cliSrvName: "!!!.d.dns.example",
|
||||
name: "bad_id",
|
||||
wildcards: []string{dnssvctest.DeviceIDWildcard},
|
||||
}, {
|
||||
wantProf: profNormal,
|
||||
wantDev: devAuto,
|
||||
wantErrMsg: "",
|
||||
cliSrvName: dnssvctest.HumanIDSrvName,
|
||||
name: "human_id_match",
|
||||
wildcards: []string{dnssvctest.DeviceIDWildcard},
|
||||
}}
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: newOnProfileByDeviceID(dnssvctest.DeviceID),
|
||||
|
||||
OnProfileByHumanID: newOnProfileByHumanID(dnssvctest.ProfileID, dnssvctest.HumanIDLower),
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
@ -446,6 +528,7 @@ func TestDefault_SetDevice_stdEncrypted(t *testing.T) {
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: sd.srv,
|
||||
DeviceIDWildcards: tc.wildcards,
|
||||
})
|
@ -44,10 +44,10 @@ var _ Interface = Empty{}
|
||||
// SetDevice implements the [Interface] interface for Empty. It does nothing
|
||||
// and returns nil.
|
||||
func (Empty) SetDevice(
|
||||
ctx context.Context,
|
||||
req *dns.Msg,
|
||||
ri *agd.RequestInfo,
|
||||
laddr netip.AddrPort,
|
||||
_ context.Context,
|
||||
_ *dns.Msg,
|
||||
_ *agd.RequestInfo,
|
||||
_ netip.AddrPort,
|
||||
) (err error) {
|
||||
return nil
|
||||
}
|
||||
@ -57,6 +57,10 @@ type Config struct {
|
||||
// ProfileDB is used to find the profiles. It must not be nil.
|
||||
ProfileDB profiledb.Interface
|
||||
|
||||
// HumanIDParser is used to normalize and parse human-readable device
|
||||
// identifiers.
|
||||
HumanIDParser *agd.HumanIDParser
|
||||
|
||||
// Server contains the data of the server for which the profiles are found.
|
||||
// It must not be nil.
|
||||
Server *agd.Server
|
||||
@ -69,6 +73,7 @@ type Config struct {
|
||||
// Default is the default profile setter.
|
||||
type Default struct {
|
||||
db profiledb.Interface
|
||||
humanIDParser *agd.HumanIDParser
|
||||
srv *agd.Server
|
||||
wildcardDomains []string
|
||||
}
|
||||
@ -83,6 +88,7 @@ func NewDefault(c *Config) (ds *Default) {
|
||||
|
||||
return &Default{
|
||||
db: c.ProfileDB,
|
||||
humanIDParser: c.HumanIDParser,
|
||||
srv: c.Server,
|
||||
wildcardDomains: wildcardDomains,
|
||||
}
|
||||
@ -104,13 +110,13 @@ func (ds *Default) SetDevice(
|
||||
}
|
||||
|
||||
srvReqInfo := dnsserver.MustRequestInfoFromContext(ctx)
|
||||
id, err := ds.deviceID(req, srvReqInfo)
|
||||
id, extID, err := ds.deviceData(req, srvReqInfo)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
prof, dev, err := ds.findDevice(ctx, laddr, ri.RemoteIP, id)
|
||||
prof, dev, err := ds.findDevice(ctx, laddr, ri.RemoteIP, id, extID)
|
||||
if err != nil {
|
||||
// Likely [errUnknownDedicated].
|
||||
return fmt.Errorf("setting profile: %w", err)
|
||||
|
@ -107,17 +107,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// Common addresses for tests.
|
||||
//
|
||||
// TODO(a.garipov): Move more common variables and constants to dnssvctest.
|
||||
var (
|
||||
linkedAddr = netip.MustParseAddr("192.0.2.1")
|
||||
dedicatedAddr = netip.MustParseAddr("192.0.2.2")
|
||||
|
||||
linkedAddrPort = netip.AddrPortFrom(linkedAddr, 12345)
|
||||
dedicatedAddrPort = netip.AddrPortFrom(dedicatedAddr, 53)
|
||||
)
|
||||
|
||||
// Common profiles and devices for tests.
|
||||
var (
|
||||
profNormal = &agd.Profile{
|
||||
@ -139,7 +128,15 @@ var (
|
||||
Enabled: false,
|
||||
},
|
||||
ID: dnssvctest.DeviceID,
|
||||
LinkedIP: linkedAddr,
|
||||
LinkedIP: dnssvctest.LinkedAddr,
|
||||
}
|
||||
|
||||
devAuto = &agd.Device{
|
||||
Auth: &agd.AuthSettings{
|
||||
Enabled: false,
|
||||
},
|
||||
ID: dnssvctest.DeviceID,
|
||||
HumanIDLower: dnssvctest.HumanIDLower,
|
||||
}
|
||||
)
|
||||
|
||||
@ -189,6 +186,32 @@ func newOnProfileByDeviceID(
|
||||
}
|
||||
}
|
||||
|
||||
// newOnProfileByHumanID returns a function with the type of
|
||||
// [agdtest.ProfileDB.OnProfileByHumanID] that returns p and d only when id and
|
||||
// humanID are equal to the given one.
|
||||
func newOnProfileByHumanID(
|
||||
wantProfID agd.ProfileID,
|
||||
wantHumanID agd.HumanIDLower,
|
||||
) (
|
||||
f func(
|
||||
_ context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error),
|
||||
) {
|
||||
return func(
|
||||
_ context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
if id == wantProfID && humanID == wantHumanID {
|
||||
return profNormal, devAuto, nil
|
||||
}
|
||||
|
||||
return nil, nil, profiledb.ErrDeviceNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// newOnProfileByLinkedIP returns a function with the type of
|
||||
// [agdtest.ProfileDB.OnProfileByLinkedIP] that returns p and d only when
|
||||
// remoteIP is equal to the given one.
|
||||
@ -208,6 +231,7 @@ func TestDefault_SetDevice_dnscrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: &agd.Server{
|
||||
Protocol: agd.ProtoDNSCrypt,
|
||||
},
|
||||
|
@ -14,38 +14,38 @@ const (
|
||||
ErrUnknownDedicated errors.Error = "unknown dedicated ip"
|
||||
)
|
||||
|
||||
// deviceIDError is an error about bad device ID or other issues found during
|
||||
// device ID checking.
|
||||
type deviceIDError struct {
|
||||
// deviceDataError is an error about bad device data or other issues found
|
||||
// during device data checking.
|
||||
type deviceDataError struct {
|
||||
err error
|
||||
typ string
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ error = (*deviceIDError)(nil)
|
||||
var _ error = (*deviceDataError)(nil)
|
||||
|
||||
// newDeviceIDError is a helper constructor for device-ID errors.
|
||||
func newDeviceIDError(orig error, typ string) (err error) {
|
||||
return &deviceIDError{
|
||||
// newDeviceDataError is a helper constructor for device-data errors.
|
||||
func newDeviceDataError(orig error, typ string) (err error) {
|
||||
return &deviceDataError{
|
||||
err: orig,
|
||||
typ: typ,
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface for *deviceIDError.
|
||||
func (err *deviceIDError) Error() (msg string) {
|
||||
// Error implements the error interface for *deviceDataError.
|
||||
func (err *deviceDataError) Error() (msg string) {
|
||||
return fmt.Sprintf("%s device id check: %s", err.typ, err.err)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ errors.Wrapper = (*deviceIDError)(nil)
|
||||
var _ errors.Wrapper = (*deviceDataError)(nil)
|
||||
|
||||
// Unwrap implements the [errors.Wrapper] interface for *deviceIDError.
|
||||
func (err *deviceIDError) Unwrap() (unwrapped error) { return err.err }
|
||||
// Unwrap implements the [errors.Wrapper] interface for *deviceDataError.
|
||||
func (err *deviceDataError) Unwrap() (unwrapped error) { return err.err }
|
||||
|
||||
// type check
|
||||
var _ errcoll.SentryReportableError = (*deviceIDError)(nil)
|
||||
var _ errcoll.SentryReportableError = (*deviceDataError)(nil)
|
||||
|
||||
// IsSentryReportable implements the [errcoll.SentryReportableError] interface
|
||||
// for *deviceIDError.
|
||||
func (*deviceIDError) IsSentryReportable() (ok bool) { return false }
|
||||
// for *deviceDataError.
|
||||
func (*deviceDataError) IsSentryReportable() (ok bool) { return false }
|
||||
|
86
internal/dnssvc/internal/devicesetter/humanid.go
Normal file
86
internal/dnssvc/internal/devicesetter/humanid.go
Normal file
@ -0,0 +1,86 @@
|
||||
package devicesetter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// extHumanID contains the data that can be parsed from an extended
|
||||
// human-readable device identifier.
|
||||
//
|
||||
// TODO(a.garipov): Optimize its allocation and freeing.
|
||||
type extHumanID struct {
|
||||
HumanID agd.HumanID
|
||||
ProfileID agd.ProfileID
|
||||
DeviceType agd.DeviceType
|
||||
}
|
||||
|
||||
// parseDeviceData returns either the device ID or the extended human-readable
|
||||
// ID data depending on what it can parse from the given string.
|
||||
//
|
||||
// TODO(a.garipov): Optimize error handling etc. based on profiles.
|
||||
func (ds *Default) parseDeviceData(s string) (id agd.DeviceID, extID *extHumanID, err error) {
|
||||
if isLikelyExtHumanID(s) {
|
||||
extID, err = ds.parseExtHumanID(s)
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return "", extID, err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Remove once the profile database learns how to match
|
||||
// IDs in a case-insensitive way.
|
||||
s = strings.ToLower(s)
|
||||
id, err = agd.NewDeviceID(s)
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return id, nil, err
|
||||
}
|
||||
|
||||
// isLikelyExtHumanID returns true if s likely contains extended human-readable
|
||||
// device-ID information.
|
||||
func isLikelyExtHumanID(s string) (ok bool) {
|
||||
return strings.Count(s, "-") >= 2
|
||||
}
|
||||
|
||||
// parseExtHumanID parses the data about a device that is identified by a device
|
||||
// type, a profile ID, and a human-readable device ID.
|
||||
func (ds *Default) parseExtHumanID(s string) (extID *extHumanID, err error) {
|
||||
defer func() { err = errors.Annotate(err, "parsing %q: %w", s) }()
|
||||
|
||||
parts := strings.SplitN(s, "-", 3)
|
||||
if len(parts) != 3 {
|
||||
// Technically shouldn't happen, as this function should only be called
|
||||
// when isLikelyExtHumanID(s) is true.
|
||||
return nil, errors.Error("not a valid ext human id")
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Use normalization.
|
||||
dt, err := agd.DeviceTypeFromDNS(parts[0])
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Remove once the profile database learns how to match
|
||||
// IDs in a case-insensitive way.
|
||||
profIDStr := strings.ToLower(parts[1])
|
||||
profID, err := agd.NewProfileID(profIDStr)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
humanID, err := ds.humanIDParser.ParseNormalized(parts[2])
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &extHumanID{
|
||||
HumanID: humanID,
|
||||
ProfileID: profID,
|
||||
DeviceType: dt,
|
||||
}, nil
|
||||
}
|
100
internal/dnssvc/internal/devicesetter/humanid_test.go
Normal file
100
internal/dnssvc/internal/devicesetter/humanid_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package devicesetter_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/devicesetter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
)
|
||||
|
||||
func TestDefault_SetDevice_humanID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "bad_type",
|
||||
in: "!!!-abcd1234-My-Device-X--10",
|
||||
wantErrMsg: `tls server name device id check: parsing "!!!-abcd1234-My-Device-X--10": ` +
|
||||
`bad device type "!!!": unknown device type`,
|
||||
}, {
|
||||
name: "bad_profile_id",
|
||||
in: "otr-\x00-My-Device-X--10",
|
||||
wantErrMsg: `tls server name device id check: parsing "otr-\x00-My-Device-X--10": ` +
|
||||
`bad profile id: bad char '\x00' at index 0`,
|
||||
}, {
|
||||
name: "bad_human_id",
|
||||
in: "otr-abcd1234-!!!",
|
||||
wantErrMsg: `tls server name device id check: parsing "otr-abcd1234-!!!": ` +
|
||||
`bad non-normalized human id "!!!": cannot normalize`,
|
||||
}}
|
||||
|
||||
profDB := &agdtest.ProfileDB{
|
||||
OnCreateAutoDevice: func(
|
||||
ctx context.Context,
|
||||
id agd.ProfileID,
|
||||
humanID agd.HumanID,
|
||||
devType agd.DeviceType,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDedicatedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByDeviceID: func(
|
||||
_ context.Context,
|
||||
devID agd.DeviceID,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByHumanID: func(
|
||||
_ context.Context,
|
||||
_ agd.ProfileID,
|
||||
_ agd.HumanIDLower,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
|
||||
OnProfileByLinkedIP: func(
|
||||
_ context.Context,
|
||||
_ netip.Addr,
|
||||
) (p *agd.Profile, d *agd.Device, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
df := devicesetter.NewDefault(&devicesetter.Config{
|
||||
ProfileDB: profDB,
|
||||
HumanIDParser: agd.NewHumanIDParser(),
|
||||
Server: srvDoT,
|
||||
DeviceIDWildcards: []string{dnssvctest.DomainForDevices},
|
||||
})
|
||||
|
||||
ctx := dnsserver.ContextWithRequestInfo(context.Background(), &dnsserver.RequestInfo{
|
||||
TLSServerName: tc.in + "." + dnssvctest.DomainForDevices,
|
||||
})
|
||||
ri := &agd.RequestInfo{
|
||||
RemoteIP: dnssvctest.ClientAddr,
|
||||
}
|
||||
|
||||
err := df.SetDevice(ctx, reqNormal, ri, dnssvctest.ServerAddrPort)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ package dnssvctest
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
@ -14,18 +15,19 @@ import (
|
||||
// Timeout is the common timeout for tests.
|
||||
const Timeout time.Duration = 1 * time.Second
|
||||
|
||||
// String representations of the common IDs for tests.
|
||||
// Common IDs for tests and their string representations.
|
||||
const (
|
||||
DeviceIDStr = "dev1234"
|
||||
ProfileIDStr = "prof1234"
|
||||
DeviceIDStr = "dev1234"
|
||||
ProfileIDStr = "prof1234"
|
||||
HumanIDStr = "My-Device-X--10"
|
||||
HumanIDLowerStr = "my-device-x--10"
|
||||
|
||||
DeviceID agd.DeviceID = DeviceIDStr
|
||||
ProfileID agd.ProfileID = ProfileIDStr
|
||||
HumanID agd.HumanID = HumanIDStr
|
||||
HumanIDLower agd.HumanIDLower = HumanIDLowerStr
|
||||
)
|
||||
|
||||
// DeviceID is the common device ID for tests.
|
||||
const DeviceID agd.DeviceID = DeviceIDStr
|
||||
|
||||
// ProfileID is the common profile ID for tests.
|
||||
const ProfileID agd.ProfileID = ProfileIDStr
|
||||
|
||||
// String representations for the common filtering-rule list ID for tests.
|
||||
const (
|
||||
FilterListID1Str = "flt_1"
|
||||
@ -66,28 +68,62 @@ const (
|
||||
// [agd.DeviceID] or use [DeviceIDSrvName].
|
||||
DeviceIDWildcard = "*." + DomainForDevices
|
||||
|
||||
// DeviceIDSrvName is the common client server-name for tests.
|
||||
// DeviceIDSrvName is the common client server-name with a device ID for
|
||||
// tests.
|
||||
DeviceIDSrvName = DeviceIDStr + "." + DomainForDevices
|
||||
|
||||
// HumanIDPath is the common client URL path with human-readable device-data
|
||||
// for tests.
|
||||
HumanIDPath = "otr-" + ProfileIDStr + "-" + HumanIDStr
|
||||
|
||||
// HumanIDSrvName is the common client server-name with human-readable
|
||||
// device-data for tests.
|
||||
HumanIDSrvName = HumanIDPath + "." + DomainForDevices
|
||||
)
|
||||
|
||||
// Use a constant block with iota to keep track of the unique final bytes of IP
|
||||
// addresses more easily.
|
||||
const (
|
||||
ipByteZero = iota
|
||||
ipByteClient
|
||||
ipByteServer
|
||||
ipByteDomain
|
||||
ipByteLinked
|
||||
ipByteDedicated
|
||||
)
|
||||
|
||||
// Common addresses for tests.
|
||||
var (
|
||||
ClientIP = net.IP{1, 2, 3, 4}
|
||||
RemoteAddr = &net.TCPAddr{
|
||||
ClientIP = net.IP{192, 0, 2, ipByteClient}
|
||||
ClientTCPAddr = &net.TCPAddr{
|
||||
IP: ClientIP,
|
||||
Port: 12345,
|
||||
}
|
||||
|
||||
ClientAddrPort = RemoteAddr.AddrPort()
|
||||
ClientAddrPort = ClientTCPAddr.AddrPort()
|
||||
ClientAddr = ClientAddrPort.Addr()
|
||||
|
||||
LocalAddr = &net.TCPAddr{
|
||||
IP: net.IP{5, 6, 7, 8},
|
||||
ServerTCPAddr = &net.TCPAddr{
|
||||
IP: net.IP{192, 0, 2, ipByteServer},
|
||||
Port: 54321,
|
||||
}
|
||||
|
||||
ServerAddrPort = LocalAddr.AddrPort()
|
||||
ServerAddrPort = ServerTCPAddr.AddrPort()
|
||||
ServerAddr = ServerAddrPort.Addr()
|
||||
|
||||
DomainAddrIPv4 = netip.AddrFrom4([4]byte{192, 0, 2, ipByteDomain})
|
||||
DomainAddrIPv6 = netip.AddrFrom16([16]byte{
|
||||
0x20, 0x01, 0x0d, 0xb8,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, ipByteDomain,
|
||||
})
|
||||
|
||||
LinkedAddr = netip.AddrFrom4([4]byte{192, 0, 2, ipByteLinked})
|
||||
LinkedAddrPort = netip.AddrPortFrom(LinkedAddr, 12345)
|
||||
|
||||
DedicatedAddr = netip.AddrFrom4([4]byte{192, 0, 2, ipByteDedicated})
|
||||
DedicatedAddrPort = netip.AddrPortFrom(DedicatedAddr, 53)
|
||||
)
|
||||
|
||||
// NewServer is a helper that returns a new *agd.Server for tests.
|
||||
|
@ -170,7 +170,7 @@ func TestMiddleware_Wrap(t *testing.T) {
|
||||
TLSServerName: srvNameForProto(tc.device, resolverName, tc.srv.Protocol),
|
||||
})
|
||||
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
req := &dns.Msg{
|
||||
Question: []dns.Question{{
|
||||
Name: tc.host,
|
||||
@ -278,7 +278,7 @@ func TestMiddleware_Wrap_error(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{})
|
||||
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
req := &dns.Msg{
|
||||
Question: []dns.Question{{
|
||||
Name: "www.example.com.",
|
||||
@ -652,7 +652,7 @@ func BenchmarkMiddleware_Wrap(b *testing.B) {
|
||||
return rw.WriteMsg(ctx, req, resp)
|
||||
})
|
||||
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
|
||||
b.Run("success", func(b *testing.B) {
|
||||
ds := &dnssvctest.DeviceSetter{
|
||||
|
@ -203,7 +203,7 @@ func TestMiddleware_ServeDNS_specialDomain(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = dnsserver.ContextWithRequestInfo(ctx, &dnsserver.RequestInfo{})
|
||||
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
req := &dns.Msg{
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn(tc.host),
|
||||
|
@ -17,17 +17,19 @@ import (
|
||||
|
||||
// Debug header name constants.
|
||||
const (
|
||||
hdrNameResType = "res-type"
|
||||
hdrNameRuleListID = "rule-list-id"
|
||||
hdrNameRule = "rule"
|
||||
hdrNameClientIP = "client-ip"
|
||||
hdrNameServerIP = "server-ip"
|
||||
hdrNameDeviceID = "device-id"
|
||||
hdrNameProfileID = "profile-id"
|
||||
hdrNameCountry = "country"
|
||||
hdrNameASN = "asn"
|
||||
hdrNameClientIP = "client-ip"
|
||||
hdrNameCountry = "country"
|
||||
hdrNameDeviceID = "device-id"
|
||||
hdrNameHumanID = "human-id"
|
||||
hdrNameProfileID = "profile-id"
|
||||
hdrNameResType = "res-type"
|
||||
hdrNameRule = "rule"
|
||||
hdrNameRuleListID = "rule-list-id"
|
||||
hdrNameServerIP = "server-ip"
|
||||
hdrNameSubdivision = "subdivision"
|
||||
hdrNameHost = "adguard-dns.com."
|
||||
|
||||
hdrDomain = "adguard-dns.com."
|
||||
)
|
||||
|
||||
// writeDebugResponse writes the debug response to rw.
|
||||
@ -90,12 +92,10 @@ func (mw *Middleware) appendDebugExtraFromContext(
|
||||
resp *dns.Msg,
|
||||
) (err error) {
|
||||
ri := agd.MustRequestInfoFromContext(ctx)
|
||||
if d := ri.Device; d != nil {
|
||||
setQuestionName(debugReq, "", hdrNameDeviceID)
|
||||
err = mw.messages.AppendDebugExtra(debugReq, resp, string(d.ID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding %s extra: %w", hdrNameDeviceID, err)
|
||||
}
|
||||
err = mw.appendDebugExtraFromDevice(ri.Device, debugReq, resp)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
if p := ri.Profile; p != nil {
|
||||
@ -117,8 +117,36 @@ func (mw *Middleware) appendDebugExtraFromContext(
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendDebugExtraFromDevice adds debug info to response got from devices data,
|
||||
// if any.
|
||||
func (mw *Middleware) appendDebugExtraFromDevice(
|
||||
dev *agd.Device,
|
||||
debugReq *dns.Msg,
|
||||
resp *dns.Msg,
|
||||
) (err error) {
|
||||
if dev == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
setQuestionName(debugReq, "", hdrNameDeviceID)
|
||||
err = mw.messages.AppendDebugExtra(debugReq, resp, string(dev.ID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding %s extra: %w", hdrNameDeviceID, err)
|
||||
}
|
||||
|
||||
if humanID := dev.HumanIDLower; humanID != "" {
|
||||
setQuestionName(debugReq, "", hdrNameHumanID)
|
||||
err = mw.messages.AppendDebugExtra(debugReq, resp, string(humanID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding %s extra: %w", hdrNameHumanID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendDebugExtraFromLocation adds debug info to response got from request
|
||||
// info location. loc should not be nil.
|
||||
// info location. loc must not be nil.
|
||||
func (mw *Middleware) appendDebugExtraFromLocation(
|
||||
loc *geoip.Location,
|
||||
debugReq *dns.Msg,
|
||||
@ -221,9 +249,9 @@ func (mw *Middleware) addDebugExtraFromFiltering(
|
||||
func setQuestionName(req *dns.Msg, prefix, suffix string) {
|
||||
var strs []string
|
||||
if prefix == "" {
|
||||
strs = []string{suffix, hdrNameHost}
|
||||
strs = []string{suffix, hdrDomain}
|
||||
} else {
|
||||
strs = []string{prefix, suffix, hdrNameHost}
|
||||
strs = []string{prefix, suffix, hdrDomain}
|
||||
}
|
||||
|
||||
req.Question[0].Name = strings.Join(strs, ".")
|
||||
|
@ -226,7 +226,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.ServerTCPAddr, dnssvctest.ClientTCPAddr)
|
||||
|
||||
ctx := agd.ContextWithRequestInfo(context.Background(), tc.reqInfo)
|
||||
|
||||
|
@ -244,7 +244,7 @@ func TestMiddleware_Wrap(t *testing.T) {
|
||||
h := mw.Wrap(newSimpleHandler(t, tc.req, wantResp))
|
||||
|
||||
ctx := newContext(tc.device, tc.profile, reqHost, reqQType, reqStart)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.ServerTCPAddr, dnssvctest.ClientTCPAddr)
|
||||
|
||||
err := h.ServeDNS(ctx, rw, tc.req)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
@ -707,7 +707,7 @@ func TestMiddleware_Wrap_filtering(t *testing.T) {
|
||||
h := mw.Wrap(newSimpleHandler(t, tc.wantUpsReq, tc.upsResp))
|
||||
|
||||
ctx := newContext(tc.device, tc.profile, reqHost, reqQType, reqStart)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.ServerTCPAddr, dnssvctest.ClientTCPAddr)
|
||||
|
||||
err := h.ServeDNS(ctx, rw, tc.req)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
@ -2,6 +2,7 @@ package mainmw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/optlog"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
@ -74,7 +76,7 @@ func (mw *Middleware) recordQueryInfo(
|
||||
ProfileID: prof.ID,
|
||||
DeviceID: devID,
|
||||
ClientCountry: reqCtry,
|
||||
ResponseCountry: mw.responseCountry(ctx, fctx, ri, respIP),
|
||||
ResponseCountry: mw.responseCountry(ctx, fctx, ri.Host, respIP, rcode),
|
||||
DomainFQDN: q.Name,
|
||||
ClientASN: reqASN,
|
||||
Elapsed: uint16(time.Since(start).Milliseconds()),
|
||||
@ -93,30 +95,34 @@ func (mw *Middleware) recordQueryInfo(
|
||||
}
|
||||
|
||||
// responseCountry returns the country of the response IP address based on the
|
||||
// request and filtering data.
|
||||
// request and filtering data. If rcode is not a NOERROR one or there is no
|
||||
// IP-address data in the response, ctry is [geoip.CountryNotApplicable].
|
||||
func (mw *Middleware) responseCountry(
|
||||
ctx context.Context,
|
||||
fctx *filteringContext,
|
||||
ri *agd.RequestInfo,
|
||||
host string,
|
||||
respIP netip.Addr,
|
||||
rcode dnsmsg.RCode,
|
||||
) (ctry geoip.Country) {
|
||||
if respIP == (netip.Addr{}) || respIP.IsUnspecified() {
|
||||
return geoip.CountryNone
|
||||
if rcode != dns.RcodeSuccess || respIP == (netip.Addr{}) || respIP.IsUnspecified() {
|
||||
return geoip.CountryNotApplicable
|
||||
}
|
||||
|
||||
host := ri.Host
|
||||
if modReq := fctx.modifiedRequest; modReq != nil {
|
||||
// If the request was modified by CNAME rule, the actual result
|
||||
// belongs to the hostname from that CNAME.
|
||||
host = agdnet.NormalizeDomain(modReq.Question[0].Name)
|
||||
}
|
||||
|
||||
return mw.country(ctx, host, respIP)
|
||||
ctry = mw.country(ctx, host, respIP)
|
||||
optlog.Debug2("mainmw: got ctry %q for resp ip %v", ctry, respIP)
|
||||
|
||||
return ctry
|
||||
}
|
||||
|
||||
// responseData is a helper that returns the response code, the first IP
|
||||
// address, and the DNSSEC AD flag from the DNS query response if the answer has
|
||||
// the type of A or AAAA or an empty IP address otherwise.
|
||||
// the type of A, AAAA, or HTTPS or an empty IP address otherwise.
|
||||
//
|
||||
// If resp is nil or contains invalid data, it returns 0xff (an unassigned
|
||||
// RCODE), net.Addr{}, and false. It reports all errors using
|
||||
@ -129,12 +135,23 @@ func (mw *Middleware) responseData(
|
||||
return 0xff, netip.Addr{}, false
|
||||
}
|
||||
|
||||
dnssec = resp.AuthenticatedData
|
||||
rcode = dnsmsg.RCode(resp.Rcode)
|
||||
|
||||
ip, err := ipFromAnswer(resp.Answer)
|
||||
if err != nil {
|
||||
mw.reportf(ctx, "getting response data: %w", err)
|
||||
}
|
||||
|
||||
return rcode, ip, dnssec
|
||||
}
|
||||
|
||||
// ipFromAnswer returns the first IP address from the answer resource records.
|
||||
func ipFromAnswer(answer []dns.RR) (ip netip.Addr, err error) {
|
||||
var rrType dns.Type
|
||||
var fam netutil.AddrFamily
|
||||
var netIP net.IP
|
||||
dnssec = resp.AuthenticatedData
|
||||
rcode = dnsmsg.RCode(resp.Rcode)
|
||||
for _, rr := range resp.Answer {
|
||||
for _, rr := range answer {
|
||||
switch v := rr.(type) {
|
||||
case *dns.A:
|
||||
fam = netutil.AddrFamilyIPv4
|
||||
@ -142,6 +159,8 @@ func (mw *Middleware) responseData(
|
||||
case *dns.AAAA:
|
||||
fam = netutil.AddrFamilyIPv6
|
||||
rrType, netIP = dns.Type(v.Hdr.Rrtype), v.AAAA
|
||||
case *dns.HTTPS:
|
||||
return ipFromHTTPSRR(v)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -149,15 +168,60 @@ func (mw *Middleware) responseData(
|
||||
break
|
||||
}
|
||||
|
||||
if netIP != nil {
|
||||
var err error
|
||||
ip, err = netutil.IPToAddr(netIP, fam)
|
||||
if err != nil {
|
||||
mw.reportf(ctx, "converting %s resp data: %w", rrType, err)
|
||||
if netIP == nil {
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
ip, err = netutil.IPToAddr(netIP, fam)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("converting %s resp data: %w", rrType, err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// ipFromHTTPSRR returns the data for the first IP hint in an HTTPS resource
|
||||
// record.
|
||||
func ipFromHTTPSRR(https *dns.HTTPS) (ip netip.Addr, err error) {
|
||||
var fam netutil.AddrFamily
|
||||
var netIP net.IP
|
||||
for _, v := range https.Value {
|
||||
fam, netIP = ipFromHTTPSRRKV(v)
|
||||
if fam != netutil.AddrFamilyNone {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return rcode, ip, dnssec
|
||||
if netIP == nil {
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
ip, err = netutil.IPToAddr(netIP, fam)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("converting https rr %s hint data: %w", fam, err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// ipFromHTTPSRRKV returns the IP-address data from the IP hint of an HTTPS
|
||||
// resource record. If the hint does not contain an IP address, fam is
|
||||
// [netutil.AddrFamilyNone] and netIP is nil.
|
||||
func ipFromHTTPSRRKV(kv dns.SVCBKeyValue) (fam netutil.AddrFamily, netIP net.IP) {
|
||||
switch kv := kv.(type) {
|
||||
case *dns.SVCBIPv4Hint:
|
||||
if len(kv.Hint) > 0 {
|
||||
return netutil.AddrFamilyIPv4, kv.Hint[0]
|
||||
}
|
||||
case *dns.SVCBIPv6Hint:
|
||||
if len(kv.Hint) > 0 {
|
||||
return netutil.AddrFamilyIPv6, kv.Hint[0]
|
||||
}
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
return netutil.AddrFamilyNone, nil
|
||||
}
|
||||
|
||||
// country is a wrapper around the GeoIP call that contains the handling of
|
||||
|
201
internal/dnssvc/internal/mainmw/record_internal_test.go
Normal file
201
internal/dnssvc/internal/mainmw/record_internal_test.go
Normal file
@ -0,0 +1,201 @@
|
||||
package mainmw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMiddleware_recordQueryInfo_respCtry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
fqdn = dnssvctest.DomainFQDN
|
||||
class = dns.ClassINET
|
||||
ttlSec = 10
|
||||
|
||||
testCtry = geoip.CountryAD
|
||||
)
|
||||
|
||||
var (
|
||||
reqA = dnsservertest.NewReq(fqdn, dns.TypeA, class)
|
||||
reqAAAA = dnsservertest.NewReq(fqdn, dns.TypeAAAA, class)
|
||||
reqTXT = dnsservertest.NewReq(fqdn, dns.TypeTXT, class)
|
||||
reqHTTPS = dnsservertest.NewReq(fqdn, dns.TypeHTTPS, class)
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
req *dns.Msg
|
||||
name string
|
||||
wantRespCtry geoip.Country
|
||||
respAns []dns.RR
|
||||
respRCode dnsmsg.RCode
|
||||
wantGeoIP bool
|
||||
}{{
|
||||
req: reqA,
|
||||
name: "empty",
|
||||
wantRespCtry: geoip.CountryNotApplicable,
|
||||
respAns: nil,
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: false,
|
||||
}, {
|
||||
req: reqA,
|
||||
name: "refused",
|
||||
wantRespCtry: geoip.CountryNotApplicable,
|
||||
respAns: nil,
|
||||
respRCode: dns.RcodeRefused,
|
||||
wantGeoIP: false,
|
||||
}, {
|
||||
req: reqA,
|
||||
name: "a",
|
||||
wantRespCtry: testCtry,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewA(fqdn, ttlSec, dnssvctest.DomainAddrIPv4),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: true,
|
||||
}, {
|
||||
req: reqA,
|
||||
name: "a_unspec",
|
||||
wantRespCtry: geoip.CountryNotApplicable,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewA(fqdn, ttlSec, netip.IPv4Unspecified()),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: false,
|
||||
}, {
|
||||
req: reqAAAA,
|
||||
name: "aaaa",
|
||||
wantRespCtry: testCtry,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewAAAA(fqdn, ttlSec, dnssvctest.DomainAddrIPv6),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: true,
|
||||
}, {
|
||||
req: reqTXT,
|
||||
name: "txt",
|
||||
wantRespCtry: geoip.CountryNotApplicable,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewTXT(fqdn, ttlSec),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: false,
|
||||
}, {
|
||||
req: reqHTTPS,
|
||||
name: "https_no_ips",
|
||||
wantRespCtry: geoip.CountryNotApplicable,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewHTTPS(fqdn, ttlSec, nil, nil),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: false,
|
||||
}, {
|
||||
req: reqHTTPS,
|
||||
name: "https_ipv4",
|
||||
wantRespCtry: testCtry,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewHTTPS(fqdn, ttlSec, []netip.Addr{dnssvctest.DomainAddrIPv4}, nil),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: true,
|
||||
}, {
|
||||
req: reqHTTPS,
|
||||
name: "https_ipv6",
|
||||
wantRespCtry: testCtry,
|
||||
respAns: []dns.RR{
|
||||
dnsservertest.NewHTTPS(fqdn, ttlSec, nil, []netip.Addr{dnssvctest.DomainAddrIPv6}),
|
||||
},
|
||||
respRCode: dns.RcodeSuccess,
|
||||
wantGeoIP: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
loc := &geoip.Location{
|
||||
Country: testCtry,
|
||||
}
|
||||
|
||||
geoIP := &agdtest.GeoIP{
|
||||
OnSubnetByLocation: func(
|
||||
_ *geoip.Location,
|
||||
_ netutil.AddrFamily,
|
||||
) (n netip.Prefix, err error) {
|
||||
panic("not implemented")
|
||||
},
|
||||
OnData: func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
|
||||
if !tc.wantGeoIP {
|
||||
t.Error("unexpected call to geoip")
|
||||
}
|
||||
|
||||
return loc, nil
|
||||
},
|
||||
}
|
||||
|
||||
queryLogCalled := false
|
||||
var gotRespCtry geoip.Country
|
||||
queryLog := &agdtest.QueryLog{
|
||||
OnWrite: func(_ context.Context, e *querylog.Entry) (err error) {
|
||||
queryLogCalled = true
|
||||
|
||||
require.NotNil(t, e)
|
||||
gotRespCtry = e.ResponseCountry
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
mw := &Middleware{
|
||||
billStat: billstat.EmptyRecorder{},
|
||||
geoIP: geoIP,
|
||||
queryLog: queryLog,
|
||||
ruleStat: rulestat.Empty{},
|
||||
}
|
||||
|
||||
ctx := dnsserver.ContextWithRequestInfo(context.Background(), &dnsserver.RequestInfo{
|
||||
StartTime: time.Now(),
|
||||
})
|
||||
|
||||
fctx := &filteringContext{
|
||||
originalRequest: tc.req,
|
||||
filteredResponse: dnsservertest.NewResp(
|
||||
int(tc.respRCode),
|
||||
tc.req,
|
||||
dnsservertest.SectionAnswer(tc.respAns),
|
||||
),
|
||||
}
|
||||
|
||||
ri := &agd.RequestInfo{
|
||||
Profile: &agd.Profile{
|
||||
QueryLogEnabled: true,
|
||||
},
|
||||
Device: &agd.Device{},
|
||||
QType: tc.req.Question[0].Qtype,
|
||||
QClass: class,
|
||||
}
|
||||
|
||||
mw.recordQueryInfo(ctx, fctx, ri)
|
||||
require.True(t, queryLogCalled)
|
||||
|
||||
assert.Equal(t, gotRespCtry, tc.wantRespCtry)
|
||||
})
|
||||
}
|
||||
}
|
@ -122,7 +122,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
tctx := agd.ContextWithRequestInfo(ctx, tc.ri)
|
||||
|
||||
dnsCk := &agdtest.DNSCheck{
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
// handling as well as records anonymous DNS statistics.
|
||||
type Middleware struct {
|
||||
cloner *dnsmsg.Cloner
|
||||
cacheManager agdcache.Manager
|
||||
db dnsdb.Interface
|
||||
geoIP geoip.Interface
|
||||
cacheMinTTL time.Duration
|
||||
@ -42,6 +44,9 @@ type Config struct {
|
||||
// Cloner is used to clone messages taken from cache.
|
||||
Cloner *dnsmsg.Cloner
|
||||
|
||||
// CacheManager is the global cache manager. CacheManager must not be nil.
|
||||
CacheManager agdcache.Manager
|
||||
|
||||
// DB is used to update anonymous statistics about DNS queries.
|
||||
DB dnsdb.Interface
|
||||
|
||||
@ -72,6 +77,7 @@ type Config struct {
|
||||
func New(c *Config) (mw *Middleware) {
|
||||
return &Middleware{
|
||||
cloner: c.Cloner,
|
||||
cacheManager: c.CacheManager,
|
||||
db: c.DB,
|
||||
geoIP: c.GeoIP,
|
||||
cacheMinTTL: c.CacheMinTTL,
|
||||
@ -135,6 +141,7 @@ func (mw *Middleware) wrapCacheMw(next dnsserver.Handler) (wrapped dnsserver.Han
|
||||
if mw.useECSCache {
|
||||
cacheMw = ecscache.NewMiddleware(&ecscache.MiddlewareConfig{
|
||||
Cloner: mw.cloner,
|
||||
CacheManager: mw.cacheManager,
|
||||
GeoIP: mw.geoIP,
|
||||
Size: mw.cacheSize,
|
||||
ECSSize: mw.ecsCacheSize,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
@ -122,6 +123,7 @@ func TestPreUpstreamMwHandler_ServeDNS_withECSCache(t *testing.T) {
|
||||
|
||||
mw := preupstream.New(&preupstream.Config{
|
||||
Cloner: agdtest.NewCloner(),
|
||||
CacheManager: agdcache.EmptyManager{},
|
||||
DB: dnsdb.Empty{},
|
||||
GeoIP: geoIP,
|
||||
CacheSize: 100,
|
||||
@ -233,7 +235,7 @@ func TestPreUpstreamMwHandler_ServeDNS_androidMetric(t *testing.T) {
|
||||
|
||||
h := mw.Wrap(handler)
|
||||
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr)
|
||||
rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.ClientTCPAddr)
|
||||
|
||||
err := h.ServeDNS(ctx, rw, tc.req)
|
||||
require.NoError(t, err)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,12 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Constants that define cache identifiers for the cache manager.
|
||||
const (
|
||||
CacheIDWithECS = "ecscache_with_ecs"
|
||||
CacheIDNoECS = "ecscache_no_ecs"
|
||||
)
|
||||
|
||||
// Middleware is a dnsserver.Middleware with ECS-aware caching.
|
||||
type Middleware struct {
|
||||
// cloner is the memory-efficient cloner of DNS messages.
|
||||
@ -50,6 +56,9 @@ type MiddlewareConfig struct {
|
||||
// Cloner is used to clone messages taken from cache.
|
||||
Cloner *dnsmsg.Cloner
|
||||
|
||||
// CacheManager is the global cache manager. CacheManager must not be nil.
|
||||
CacheManager agdcache.Manager
|
||||
|
||||
// GeoIP is the GeoIP database used to get subnets for countries. It must
|
||||
// not be nil.
|
||||
GeoIP geoip.Interface
|
||||
@ -69,18 +78,25 @@ type MiddlewareConfig struct {
|
||||
UseTTLOverride bool
|
||||
}
|
||||
|
||||
// NewMiddleware initializes a new ECS-aware LRU caching middleware. c must not
|
||||
// be nil.
|
||||
// NewMiddleware initializes a new ECS-aware LRU caching middleware. It also
|
||||
// adds the caches with IDs [CacheIDNoECS] and [CacheIDWithECS] to the cache
|
||||
// manager. c must not be nil.
|
||||
func NewMiddleware(c *MiddlewareConfig) (m *Middleware) {
|
||||
cache := agdcache.NewLRU[uint64, *cacheItem](&agdcache.LRUConfig{
|
||||
Size: c.Size,
|
||||
})
|
||||
ecsCache := agdcache.NewLRU[uint64, *cacheItem](&agdcache.LRUConfig{
|
||||
Size: c.ECSSize,
|
||||
})
|
||||
|
||||
c.CacheManager.Add(CacheIDNoECS, cache)
|
||||
c.CacheManager.Add(CacheIDWithECS, ecsCache)
|
||||
|
||||
return &Middleware{
|
||||
cloner: c.Cloner,
|
||||
cache: agdcache.NewLRU[uint64, *cacheItem](&agdcache.LRUConfig{
|
||||
Size: c.Size,
|
||||
}),
|
||||
ecsCache: agdcache.NewLRU[uint64, *cacheItem](&agdcache.LRUConfig{
|
||||
Size: c.ECSSize,
|
||||
}),
|
||||
geoIP: c.GeoIP,
|
||||
cloner: c.Cloner,
|
||||
cache: cache,
|
||||
ecsCache: ecsCache,
|
||||
geoIP: c.GeoIP,
|
||||
cacheReqPool: syncutil.NewPool(func() (req *cacheRequest) {
|
||||
return &cacheRequest{}
|
||||
}),
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
|
||||
@ -686,6 +687,7 @@ func newWithCache(
|
||||
h,
|
||||
ecscache.NewMiddleware(&ecscache.MiddlewareConfig{
|
||||
Cloner: agdtest.NewCloner(),
|
||||
CacheManager: agdcache.EmptyManager{},
|
||||
GeoIP: geoIP,
|
||||
Size: 100,
|
||||
ECSSize: 100,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user