diff --git a/.gitignore b/.gitignore index 57dd421..01ef28c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,5 @@ AdGuardDNS asn.mmdb config.yaml country.mmdb -dnsdb.bolt querylog.jsonl profilecache.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 982a33f..c60d030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,190 @@ The format is **not** based on [Keep a Changelog][kec], since the project +## AGDNS-1537 / Build 580 + + * The optional property `bind_interfaces` of `server_groups.*.servers` + objects has been changed, property `subnet` is now an array and has been + ranamed to `subnets`. So replace this: + + ```yaml + bind_interfaces: + - id: 'dns' + subnet: '10.0.0.1/32' + - id: 'dns' + subnet: '10.0.0.2/32' + - id: 'dns' + subnet: '10.0.0.3/32' + - id: 'dns_secondary' + subnet: '10.0.0.1/32' + ``` + + with this: + + ```yaml + bind_interfaces: + - id: 'dns' + subnets: + - '10.0.0.1/32' + - '10.0.0.2/32' + - '10.0.0.3/32' + - id: 'dns_secondary' + subnets: + - '10.0.0.1/32' + ``` + + + +## AGDNS-1537 / Build 566 + + * The configuration property `filtering_groups.safe_browsing` has been changed, + new properties have been added: `block_dangerous_domains` and + `block_newly_registered_domains`. + + + +## AGDNS-1580 / Build 562 + + * The environment variable `DNSDB_PATH` has been removed. + * New configuration `dnsdb` has been added, it has an enabled/disabled flag + and the property `max_size` which describes the maximum amount of records in + the in-memory buffer. Example configuration: + + ```yaml + dnsdb: + enabled: true + max_size: 500000 + ``` + + + +## AGDNS-1537 / Build 559 + + * Configuration properties `safe_browsing.url` and `adult_blocking.url` are + now removed. Use newly added environment variables `ADULT_BLOCKING_URL` and + `SAFE_BROWSING_URL`. + * New environment variable `NEW_REG_DOMAINS_URL` has been added, this is the + link to the source list of the newly registered domains. + + + +## AGDNS-1567 / Build 557 + + * The environment variable `BACKEND_ENDPOINT` was replaced with three + environment variables: + + * `LINKED_IP_TARGET_URL`: the target URL to which linked IP API requests + are proxied. + * `PROFILES_URL`: the endpoint for profiles sync API. + * `BILLSTAT_URL`: the endpoint for backend billing statistics uploader. + + + +## AGDNS-1561 / Build 554 + + * The `filters` object has a new property, `max_size`, which describes the + maximum size of the downloadable content for a rule-list in a human-readable + format. Example configuration: + + ```yaml + filters: + # … + max_size: 256MB + ``` + + + +## AGDNS-1561 / Build 550 + + * Properties `so_sndbuf` and `so_rcvbuf` of object `network` have been changed. + Now they are in a human-readable format. Example configuration: + + ```yaml + network: + so_sndbuf: 2MB + so_rcvbuf: 0 + ``` + + * The object `filters` has been changed. Two properties, + `rule_list_cache_size` and `use_rule_list_cache` have been extracted to the + new object `rule_list_cache` and renamed to `size` and `enabled`. So + replace this: + + ```yaml + filters: + response_ttl: 5m + custom_filter_cache_size: 1024 + safe_search_cache_size: 1024 + rule_list_cache_size: 10000 + refresh_interval: 1h + refresh_timeout: 5m + use_rule_list_cache: true + ``` + + with this: + + ```yaml + filters: + response_ttl: 5m + custom_filter_cache_size: 1024 + safe_search_cache_size: 1024 + refresh_interval: 1h + refresh_timeout: 5m + rule_list_cache: + enabled: true + size: 10000 + ``` + + Adjust the values, if necessary. + + + +## AGDNS-1566 / Build 549 + + * There is now a new env variable `RESEARCH_LOGS` that controls whether + logging of additional info for research purposes is enabled. These log + records can be filtered out by `research:` prefix. The default value is + `0`, i.e. additional logging is disabled. The first thing that is logged + in this version is domains which responses have ECH config. The log will + only be recorded when both `RESEARCH_LOGS` and `RESEARCH_METRICS` are set + to `1`. + + * Added a new research metric `dns_research_response_ech` that counts the + number of responses with a ECH configuration. + + + +## AGDNS-1556 / Build 547 + + * The object `cache` has a new property `ttl_override`. It describes the TTL + override settings, such as the minimum TTL for cache items and the `enabled` + switch. It overwrites the TTL from DNS response in case it's less than this + minimum value. So replace this: + + ```yaml + cache: + type: "simple" + size: 10000 + ecs_size: 10000 + ``` + + with this: + + ```yaml + cache: + type: "simple" + size: 10000 + ecs_size: 10000 + ttl_override: + enabled: true + # The minimum duration of TTL for a cache item. + min: 60s + ``` + + Adjust the values, if necessary. + + + ## AGDNS-1498 / Build 527 * Object `ratelimit` has a new property, `connection_limit`, which allows diff --git a/Makefile b/Makefile index 736b97d..decf5b3 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ # # AdGuard-Project-Version: 2 -# Don't name these macros "GO" etc., because GNU Make apparenly makes +# Don't name these macros "GO" etc., because GNU Make apparently makes # them exported environment variables with the literal value of # "${GO:-go}" and so on, which is not what we need. Use a dot in the # name to make sure that users don't have an environment variable with diff --git a/config.dist.yaml b/config.dist.yaml index 10bc23d..6806607 100644 --- a/config.dist.yaml +++ b/config.dist.yaml @@ -62,6 +62,10 @@ cache: size: 10000 # The total number of items in the cache for hostnames with ECS support. ecs_size: 10000 + ttl_override: + enabled: true + # The minimum duration of TTL for a cache item. + min: 60s # DNS upstream configuration. upstream: @@ -77,6 +81,11 @@ upstream: backoff_duration: 30s domain_template: '${RANDOM}.neverssl.com' +# DNSDB configuration. +dnsdb: + enabled: true + max_size: 500000 + # Common DNS HTTP backend service configuration. backend: # Timeout for all outgoing backend HTTP requests. Set to `0s` to disable @@ -192,7 +201,6 @@ web: # AdGuard general safe browsing filter configuration. safe_browsing: - url: 'https://raw.githubusercontent.com/ameshkov/PersonalFilters/master/safebrowsing_test.txt' block_host: 'standard-block.dns.adguard.com' cache_size: 1024 cache_ttl: 1h @@ -200,7 +208,6 @@ safe_browsing: # AdGuard adult content blocking filter configuration. adult_blocking: - url: 'https://raw.githubusercontent.com/ameshkov/PersonalFilters/master/adult_test.txt' block_host: 'family-block.dns.adguard.com' cache_size: 1024 cache_ttl: 1h @@ -215,16 +222,20 @@ filters: custom_filter_cache_size: 1024 # The size of the LRU cache of safe-search filtering results. safe_search_cache_size: 1024 - # The size of the LRU cache of rule-list filtering results. - rule_list_cache_size: 10000 # How often to update filters from the index. See the documentation for the # FILTER_INDEX_URL environment variable. refresh_interval: 1h # The timeout for the entire filter update operation. Be aware that each # individual refresh operation also has its own hardcoded 30s timeout. refresh_timeout: 5m - # If true, use filtering rule list result cache. - use_rule_list_cache: true + # MaxSize is the maximum size of the downloadable filtering rule-list. + max_size: 256MB + # Rule list cache. + rule_list_cache: + # If true, use filtering rule list result cache. + enabled: true + # The size of the LRU cache of rule-list filtering results. + size: 10000 # Filtering groups are a set of different filtering configurations. These # filtering configurations are then used by server_groups. @@ -240,6 +251,8 @@ filtering_groups: - 'adguard_dns_filter' safe_browsing: enabled: true + block_dangerous_domains: true + block_newly_registered_domains: false block_private_relay: false block_firefox_canary: true - id: 'family' @@ -254,6 +267,8 @@ filtering_groups: - 'adguard_dns_filter' safe_browsing: enabled: true + block_dangerous_domains: true + block_newly_registered_domains: false block_private_relay: false block_firefox_canary: true - id: 'non_filtering' @@ -263,6 +278,8 @@ filtering_groups: enabled: false safe_browsing: enabled: false + block_dangerous_domains: true + block_newly_registered_domains: false block_private_relay: false block_firefox_canary: true @@ -330,9 +347,11 @@ server_groups: # the plain-DNS servers. bind_interfaces: - id: 'eth0_plain_dns' - subnet: '127.0.0.0/8' + subnets: + - '127.0.0.0/8' - id: 'eth0_plain_dns_secondary' - subnet: '127.0.0.0/8' + subnets: + - '127.0.0.0/8' - name: 'default_dot' protocol: 'tls' linked_ip_enabled: false @@ -383,9 +402,9 @@ additional_metrics_info: # Network settings. network: - # Defines the size of socket send buffer in bytes. Default is zero (uses - # system settings). + # Defines the size of socket send buffer in a human-readable format. + # Default is zero (uses system settings). so_sndbuf: 0 - # Defines the size of socket receive buffer in bytes. Default is zero - # (uses system settings). + # Defines the size of socket receive buffer in a human-readable format. + # Default is zero (uses system settings). so_rcvbuf: 0 diff --git a/doc/configuration.md b/doc/configuration.md index 50e631c..90d8cc9 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -15,6 +15,7 @@ configuration file with comments. * [Cache](#cache) * [Upstream](#upstream) * [Healthcheck](#upstream-healthcheck) + * [DNSDB](#dnsdb) * [Backend](#backend) * [Query log](#query_log) * [GeoIP database](#geoip) @@ -242,6 +243,25 @@ The `cache` object has the following properties: **Example:** `10000`. + * `ttl_override`: + The object describes cache TTL override mechanics. It has the following + properties: + + * `enabled`: + If true, the TTL overrides are enabled. + + * `min`: + The minimum duration for TTL for cache items of both caches, with and + without ECS support. The recommended value is `60s`. + + **Property example:** + + ```yaml + 'ttl_override': + 'enabled': true + 'min': 60s + ``` + ## Upstream @@ -320,6 +340,24 @@ connection to the main upstream as restored, and requests are routed back to it. +## DNSDB + +The `DNSDB` object has the following properties: + + * `enabled`: + If true, the DNSDB memory buffer is enabled. + + **Example:** `true`. + + * `max_size`: + The maximum number of records in the in-memory buffer. The record key is a + combination of the target hostname from the question and the resource-record + type of the question or the answer. + + **Example:** `500000`. + + + ## Backend The `backend` object has the following properties: @@ -635,11 +673,6 @@ The `filters` object has the following properties: **Example:** `1024`. - * `rule_list_cache_size`: - The size of the LRU cache of the rule-list filtering results. - - **Example:** `10000`. - * `refresh_interval`: How often AdGuard DNS refreshes the rule-list filters from the filter index, as well as the blocked services list from the [blocked list @@ -654,11 +687,25 @@ The `filters` object has the following properties: **Example:** `5m`. - * `use_rule_list_cache`: - If true, use the rule-list filtering result cache. This cache is not used - for users' custom rules. + * `max_size`: + The maximum size of the downloadable content for a rule-list in a + human-readable format. - **Example:** `true`. + **Example:** `256MB`. + + * `rule_list_cache`: + Rule lists cache settings. It has the following properties: + + * `enabled`: + If true, use the rule-list filtering result cache. This cache is not + used for users' custom rules. + + **Example:** `true`. + + * `rule_list_cache-size`: + The size of the LRU cache of the rule-list filtering results. + + **Example:** `10000`. [env-blocked_services]: environment.md#BLOCKED_SERVICE_INDEX_URL @@ -724,6 +771,16 @@ The items of the `filtering_groups` array have the following properties: **Example:** `true`. + * `block_dangerous_domains`: + Shows if the dangerous domains filtering should be enforced. + + **Example:** `true`. + + * `block_newly_registered_domains`: + Shows if the newly registered domains filtering should be enforced. + + **Example:** `true`. + * `private_relay`: If true, Apple Private Relay queries are blocked for requests using this filtering group. @@ -953,10 +1010,12 @@ The items of the `servers` array have the following properties: ```yaml 'bind_interfaces': - - 'id': eth0_plain_dns' - 'subnet': '172.17.0.0/16' - - 'id': eth0_plain_dns_secondary' - 'subnet': '172.17.0.0/16' + - 'id': 'eth0_plain_dns' + 'subnets': + - '172.17.0.0/16' + - 'id': 'eth0_plain_dns_secondary' + 'subnets': + - '172.17.0.0/16' ``` * `dnscrypt`: @@ -1017,20 +1076,20 @@ The `connectivity_check` object has the following properties: The `network` object has the following properties: * `so_rcvbuf`: - The size of socket receive buffer (`SO_RCVBUF`), in bytes. Default is zero, - which means use the default system settings. + The size of socket receive buffer (`SO_RCVBUF`), in a human-readable format. + Default is zero, which means use the default system settings. See also [notes on these parameters](#recommended-buffers). - **Example:** `1048576`. + **Example:** `1MB`. * `so_sndbuf`: - The size of socket send buffer (`SO_SNDBUF`), in bytes. Default is zero, - which means use the default system settings. + The size of socket send buffer (`SO_SNDBUF`), in a human-readable format. + Default is zero, which means use the default system settings. See also [notes on these parameters](#recommended-buffers). - **Example:** `1048576`. + **Example:** `1MB`. diff --git a/doc/debugdns.md b/doc/debugdns.md index 16e64fc..30405ed 100644 --- a/doc/debugdns.md +++ b/doc/debugdns.md @@ -28,6 +28,7 @@ example.com. 17597 IN A 93.184.216.34 ;; ADDITIONAL SECTION: client-ip.adguard-dns.com. 10 CH TXT "127.0.0.1" +server-ip.adguard-dns.com. 10 CH TXT "94.140.14.14" resp.res-type.adguard-dns.com. 10 CH TXT "normal" ;; Query time: 26 msec @@ -49,6 +50,15 @@ In the `ADDITIONAL SECTION`, the following debug information is returned: client-ip.adguard-dns.com. 10 CH TXT "127.0.0.1" ``` + * `server-ip`: + The IP address of the server. The full name is `server-ip.adguard-dns.com`. + + **Example:** + + ```none + server-ip.adguard-dns.com. 10 CH TXT "127.0.0.1" + ``` + * `device-id`: The ID of the device as detected by the server, if any. The full name is `device-id.adguard-dns.com`. @@ -87,7 +97,7 @@ In the `ADDITIONAL SECTION`, the following debug information is returned: ```none asn.adguard-dns.com. 10 CH TXT "1234" ``` - + * `subdivision`: User's location subdivision code. This field could be empty even if user's country code is present. The full name is `subdivision.adguard-dns.com`. @@ -142,7 +152,7 @@ response. Rule longer than 255 bytes: ```none - req.rule.adguard-dns.com. 0 CH TXT "||heregoesthefirstpartoftherule" + req.rule.adguard-dns.com. 0 CH TXT "||heregoesthefirstpartoftherule" "heregoesthesecondpartoftherule" ``` diff --git a/doc/development.md b/doc/development.md index b925b2d..42990f0 100644 --- a/doc/development.md +++ b/doc/development.md @@ -216,9 +216,14 @@ curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoLit You'll need to supply the following: - * [`BACKEND_ENDPOINT`](#env-BACKEND_ENDPOINT) + * [`ADULT_BLOCKING_URL`](#env-ADULT_BLOCKING_URL) + * [`BILLSTAT_URL`](#env-BILLSTAT_URL) * [`CONSUL_ALLOWLIST_URL`](#env-CONSUL_ALLOWLIST_URL) * [`GENERAL_SAFE_SEARCH_URL`](#env-GENERAL_SAFE_SEARCH_URL) + * [`LINKED_IP_TARGET_URL`](#env-LINKED_IP_TARGET_URL) + * [`NEW_REG_DOMAINS_URL`](#env-NEW_REG_DOMAINS_URL) + * [`PROFILES_URL`](#env-PROFILES_URL) + * [`SAFE_BROWSING_URL`](#env-SAFE_BROWSING_URL) * [`YOUTUBE_SAFE_SEARCH_URL`](#env-YOUTUBE_SAFE_SEARCH_URL) See the [external HTTP API documentation][externalhttp]. @@ -238,18 +243,22 @@ You may also need to remove `probe_ipv6` if your network does not support IPv6. ```sh env \ - BACKEND_ENDPOINT='PUT BACKEND URL HERE' \ - BLOCKED_SERVICE_INDEX_URL='https://atropnikov.github.io/HostlistsRegistry/assets/services.json'\ + ADULT_BLOCKING_URL='https://raw.githubusercontent.com/ameshkov/PersonalFilters/master/adult_test.txt' \ + BILLSTAT_URL='PUT BILLSTAT API BACKEND URL HERE' \ + BLOCKED_SERVICE_INDEX_URL='https://adguardteam.github.io/HostlistsRegistry/assets/services.json'\ CONSUL_ALLOWLIST_URL='PUT CONSUL ALLOWLIST URL HERE' \ CONFIG_PATH='./config.yaml' \ - DNSDB_PATH='./test/cache/dnsdb.bolt' \ - FILTER_INDEX_URL='https://atropnikov.github.io/HostlistsRegistry/assets/filters.json' \ + FILTER_INDEX_URL='https://adguardteam.github.io/HostlistsRegistry/assets/filters.json' \ FILTER_CACHE_PATH='./test/cache' \ + NEW_REG_DOMAINS_URL='PUT NEWLY REGISTERED DOMAINS FILTER URL HERE' \ PROFILES_CACHE_PATH='./test/profilecache.json' \ + PROFILES_URL='PUT PROFILES API BACKEND URL HERE' \ + SAFE_BROWSING_URL='https://raw.githubusercontent.com/ameshkov/PersonalFilters/master/safebrowsing_test.txt' \ GENERAL_SAFE_SEARCH_URL='https://adguardteam.github.io/HostlistsRegistry/assets/engines_safe_search.txt' \ GEOIP_ASN_PATH='./test/GeoLite2-ASN-Test.mmdb' \ GEOIP_COUNTRY_PATH='./test/GeoIP2-City-Test.mmdb' \ QUERYLOG_PATH='./test/cache/querylog.jsonl' \ + LINKED_IP_TARGET_URL='PUT LINKED IP TARGET URL HERE' \ LISTEN_ADDR='127.0.0.1' \ LISTEN_PORT='8081' \ RULESTAT_URL='https://testchrome.adtidy.org/api/1.0/rulestats.html' \ diff --git a/doc/environment.md b/doc/environment.md index f4d8828..02e52f7 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -6,24 +6,29 @@ sensitive configuration. All other configuration is stored in the ## Contents - * [`BACKEND_ENDPOINT`](#BACKEND_ENDPOINT) + * [`ADULT_BLOCKING_URL`](#ADULT_BLOCKING_URL) + * [`BILLSTAT_URL`](#BILLSTAT_URL) * [`BLOCKED_SERVICE_INDEX_URL`](#BLOCKED_SERVICE_INDEX_URL) * [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL) * [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL) * [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL) * [`CONFIG_PATH`](#CONFIG_PATH) - * [`DNSDB_PATH`](#DNSDB_PATH) * [`FILTER_INDEX_URL`](#FILTER_INDEX_URL) * [`FILTER_CACHE_PATH`](#FILTER_CACHE_PATH) - * [`PROFILES_CACHE_PATH`](#PROFILES_CACHE_PATH) * [`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_URL`](#PROFILES_URL) * [`QUERYLOG_PATH`](#QUERYLOG_PATH) * [`RESEARCH_METRICS`](#RESEARCH_METRICS) + * [`RESEARCH_LOGS`](#RESEARCH_LOGS) * [`RULESTAT_URL`](#RULESTAT_URL) + * [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL) * [`SENTRY_DSN`](#SENTRY_DSN) * [`SSL_KEY_LOG_FILE`](#SSL_KEY_LOG_FILE) * [`VERBOSE`](#VERBOSE) @@ -34,16 +39,26 @@ sensitive configuration. All other configuration is stored in the -## `BACKEND_ENDPOINT` +## `ADULT_BLOCKING_URL` -The base backend URL to which API paths are appended. The backend endpoints -apart from the `/ddns/`and `/linkip/` ones must reply with a 200 status code on -success. +The URL of source list of rules for adult blocking filter. **Default:** No default value, the variable is **required.** +## `BILLSTAT_URL` + +The base backend URL for backend billing statistics uploader API. The backend +endpoints must reply with a 200 status code on success. See the [external HTTP +API requirements section][ext-billstat] + +**Default:** No default value, the variable is **required.** + +[ext-billstat]: externalhttp.md#backend-billstat + + + ## `BLOCKED_SERVICE_INDEX_URL` The URL of the blocked service index file server. See the [external HTTP API @@ -101,57 +116,15 @@ for the DNS server checking. If not specified, the -## `DNSDB_PATH` - -The path to the DNSDB BoltDB database. If empty or unset, DNSDB statistics -collection is disabled. - -**Default:** **Unset.** - -**Example:** `./dnsdb.bolt`. - - - ## `FILTER_CACHE_PATH` -The path to the directory with the filter lists cache. +The path to the directory used to store the cached version of all filters and +filter indexes. **Default:** `./filters/`. -## `PROFILES_CACHE_PATH` - -The path to the profile cache file: - - * `none` means that the profile caching is disabled. - - * 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: - - ```sh - protoc\ - --decode\ - profiledb.FileCache\ - ./internal/profiledb/internal/filecachepb/filecache.proto\ - < /path/to/profilecache.pb - ``` - - * A file with the extension `.json` means that the profiles are cached in the - JSON format. This format is **deprecated** and is not recommended. - -The profile cache is read on start and is later updated on every -[full refresh][conf-backend-full_refresh_interval]. - -**Default:** `./profilecache.json`. - -[conf-backend-full_refresh_interval]: configuration.md#backend-full_refresh_interval - - - ## `FILTER_INDEX_URL` The URL of the filtering rule index file server. See the [external HTTP API @@ -184,12 +157,16 @@ countries and continents respectively. -## `LOG_TIMESTAMP` +## `LINKED_IP_TARGET_URL` -If `1`, show timestamps in the plain text logs. If `0`, don't show the -timestamps. +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:** `1`. +**Default:** **Unset.** + +[conf-web-linked_ip]: configuration.md#web-linked_ip +[ext-linked_ip]: externalhttp.md#backend-linkip @@ -212,6 +189,68 @@ health check, Prometheus, `pprof`, and other endpoints. +## `LOG_TIMESTAMP` + +If `1`, show timestamps in the plain text logs. If `0`, don't show the +timestamps. + +**Default:** `1`. + + + +## `NEW_REG_DOMAINS_URL` + +The URL of source list of rules for newly registered domains safe browsing +filter. + +**Default:** No default value, the variable is **required.** + + + +## `PROFILES_CACHE_PATH` + +The path to the profile cache file: + +* `none` means that the profile caching is disabled. + +* 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: + + ```sh + protoc\ + --decode\ + profiledb.FileCache\ + ./internal/profiledb/internal/filecachepb/filecache.proto\ + < /path/to/profilecache.pb + ``` + +* A file with the extension `.json` means that the profiles are cached in the + JSON format. This format is **deprecated** and is not recommended. + +The profile cache is read on start and is later updated on every +[full refresh][conf-backend-full_refresh_interval]. + +**Default:** `./profilecache.json`. + +[conf-backend-full_refresh_interval]: configuration.md#backend-full_refresh_interval + + + +## `PROFILES_URL` + +The base backend URL for profiles API. The backend endpoints must reply with a +200 status code on success. See the [external HTTP API requirements +section][ext-profiles]. + +**Default:** No default value, the variable is **required.** + +[ext-profiles]: externalhttp.md#profiles-backend + + + ## `QUERYLOG_PATH` The path to the file into which the query log is going to be written. @@ -229,6 +268,16 @@ If `1`, enable collection of a set of special prometheus metrics (prefix is +## `RESEARCH_LOGS` + +If `1`, enable logging of additional info that may be required for research +purposes (prefix `research:`). The log will only be written when +`RESEARCH_METRICS` is also set to `1`. If `0`, disable logging of this info. + +**Default:** `0`. + + + ## `RULESTAT_URL` The URL to send filtering rule list statistics to. If empty or unset, the @@ -243,6 +292,14 @@ requirements section][ext-rulestat] on the expected format of the response. +## `SAFE_BROWSING_URL` + +The URL of source list of rules for dangerous domains safe browsing filter. + +**Default:** No default value, the variable is **required.** + + + ## `SENTRY_DSN` Sentry error collector address. The special value `stderr` makes AdGuard DNS diff --git a/doc/externalhttp.md b/doc/externalhttp.md index fd263fe..cb3cf01 100644 --- a/doc/externalhttp.md +++ b/doc/externalhttp.md @@ -14,30 +14,50 @@ document should set the `Server` header in their replies. ## Contents - * [Backend And Linked IP Service](#backend) - * [`GET /dns_api/v1/settings`](#backend-get-v1-settings) - * [`POST /dns_api/v1/settings`](#backend-post-v1-devices_activity) - * [Proxied Linked IP and Dynamic DNS (DDNS) Endpoints](#backend-linkip) + * [Backend Billing Statistics](#backend-billstat) + * [Backend Profiles Service](#backend-profiles) * [Consul Key-Value Storage](#consul) * [Filtering](#filters) * [Blocked Services](#filters-blocked-services) * [Filtering Rule Lists](#filters-lists) * [Safe Search](#filters-safe-search) + * [Proxied Linked IP and Dynamic DNS (DDNS) Endpoints](#backend-linkip) * [Rule Statistics Service](#rulestat) -## Backend And Linked IP Service +## Backend Billing Statistics -This is the service to which the [`BACKEND_ENDPOINT`][env-backend] environment -variable points. This service must provide two endpoints: +This is the service to which the [`BILLSTAT_URL`][env-billstat_url] environment +variable points. This service must provide one endpoint: +`POST /dns_api/v1/devices_activity`, it must respond with a `200 OK` response +code and accept a JSON document in the following format: + +```json +{ + "devices": [ + { + "client_country": "AU", + "device_id": "abcd1234", + "time_ms": 1624443079309, + "asn": 1234, + "queries": 1000, + "proto": 1 + } + ] +} +``` + +[env-billstat_url]: environment.md#BILLSTAT_URL - ### `GET /dns_api/v1/settings` +## Backend Profiles Service -This endpoint must respond with a `200 OK` response code and a JSON document in -the following format: +This is the service to which the [`PROFILES_URL`][env-profiles_url] environment +variable points. This service must provide one endpoint: +`GET /dns_api/v1/settings`, it must respond with a `200 OK` response code and +accept a JSON document in the following format: ```json { @@ -108,43 +128,7 @@ the following format: } ``` - - - ### `POST /dns_api/v1/devices_activity` - -This endpoint must respond with a `200 OK` response code and accept a JSON -document in the following format: - -```json -{ - "devices": [ - { - "client_country": "AU", - "device_id": "abcd1234", - "time_ms": 1624443079309, - "asn": 1234, - "queries": 1000, - "proto": 1 - } - ] -} -``` - - - ### Proxied Linked IP and Dynamic DNS (DDNS) Endpoints - -The same service defined by the [`BACKEND_ENDPOINT`][env-backend] environment -variable should define the following endpoints: - - * `GET /linkip/{device_id}/{encrypted}/status`; - * `GET /linkip/{device_id}/{encrypted}`; - * `POST /ddns/{device_id}/{encrypted}/{domain}`; - * `POST /linkip/{device_id}/{encrypted}`. - -The AdGuard DNS proxy will add the `CF-Connecting-IP` header with the IP address -of the original client as well as set the `User-Agent` header to its own value. - -[env-backend]: environment.md#BACKEND_ENDPOINT +[env-profiles_url]: environment.md#PROFILES_URL @@ -192,7 +176,7 @@ format: "id": "my_filter", "rules": [ "||example.com^", - "||example.net^", + "||example.net^" ] } ] @@ -252,6 +236,23 @@ code and filtering rule lists with [`$dnsrewrite`][rules-dnsrewrite] rules for +## Proxied Linked IP and Dynamic DNS (DDNS) Endpoints + +The service defined by the [`LINKED_IP_TARGET_URL`][env-linked_ip_target_url] +environment variable should define the following endpoints: + + * `GET /linkip/{device_id}/{encrypted}/status`; + * `GET /linkip/{device_id}/{encrypted}`; + * `POST /ddns/{device_id}/{encrypted}/{domain}`; + * `POST /linkip/{device_id}/{encrypted}`. + +The AdGuard DNS proxy will add the `CF-Connecting-IP` header with the IP address +of the original client as well as set the `User-Agent` header to its own value. + +[env-linked_ip_target_url]: environment.md#LINKED_IP_TARGET_URL + + + ## Rule Statistics Service This endpoint, defined by [`RULESTAT_URL`][env-rulestat], must respond with a @@ -263,7 +264,7 @@ This endpoint, defined by [`RULESTAT_URL`][env-rulestat], must respond with a { "15": { "||example.com^": 1234, - "||example.net^": 5678, + "||example.net^": 5678 } } ] diff --git a/doc/http.md b/doc/http.md index f3565b7..135495e 100644 --- a/doc/http.md +++ b/doc/http.md @@ -110,7 +110,7 @@ The `protocol` field can have one of the following values: The linked IP and Dynamic DNS (DDNS, DynDNS) HTTP proxy. If the [linked IP configuration][conf-web-linked_ip] is not empty, the following queries are -either processed or proxied to [`BACKEND_ENDPOINT`][env-backend]. +either processed or proxied to [`LINKED_IP_TARGET_URL`][env-linked_ip_target_url]. * `GET /robots.txt`: a special response is served, see below; * `GET /linkip/{device_id}/{encrypted}/status`: proxied; @@ -128,7 +128,7 @@ Disallow: / The [static content](#static-content) is not served on the linked IP addresses. [conf-web-linked_ip]: configuration.md#web-linked_ip -[env-backend]: environment.md#BACKEND_ENDPOINT +[env-linked_ip_target_url]: environment.md#LINKED_IP_TARGET_URL diff --git a/go.mod b/go.mod index 6db877b..4a111e6 100644 --- a/go.mod +++ b/go.mod @@ -4,27 +4,26 @@ go 1.20 require ( github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0 - github.com/AdguardTeam/golibs v0.13.2 - github.com/AdguardTeam/urlfilter v0.16.1 - github.com/ameshkov/dnscrypt/v2 v2.2.5 + github.com/AdguardTeam/golibs v0.13.6 + github.com/AdguardTeam/urlfilter v0.16.2 + github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc github.com/bluele/gcache v0.0.2 github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/caarlos0/env/v7 v7.1.0 - github.com/getsentry/sentry-go v0.19.0 - github.com/google/renameio v1.0.1 - github.com/miekg/dns v1.1.52 + github.com/getsentry/sentry-go v0.21.0 + github.com/google/renameio/v2 v2.0.0 + github.com/miekg/dns v1.1.55 github.com/oschwald/maxminddb-golang v1.10.0 github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible - github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/client_model v0.3.0 - github.com/prometheus/common v0.41.0 + github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_model v0.4.0 + github.com/prometheus/common v0.44.0 github.com/quic-go/quic-go v0.35.1 - github.com/stretchr/testify v1.8.2 - go.etcd.io/bbolt v1.3.7 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/net v0.8.0 - golang.org/x/sys v0.6.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/net v0.12.0 + golang.org/x/sys v0.10.0 golang.org/x/time v0.3.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 @@ -38,22 +37,22 @@ require ( github.com/cespare/xxhash/v2 v2.2.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-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/onsi/ginkgo/v2 v2.9.0 // indirect - github.com/panjf2000/ants/v2 v2.7.1 // indirect + github.com/onsi/ginkgo/v2 v2.10.0 // indirect + github.com/panjf2000/ants/v2 v2.7.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/text v0.11.0 // indirect + golang.org/x/tools v0.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index da53b2a..b90aa1f 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.13.2 h1:BPASsyQKmb+b8VnvsNOHp7bKfcZl9Z+Z2UhPjOiupSc= -github.com/AdguardTeam/golibs v0.13.2/go.mod h1:7ylQLv2Lqsc3UW3jHoITynYk6Y1tYtgEMkR09ppfsN8= +github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ= +github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= -github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= +github.com/AdguardTeam/urlfilter v0.16.2 h1:k9m9dUYVJ3sTswYa2/ukVNjicfGcz0oqFDO13hPmfHE= +github.com/AdguardTeam/urlfilter v0.16.2/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= 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= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY= -github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM= +github.com/ameshkov/dnscrypt/v2 v2.2.7 h1:aEitLIR8HcxVodZ79mgRcCiC0A0I5kZPBuWGFwwulAw= +github.com/ameshkov/dnscrypt/v2 v2.2.7/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow= 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-20230201085229-3ddf4bad03dc h1:Keo7wQ7UODUaHcEi7ltENhbAK2VgZjfat6mLy03tQzo= @@ -34,29 +34,28 @@ 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.19.0 h1:BcCH3CN5tXt5aML+gwmbFwVptLLQA+eT866fCO9wVOM= -github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKYwpEqryTOC/nE= +github.com/getsentry/sentry-go v0.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtVQWon4= +github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= -github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= -github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -64,16 +63,16 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c= -github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= -github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= -github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs= +github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= -github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M= -github.com/panjf2000/ants/v2 v2.7.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= +github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU= +github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= github.com/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= @@ -81,14 +80,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw= -github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= @@ -97,46 +96,46 @@ github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8G github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -147,23 +146,23 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index a688d3a..1b6b6e5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -4,6 +4,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= @@ -23,17 +24,36 @@ github.com/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGj github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.1.0 h1:hvO96X345XagdH1fAoBjpBYG4a1ghhL/QzalkduPuXk= +github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= 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/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= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +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/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= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -49,6 +69,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -58,16 +79,20 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ 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/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= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 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.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +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= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= @@ -79,23 +104,35 @@ github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 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/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 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= @@ -107,17 +144,19 @@ github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200j 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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 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= @@ -125,6 +164,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI 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/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= 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= @@ -132,6 +172,7 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+ 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/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= @@ -144,47 +185,81 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20220517205856-0058ec4f073c/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= 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= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1 h1:ujPKutqRlJtcfWk6toYVYagwra7HQHbXOaS171b4Tg8= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= +github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE= +github.com/kataras/golog v0.1.7/go.mod h1:jOSQ+C5fUqsNSwurB/oAHq1IFSb0KI3l6GMa7xB6dZA= +github.com/kataras/golog v0.1.8 h1:isP8th4PJH2SrbkciKnylaND9xoTtfxv++NB+DF0l9g= +github.com/kataras/golog v0.1.8/go.mod h1:rGPAin4hYROfk1qT9wZP6VY2rsb4zzc37QpdPjdkqVw= github.com/kataras/iris/v12 v12.2.0-beta5 h1:grB/oCf5baZhmYIeDMfgN3LYrtEcmK8pbxlRvEZ2pgw= +github.com/kataras/iris/v12 v12.2.0-beta5/go.mod h1:q26aoWJ0Knx/00iPKg5iizDK7oQQSPjbD8np0XDh6dc= +github.com/kataras/iris/v12 v12.2.0 h1:WzDY5nGuW/LgVaFS5BtTkW3crdSKJ/FEgWnxPnIVVLI= +github.com/kataras/iris/v12 v12.2.0/go.mod h1:BLzBpEunc41GbE68OUaQlqX4jzi791mx5HU04uPb90Y= github.com/kataras/pio v0.0.11 h1:kqreJ5KOEXGMwHAWHDwIl+mjfNCPhAwZPa8gK7MKlyw= +github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +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/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= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= +github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= 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/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= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= @@ -193,40 +268,60 @@ github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4 github.com/marten-seemann/qtls-go1-18 v0.1.0/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI= 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/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/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= github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI= @@ -275,6 +370,7 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133 h1:JtcyT0rk/9PKO github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= @@ -284,25 +380,43 @@ 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= 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/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/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= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +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/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/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= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +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/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/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= 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= +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= go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM= @@ -326,8 +440,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx 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/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -336,42 +449,49 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r 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-20190313220215-9f648a60d977/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-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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-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= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 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= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= @@ -379,9 +499,7 @@ golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGm 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.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= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -393,6 +511,8 @@ google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -414,6 +534,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= diff --git a/internal/agd/agd.go b/internal/agd/agd.go index de60e60..fb618c3 100644 --- a/internal/agd/agd.go +++ b/internal/agd/agd.go @@ -2,39 +2,11 @@ package agd import ( - "crypto/rand" - "encoding/base64" "fmt" ) // Common Constants, Types, And Utilities -// RequestID is the ID of a request. It is an opaque, randomly generated -// string. API users should not rely on it being pseudorandom or -// cryptographically random. -type RequestID string - -// NewRequestID returns a new pseudorandom RequestID. Prefer this to manual -// conversion from other string types. -func NewRequestID() (id RequestID) { - // Generate a random 16-byte (128-bit) number, encode it into a URL-safe - // Base64 string, and return it. - const N = 16 - - var idData [N]byte - _, err := rand.Read(idData[:]) - if err != nil { - panic(fmt.Errorf("generating random request id: %w", err)) - } - - enc := base64.URLEncoding.WithPadding(base64.NoPadding) - n := enc.EncodedLen(N) - idData64 := make([]byte, n) - enc.Encode(idData64, idData[:]) - - return RequestID(idData64) -} - // unit is a convenient alias for struct{}. type unit = struct{} diff --git a/internal/agd/context.go b/internal/agd/context.go index 217c3f8..7ce6fb5 100644 --- a/internal/agd/context.go +++ b/internal/agd/context.go @@ -50,7 +50,7 @@ func RequestIDFromContext(ctx context.Context) (id RequestID, ok bool) { const key = ctxKeyReqID v := ctx.Value(key) if v == nil { - return "", false + return RequestID{}, false } id, ok = v.(RequestID) @@ -95,14 +95,14 @@ type RequestInfo struct { // Server is the name of the server which handles this request. Server ServerName - // ID is the unique ID of the request. It is resurfaced here to optimize - // context lookups. - ID RequestID - // Host is the lowercased, non-FQDN version of the hostname from the // question of the request. Host string + // ID is the unique ID of the request. It is resurfaced here to optimize + // context lookups. + ID RequestID + // QType is the type of question for this request. QType dnsmsg.RRType diff --git a/internal/agd/country.go b/internal/agd/country.go index ea9302e..8e0a52c 100644 --- a/internal/agd/country.go +++ b/internal/agd/country.go @@ -1065,5 +1065,14 @@ func isUserAssigned(s string) (ok bool) { return false } - return s == "AA" || s == "OO" || s == "ZZ" || s[0] == 'X' || (s[0] == 'Q' && s[1] >= 'M') + if s[0] == 'X' || (s[0] == 'Q' && s[1] >= 'M') { + return true + } + + switch s { + case "AA", "OO", "ZZ": + return true + default: + return false + } } diff --git a/internal/agd/country_generate.go b/internal/agd/country_generate.go index 066ef1f..553b8c1 100644 --- a/internal/agd/country_generate.go +++ b/internal/agd/country_generate.go @@ -137,7 +137,16 @@ func isUserAssigned(s string) (ok bool) { return false } - return s == "AA" || s == "OO" || s == "ZZ" || s[0] == 'X' || (s[0] == 'Q' && s[1] >= 'M') + if s[0] == 'X' || (s[0] == 'Q' && s[1] >= 'M') { + return true + } + + switch s { + case "AA", "OO", "ZZ": + return true + default: + return false + } } ` diff --git a/internal/agd/filterlist.go b/internal/agd/filterlist.go index 0fbe429..1c5cba0 100644 --- a/internal/agd/filterlist.go +++ b/internal/agd/filterlist.go @@ -2,29 +2,11 @@ package agd import ( "fmt" - "net/url" - "time" "unicode/utf8" "github.com/AdguardTeam/golibs/errors" ) -// Filter Lists - -// FilterList is a list of filter rules. -type FilterList struct { - // URL is the URL used to refresh the filter. - URL *url.URL - - // ID is the unique ID of this filter. It will also be used to create the - // cache file. - ID FilterListID - - // RefreshIvl is the interval that defines how often a filter should be - // refreshed. It is also used to check if the cached file is fresh enough. - RefreshIvl time.Duration -} - // FilterListID is the ID of a filter list. It is an opaque string. type FilterListID string @@ -51,6 +33,10 @@ const ( // a request was filtered by the safe browsing filter. FilterListIDSafeBrowsing FilterListID = "safe_browsing" + // FilterListIDNewRegDomains is the special shared filter list ID used when + // a request was filtered by the newly registered domains filter. + FilterListIDNewRegDomains FilterListID = "newly_registered_domains" + // FilterListIDGeneralSafeSearch is the shared filter list ID used when // a request was modified by the general safe search filter. FilterListIDGeneralSafeSearch FilterListID = "general_safe_search" @@ -135,6 +121,14 @@ type FilteringGroup struct { // should be enforced. SafeBrowsingEnabled bool + // BlockDangerousDomains shows whether the dangerous domains safe browsing + // filtering should be enforced. + BlockDangerousDomains bool + + // BlockNewlyRegisteredDomains shows whether the newly registered domains + // safe browsing filtering should be enforced. + BlockNewlyRegisteredDomains bool + // GeneralSafeSearch shows whether the general safe search filtering should // be enforced. GeneralSafeSearch bool diff --git a/internal/agd/profile.go b/internal/agd/profile.go index 913ea3c..96d5ad8 100644 --- a/internal/agd/profile.go +++ b/internal/agd/profile.go @@ -30,6 +30,13 @@ type Profile struct { // [internal/profiledb/internal.FileCacheVersion]. Parental *ParentalProtectionSettings + // SafeBrowsing are the safe browsing settings for this profile. They are + // ignored if FilteringEnabled is set to false. + // + // NOTE: Do not change fields of this structure without incrementing + // [internal/profiledb/internal.FileCacheVersion]. + SafeBrowsing *SafeBrowsingSettings + // BlockingMode defines the way blocked responses are constructed. // // NOTE: Do not change fields of this structure without incrementing @@ -85,14 +92,6 @@ type Profile struct { // [internal/profiledb/internal.FileCacheVersion]. FilteringEnabled bool - // SafeBrowsingEnabled defines whether queries from devices of this profile - // should be filtered using the safe browsing filter. Requires - // FilteringEnabled to be set to true. - // - // NOTE: Do not change fields of this structure without incrementing - // [internal/profiledb/internal.FileCacheVersion]. - SafeBrowsingEnabled bool - // RuleListsEnabled defines whether queries from devices of this profile // should be filtered using the filtering rule lists in RuleListIDs. // Requires FilteringEnabled to be set to true. @@ -158,9 +157,12 @@ func NewProfileID(s string) (id ProfileID, err error) { // DayRange is a range within a single day. Start and End are minutes from the // start of day, with 0 being 00:00:00.(0) and 1439, 23:59:59.(9). // -// Additionally, if both Start and End are set to math.MaxUint16, the range is -// a special zero-length range. This is done to reduce the amount of pointers -// and thus GC time. +// Additionally, if both Start and End are set to [math.MaxUint16], the range is +// a special zero-length range. This is needed, because when both Start and End +// are zero, such DayRange indicates one minute after midnight; as well as to +// reduce the amount of pointers and thus GC time. +// +// TODO(a.garipov): Refactor. See AGDNS-1516. type DayRange struct { Start uint16 End uint16 @@ -261,6 +263,25 @@ type ParentalProtectionSettings struct { YoutubeSafeSearch bool } +// SafeBrowsingSettings are the safe browsing settings of a profile. +// +// NOTE: Do not change fields of this structure without incrementing +// [internal/profiledb/internal.FileCacheVersion]. +type SafeBrowsingSettings struct { + // Enabled defines whether queries from devices of this profile should be + // filtered using the safe browsing filter. This must be true in order for + // all parameters below to work. + Enabled bool + + // BlockDangerousDomains shows whether the dangerous domains safe browsing + // filtering should be enforced. + BlockDangerousDomains bool + + // BlockNewlyRegisteredDomains shows whether the newly registered domains + // safe browsing filtering should be enforced. + BlockNewlyRegisteredDomains bool +} + // BlockedServiceID is the ID of a blocked service. While these are usually // human-readable, clients should treat them as opaque strings. // diff --git a/internal/agd/requestid.go b/internal/agd/requestid.go new file mode 100644 index 0000000..26694fe --- /dev/null +++ b/internal/agd/requestid.go @@ -0,0 +1,52 @@ +package agd + +import ( + "encoding/base64" + "fmt" + "time" + + "golang.org/x/exp/rand" +) + +// RequestIDLen is the length of a [RequestID] in bytes. A RequestID is +// currently a random 16-byte (128-bit) number. +const RequestIDLen = 16 + +// RequestID is the ID of a request. It is an opaque, randomly generated +// string. API users should not rely on it being pseudorandom or +// cryptographically random. +type RequestID [RequestIDLen]byte + +// requestIDRand is used to create [RequestID]s. +// +// TODO(a.garipov): Consider making a struct instead of using one global source. +var requestIDRand = rand.New(&rand.LockedSource{}) + +// InitRequestID initializes the [RequestID] generator. +func InitRequestID() { + requestIDRand.Seed(uint64(time.Now().UnixNano())) +} + +// NewRequestID returns a new pseudorandom RequestID. Prefer this to manual +// conversion from other string types. +func NewRequestID() (id RequestID) { + _, err := requestIDRand.Read(id[:]) + if err != nil { + panic(fmt.Errorf("generating random request id: %w", err)) + } + + return id +} + +// type check +var _ fmt.Stringer = RequestID{} + +// String implements the [fmt.Stringer] interface for RequestID. +func (id RequestID) String() (s string) { + enc := base64.URLEncoding.WithPadding(base64.NoPadding) + n := enc.EncodedLen(RequestIDLen) + idData64 := make([]byte, n) + enc.Encode(idData64, id[:]) + + return string(idData64) +} diff --git a/internal/agd/requestid_test.go b/internal/agd/requestid_test.go new file mode 100644 index 0000000..700a4d8 --- /dev/null +++ b/internal/agd/requestid_test.go @@ -0,0 +1,29 @@ +package agd_test + +import ( + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/stretchr/testify/require" +) + +var reqIDSink agd.RequestID + +func BenchmarkNewRequestID(b *testing.B) { + agd.InitRequestID() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + reqIDSink = agd.NewRequestID() + } + + require.NotEmpty(b, reqIDSink) + + // 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 + // BenchmarkNewRequestID-16 50985721 24.91 ns/op 0 B/op 0 allocs/op +} diff --git a/internal/agdhttp/client.go b/internal/agdhttp/client.go index 3c46d66..75b6263 100644 --- a/internal/agdhttp/client.go +++ b/internal/agdhttp/client.go @@ -91,7 +91,7 @@ func (c *Client) do( reqID, ok := agd.RequestIDFromContext(ctx) if ok { - req.Header.Set(httphdr.XRequestID, string(reqID)) + req.Header.Set(httphdr.XRequestID, reqID.String()) } req.Header.Set(httphdr.UserAgent, c.userAgent) diff --git a/internal/agdmaps/agdmaps.go b/internal/agdmaps/agdmaps.go deleted file mode 100644 index d47c07e..0000000 --- a/internal/agdmaps/agdmaps.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package agdmaps contains utilities for map handling. -package agdmaps - -import ( - "golang.org/x/exp/constraints" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" -) - -// OrderedRange is like the usual Go range but sorts the keys before iterating -// ensuring a predictable order. If cont is false, OrderedRange stops the -// iteration. -func OrderedRange[K constraints.Ordered, V any, M ~map[K]V](m M, f func(k K, v V) (cont bool)) { - keys := maps.Keys(m) - slices.Sort(keys) - for _, k := range keys { - if !f(k, m[k]) { - break - } - } -} - -// OrderedRangeError is like [OrderedRange] but uses an error to signal that the -// iteration must be stopped. err is the same error as the one returned from f, -// or nil if no errors are returned. -func OrderedRangeError[K constraints.Ordered, V any, M ~map[K]V]( - m M, - f func(k K, v V) (err error), -) (err error) { - keys := maps.Keys(m) - slices.Sort(keys) - for _, k := range keys { - err = f(k, m[k]) - if err != nil { - return err - } - } - - return nil -} diff --git a/internal/agdnet/resolver.go b/internal/agdnet/resolver.go index 8bc7af7..1c632c8 100644 --- a/internal/agdnet/resolver.go +++ b/internal/agdnet/resolver.go @@ -124,18 +124,9 @@ func (c *CachingResolver) resolve( refrTime := time.Now() // Don't resolve IP addresses. - ip := net.ParseIP(host) + ip := ipFromHost(host, fam) if ip != nil { - ip4 := ip.To4() - if fam == netutil.AddrFamilyIPv4 && ip4 != nil { - ips = []net.IP{ip4} - } else if fam == netutil.AddrFamilyIPv6 && ip4 == nil { - ips = []net.IP{ip} - } else { - // Not the right kind of IP address. Cache absence of IP addresses - // for this network forever. - ips = []net.IP{} - } + ips = []net.IP{ip} // Set the refresh time to the maximum date that time.Duration allows to // prevent this item from refreshing. @@ -168,6 +159,24 @@ func (c *CachingResolver) resolve( return item, nil } +// ipFromHost returns a normalized IP address if host contains an IP address of +// the given address family. +func ipFromHost(host string, fam netutil.AddrFamily) (ip net.IP) { + ip = net.ParseIP(host) + if ip == nil { + return nil + } + + ip4 := ip.To4() + if fam == netutil.AddrFamilyIPv4 && ip4 != nil { + return ip4 + } else if fam == netutil.AddrFamilyIPv6 && ip4 == nil { + return ip + } + + return nil +} + // isExpectedLookupError returns true if the error is an expected lookup error. func isExpectedLookupError(fam netutil.AddrFamily, err error) (ok bool) { var dnsErr *net.DNSError diff --git a/internal/agdtest/interface.go b/internal/agdtest/interface.go index 3036150..bc1eb56 100644 --- a/internal/agdtest/interface.go +++ b/internal/agdtest/interface.go @@ -26,141 +26,6 @@ import ( // // Keep entities within a module/package in alphabetic order. -// Module std - -// Package net -// -// TODO(a.garipov): Move these to golibs? - -// type check -var _ net.Conn = (*Conn)(nil) - -// Conn is the [net.Conn] for tests. -type Conn struct { - OnClose func() (err error) - OnLocalAddr func() (laddr net.Addr) - OnRead func(b []byte) (n int, err error) - OnRemoteAddr func() (raddr net.Addr) - OnSetDeadline func(t time.Time) (err error) - OnSetReadDeadline func(t time.Time) (err error) - OnSetWriteDeadline func(t time.Time) (err error) - OnWrite func(b []byte) (n int, err error) -} - -// Close implements the [net.Conn] interface for *Conn. -func (c *Conn) Close() (err error) { - return c.OnClose() -} - -// LocalAddr implements the [net.Conn] interface for *Conn. -func (c *Conn) LocalAddr() (laddr net.Addr) { - return c.OnLocalAddr() -} - -// Read implements the [net.Conn] interface for *Conn. -func (c *Conn) Read(b []byte) (n int, err error) { - return c.OnRead(b) -} - -// RemoteAddr implements the [net.Conn] interface for *Conn. -func (c *Conn) RemoteAddr() (raddr net.Addr) { - return c.OnRemoteAddr() -} - -// SetDeadline implements the [net.Conn] interface for *Conn. -func (c *Conn) SetDeadline(t time.Time) (err error) { - return c.OnSetDeadline(t) -} - -// SetReadDeadline implements the [net.Conn] interface for *Conn. -func (c *Conn) SetReadDeadline(t time.Time) (err error) { - return c.OnSetReadDeadline(t) -} - -// SetWriteDeadline implements the [net.Conn] interface for *Conn. -func (c *Conn) SetWriteDeadline(t time.Time) (err error) { - return c.OnSetWriteDeadline(t) -} - -// Write implements the [net.Conn] interface for *Conn. -func (c *Conn) Write(b []byte) (n int, err error) { - return c.OnWrite(b) -} - -// type check -var _ net.Listener = (*Listener)(nil) - -// Listener is a [net.Listener] for tests. -type Listener struct { - OnAccept func() (c net.Conn, err error) - OnAddr func() (addr net.Addr) - OnClose func() (err error) -} - -// Accept implements the [net.Listener] interface for *Listener. -func (l *Listener) Accept() (c net.Conn, err error) { - return l.OnAccept() -} - -// Addr implements the [net.Listener] interface for *Listener. -func (l *Listener) Addr() (addr net.Addr) { - return l.OnAddr() -} - -// Close implements the [net.Listener] interface for *Listener. -func (l *Listener) Close() (err error) { - return l.OnClose() -} - -// type check -var _ net.PacketConn = (*PacketConn)(nil) - -// PacketConn is the [net.PacketConn] for tests. -type PacketConn struct { - OnClose func() (err error) - OnLocalAddr func() (laddr net.Addr) - OnReadFrom func(b []byte) (n int, addr net.Addr, err error) - OnSetDeadline func(t time.Time) (err error) - OnSetReadDeadline func(t time.Time) (err error) - OnSetWriteDeadline func(t time.Time) (err error) - OnWriteTo func(b []byte, addr net.Addr) (n int, err error) -} - -// Close implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) Close() (err error) { - return c.OnClose() -} - -// LocalAddr implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) LocalAddr() (laddr net.Addr) { - return c.OnLocalAddr() -} - -// ReadFrom implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { - return c.OnReadFrom(b) -} - -// SetDeadline implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) SetDeadline(t time.Time) (err error) { - return c.OnSetDeadline(t) -} - -// SetReadDeadline implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) SetReadDeadline(t time.Time) (err error) { - return c.OnSetReadDeadline(t) -} - -// SetWriteDeadline implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) SetWriteDeadline(t time.Time) (err error) { - return c.OnSetWriteDeadline(t) -} - -// WriteTo implements the [net.PacketConn] interface for *PacketConn. -func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - return c.OnWriteTo(b, addr) -} - // Module AdGuardDNS // type check diff --git a/internal/backend/billstat.go b/internal/backend/billstat.go index 5090ae4..45a7625 100644 --- a/internal/backend/billstat.go +++ b/internal/backend/billstat.go @@ -10,9 +10,9 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" - "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/mapsutil" ) // Billing Statistics Uploader @@ -114,7 +114,7 @@ type v1DevicesActivityReqDevice struct { // devices activity HTTP API. func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityReqDevice) { devices = make([]*v1DevicesActivityReqDevice, 0, len(records)) - agdmaps.OrderedRange(records, func(id agd.DeviceID, rec *billstat.Record) (cont bool) { + mapsutil.OrderedRange(records, func(id agd.DeviceID, rec *billstat.Record) (cont bool) { devices = append(devices, &v1DevicesActivityReqDevice{ ClientCountry: rec.Country, DeviceID: id, diff --git a/internal/backend/profiledb.go b/internal/backend/profiledb.go index a61544e..e7f84d3 100644 --- a/internal/backend/profiledb.go +++ b/internal/backend/profiledb.go @@ -216,7 +216,28 @@ type v1SettingsRespRuleLists struct { // v1SettingsRespSafeBrowsing is the structure for decoding the general safe // browsing filtering settings from the backend. type v1SettingsRespSafeBrowsing struct { - Enabled bool `json:"enabled"` + BlockDangerousDomains *bool `json:"block_dangerous_domains"` + BlockNewlyRegisteredDomains bool `json:"block_nrd"` + Enabled bool `json:"enabled"` +} + +// toInternal converts s to an [agd.SafeBrowsingSettings] instance. +func (s *v1SettingsRespSafeBrowsing) toInternal() (res *agd.SafeBrowsingSettings) { + if s == nil { + return nil + } + + // TODO(d.kolyshev): Don't make this migration after AGDNS-1537. + blockDangerDomains := s.Enabled + if s.BlockDangerousDomains != nil { + blockDangerDomains = *s.BlockDangerousDomains + } + + return &agd.SafeBrowsingSettings{ + Enabled: s.Enabled, + BlockDangerousDomains: blockDangerDomains, + BlockNewlyRegisteredDomains: s.BlockNewlyRegisteredDomains, + } } // v1SettingsResp is the structure for decoding the response from the backend. @@ -510,8 +531,6 @@ func (r *v1SettingsResp) toInternal( // other validation errors. } - sbEnabled := s.SafeBrowsing != nil && s.SafeBrowsing.Enabled - pr.Devices = append(pr.Devices, devices...) pr.Profiles = append(pr.Profiles, &agd.Profile{ @@ -523,7 +542,7 @@ func (r *v1SettingsResp) toInternal( RuleListIDs: ruleLists, CustomRules: rules, FilteredResponseTTL: fltRespTTL, - SafeBrowsingEnabled: sbEnabled, + SafeBrowsing: s.SafeBrowsing.toInternal(), RuleListsEnabled: rlEnabled, FilteringEnabled: s.FilteringEnabled, QueryLogEnabled: s.QueryLogEnabled, diff --git a/internal/backend/profiledb_test.go b/internal/backend/profiledb_test.go index 6abcb22..e4a5017 100644 --- a/internal/backend/profiledb_test.go +++ b/internal/backend/profiledb_test.go @@ -114,6 +114,12 @@ func testProfileResp(t *testing.T) (resp *profiledb.StorageResponse) { YoutubeSafeSearch: false, } + wantSafeBrowsing := &agd.SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNewlyRegisteredDomains: false, + } + wantLinkedIP := netip.AddrFrom4([4]byte{1, 2, 3, 4}) wantBlockingMode := dnsmsg.BlockingModeCodec{ @@ -139,7 +145,7 @@ func testProfileResp(t *testing.T) (resp *profiledb.StorageResponse) { RuleListIDs: []agd.FilterListID{"1"}, CustomRules: nil, FilteredResponseTTL: 10 * time.Second, - SafeBrowsingEnabled: true, + SafeBrowsing: wantSafeBrowsing, RuleListsEnabled: true, FilteringEnabled: true, QueryLogEnabled: true, @@ -160,7 +166,7 @@ func testProfileResp(t *testing.T) (resp *profiledb.StorageResponse) { RuleListIDs: []agd.FilterListID{"1"}, CustomRules: []agd.FilterRuleText{"||example.org^"}, FilteredResponseTTL: 3600 * time.Second, - SafeBrowsingEnabled: true, + SafeBrowsing: wantSafeBrowsing, RuleListsEnabled: true, FilteringEnabled: true, QueryLogEnabled: true, diff --git a/internal/bindtodevice/connindex_linux_test.go b/internal/bindtodevice/connindex_linux_internal_test.go similarity index 100% rename from internal/bindtodevice/connindex_linux_test.go rename to internal/bindtodevice/connindex_linux_internal_test.go diff --git a/internal/bindtodevice/manager_linux.go b/internal/bindtodevice/manager_linux.go index 567ad5f..e906064 100644 --- a/internal/bindtodevice/manager_linux.go +++ b/internal/bindtodevice/manager_linux.go @@ -10,10 +10,10 @@ import ( "sync" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/mapsutil" ) // Manager creates individual listeners and dispatches connections to them. @@ -80,7 +80,7 @@ func (m *Manager) Add(id ID, ifaceName string, port uint16, conf *ControlConfig) return nil } - err = agdmaps.OrderedRangeError(m.ifaceListeners, validateDup) + err = mapsutil.OrderedRangeError(m.ifaceListeners, validateDup) if err != nil { // Don't wrap the error, because it's informative enough as is. return err diff --git a/internal/cmd/additional.go b/internal/cmd/additional.go index 3fd54c1..cf1b98a 100644 --- a/internal/cmd/additional.go +++ b/internal/cmd/additional.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps" + "github.com/AdguardTeam/golibs/mapsutil" "github.com/prometheus/common/model" ) @@ -14,7 +14,7 @@ type additionalInfo map[string]string // validateAdditionalInfo return an error is the section is invalid. func (c additionalInfo) validate() (err error) { - return agdmaps.OrderedRangeError(c, func(k, _ string) (keyErr error) { + return mapsutil.OrderedRangeError(c, func(k, _ string) (keyErr error) { if model.LabelName(k).IsValid() { return nil } diff --git a/internal/cmd/backend.go b/internal/cmd/backend.go index c1592c3..175b5b5 100644 --- a/internal/cmd/backend.go +++ b/internal/cmd/backend.go @@ -16,6 +16,9 @@ import ( // Business Logic Backend Configuration // backendConfig is the backend module configuration. +// +// TODO(a.garipov): Reorganize this object as there is no longer the only one +// backend environment variable anymore. type backendConfig struct { // Timeout is the timeout for all outgoing HTTP requests. Zero means no // timeout. @@ -34,23 +37,6 @@ type backendConfig struct { BillStatIvl timeutil.Duration `yaml:"bill_stat_interval"` } -// toInternal converts c to the data storage configuration for the DNS server. -// c is assumed to be valid. -func (c *backendConfig) toInternal( - envs *environments, - errColl agd.ErrorCollector, -) (profStrg *backend.ProfileStorageConfig, billStat *backend.BillStatConfig) { - backendEndpoint := &envs.BackendEndpoint.URL - - return &backend.ProfileStorageConfig{ - BaseEndpoint: netutil.CloneURL(backendEndpoint), - Now: time.Now, - ErrColl: errColl, - }, &backend.BillStatConfig{ - BaseEndpoint: netutil.CloneURL(backendEndpoint), - } -} - // validate returns an error if the backend configuration is invalid. func (c *backendConfig) validate() (err error) { switch { @@ -78,13 +64,40 @@ func setupBackend( sigHdlr signalHandler, errColl agd.ErrorCollector, ) (profDB *profiledb.Default, rec *billstat.RuntimeRecorder, err error) { - profStrgConf, billStatConf := conf.toInternal(envs, errColl) + rec, err = setupBillStat(conf, envs, sigHdlr, errColl) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, nil, err + } + + profDB, err = setupProfDB(conf, envs, sigHdlr, errColl) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, nil, err + } + + return profDB, rec, nil +} + +// setupBillStat creates and returns a billing-statistics recorder as well as +// starts and registers its refresher in the signal handler. +func setupBillStat( + conf *backendConfig, + envs *environments, + sigHdlr signalHandler, + errColl agd.ErrorCollector, +) (rec *billstat.RuntimeRecorder, err error) { + billStatConf := &backend.BillStatConfig{ + BaseEndpoint: netutil.CloneURL(&envs.BillStatURL.URL), + } + rec = billstat.NewRuntimeRecorder(&billstat.RuntimeRecorderConfig{ Uploader: backend.NewBillStat(billStatConf), }) refrIvl := conf.RefreshIvl.Duration timeout := conf.Timeout.Duration + billStatRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ Context: func() (ctx context.Context, cancel context.CancelFunc) { return context.WithTimeout(context.Background(), timeout) @@ -98,21 +111,37 @@ func setupBackend( }) err = billStatRefr.Start() if err != nil { - return nil, nil, fmt.Errorf("starting bill stat recorder refresher: %w", err) + return nil, fmt.Errorf("starting bill stat recorder refresher: %w", err) } sigHdlr.add(billStatRefr) - profStrg := backend.NewProfileStorage(profStrgConf) - profDB, err = profiledb.New( - profStrg, - conf.FullRefreshIvl.Duration, - envs.ProfilesCachePath, - ) - if err != nil { - return nil, nil, fmt.Errorf("creating default profile database: %w", err) + return rec, nil +} + +// setupProfDB creates and returns a profile database as well as starts and +// registers its refresher in the signal handler. +func setupProfDB( + conf *backendConfig, + envs *environments, + sigHdlr signalHandler, + errColl agd.ErrorCollector, +) (profDB *profiledb.Default, err error) { + profStrgConf := &backend.ProfileStorageConfig{ + BaseEndpoint: netutil.CloneURL(&envs.ProfilesURL.URL), + Now: time.Now, + ErrColl: errColl, } + profStrg := backend.NewProfileStorage(profStrgConf) + profDB, err = profiledb.New(profStrg, conf.FullRefreshIvl.Duration, envs.ProfilesCachePath) + if err != nil { + return nil, fmt.Errorf("creating default profile database: %w", err) + } + + refrIvl := conf.RefreshIvl.Duration + timeout := conf.Timeout.Duration + profDBRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ Context: func() (ctx context.Context, cancel context.CancelFunc) { return context.WithTimeout(context.Background(), timeout) @@ -126,10 +155,10 @@ func setupBackend( }) err = profDBRefr.Start() if err != nil { - return nil, nil, fmt.Errorf("starting default profile database refresher: %w", err) + return nil, fmt.Errorf("starting default profile database refresher: %w", err) } sigHdlr.add(profDBRefr) - return profDB, rec, nil + return profDB, nil } diff --git a/internal/cmd/cache.go b/internal/cmd/cache.go index b8ccf22..c6d5ba9 100644 --- a/internal/cmd/cache.go +++ b/internal/cmd/cache.go @@ -1,6 +1,10 @@ package cmd -import "fmt" +import ( + "fmt" + + "github.com/AdguardTeam/golibs/timeutil" +) // Cache Configuration @@ -9,6 +13,9 @@ import "fmt" // TODO(a.garipov): Consider adding parameter Enabled or a new Type instead of // relying on Size == 0 to disable cache. type cacheConfig struct { + // TTLOverride is a section with the settings for cache item TTL overrides. + TTLOverride *ttlOverride `yaml:"ttl_override"` + // Type of cache to use. See cacheType* constants. Type string `yaml:"type"` @@ -21,6 +28,16 @@ type cacheConfig struct { ECSSize int `yaml:"ecs_size"` } +// ttlOverride represents TTL override configuration. +type ttlOverride struct { + // Min describes the minimum duration for cache item TTL. + Min timeutil.Duration `yaml:"min"` + + // Enabled returns true if the cache item TTL could be overwritten with Min + // value. + Enabled bool `yaml:"enabled"` +} + // Cache types. const ( cacheTypeECS = "ecs" @@ -42,6 +59,25 @@ func (c *cacheConfig) validate() (err error) { return newMustBeNonNegativeError("size", c.Size) case c.Type == cacheTypeECS && c.ECSSize < 0: return newMustBeNonNegativeError("ecs_size", c.ECSSize) + default: + // Go on. + } + + err = c.TTLOverride.validate() + if err != nil { + return fmt.Errorf("ttl_override: %w", err) + } + + return nil +} + +// validate returns an error if the TTL override configuration is invalid. +func (c *ttlOverride) validate() (err error) { + switch { + case c == nil: + return errNilConfig + case c.Min.Duration <= 0: + return newMustBePositiveError("min", c.Min) default: return nil } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index f8dde60..65cc23f 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -5,10 +5,8 @@ package cmd import ( "context" "fmt" - "math/rand" "os" "runtime" - "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" @@ -32,9 +30,7 @@ import ( func Main() { // Initial Configuration - //lint:ignore SA1019 According to ameshkov, using a non-cryptographically - //secure RNG is fine for things such as random upstream selection. - rand.Seed(time.Now().UnixNano()) + agd.InitRequestID() // Log only to stdout and let users decide how to process it. log.SetOutput(os.Stdout) @@ -93,11 +89,17 @@ func Main() { err = os.MkdirAll(envs.FilterCachePath, agd.DefaultDirPerm) check(err) + // TODO(ameshkov): Consider making a separated max_size config for + // safe-browsing and adult-blocking filters. + maxFilterSize := int64(c.Filters.MaxSize.Bytes()) + safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter( c.SafeBrowsing, filteringResolver, agd.FilterListIDSafeBrowsing, + envs.SafeBrowsingURL, envs.FilterCachePath, + maxFilterSize, sigHdlr, errColl, ) @@ -107,7 +109,22 @@ func Main() { c.AdultBlocking, filteringResolver, agd.FilterListIDAdultBlocking, + envs.AdultBlockingURL, envs.FilterCachePath, + maxFilterSize, + sigHdlr, + errColl, + ) + check(err) + + _, newRegDomainsFilter, err := setupHashPrefixFilter( + // Reuse general safe browsing filter configuration. + c.SafeBrowsing, + filteringResolver, + agd.FilterListIDNewRegDomains, + envs.NewRegDomainsURL, + envs.FilterCachePath, + maxFilterSize, sigHdlr, errColl, ) @@ -121,6 +138,7 @@ func Main() { envs, safeBrowsingFilter, adultBlockingFilter, + newRegDomainsFilter, ) fltRefrTimeout := c.Filters.RefreshTimeout.Duration @@ -174,8 +192,7 @@ func Main() { // DNSDB - dnsDB, err := envs.buildDNSDB(sigHdlr, errColl) - check(err) + dnsDB := c.DNSDB.toInternal(errColl) // Filtering-rule statistics @@ -222,28 +239,31 @@ func Main() { } dnsConf := &dnssvc.Config{ - Messages: messages, - SafeBrowsing: hashprefix.NewMatcher(hashStorages), - BillStat: billStatRec, - ProfileDB: profDB, - DNSCheck: dnsCk, - NonDNS: webSvc, - DNSDB: dnsDB, - ErrColl: errColl, - FilterStorage: fltStrg, - GeoIP: geoIP, - Handler: handler, - QueryLog: c.buildQueryLog(envs), - RuleStat: ruleStat, - RateLimit: rateLimiter, - ConnLimiter: connLimiter, - FilteringGroups: fltGroups, - ServerGroups: srvGrps, - CacheSize: c.Cache.Size, - ECSCacheSize: c.Cache.ECSSize, - UseECSCache: c.Cache.Type == cacheTypeECS, - ResearchMetrics: bool(envs.ResearchMetrics), - ControlConf: ctrlConf, + Messages: messages, + SafeBrowsing: hashprefix.NewMatcher(hashStorages), + BillStat: billStatRec, + ProfileDB: profDB, + DNSCheck: dnsCk, + NonDNS: webSvc, + DNSDB: dnsDB, + ErrColl: errColl, + FilterStorage: fltStrg, + GeoIP: geoIP, + Handler: handler, + QueryLog: c.buildQueryLog(envs), + RuleStat: ruleStat, + RateLimit: rateLimiter, + ConnLimiter: connLimiter, + FilteringGroups: fltGroups, + ServerGroups: srvGrps, + CacheSize: c.Cache.Size, + ECSCacheSize: c.Cache.ECSSize, + CacheMinTTL: c.Cache.TTLOverride.Min.Duration, + UseCacheTTLOverride: c.Cache.TTLOverride.Enabled, + UseECSCache: c.Cache.Type == cacheTypeECS, + ResearchMetrics: bool(envs.ResearchMetrics), + ResearchLogs: bool(envs.ResearchLogs), + ControlConf: ctrlConf, } dnsSvc, err := dnssvc.New(dnsConf) diff --git a/internal/cmd/config.go b/internal/cmd/config.go index f7475bc..3dc1f7a 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -30,6 +30,9 @@ type configuration struct { // Upstream is the configuration of upstream servers for the DNS servers. Upstream *upstreamConfig `yaml:"upstream"` + // DNSDB is the configuration of DNSDB buffer. + DNSDB *dnsDBConfig `yaml:"dnsdb"` + // Backend is the AdGuard HTTP backend service configuration. See the // environments type for more backend parameters. Backend *backendConfig `yaml:"backend"` @@ -114,6 +117,9 @@ func (c *configuration) validate() (err error) { }, { validate: c.Cache.validate, name: "cache", + }, { + validate: c.DNSDB.validate, + name: "dnsdb", }, { validate: c.Backend.validate, name: "backend", diff --git a/internal/cmd/dnsdb.go b/internal/cmd/dnsdb.go new file mode 100644 index 0000000..b59825d --- /dev/null +++ b/internal/cmd/dnsdb.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" +) + +// DNSDB Configuration. + +// dnsDBConfig is the configuration of the DNSDB module. +type dnsDBConfig struct { + // MaxSize is the maximum amount of records in the memory buffer. + MaxSize int `yaml:"max_size"` + + // Enabled describes if the DNSDB memory buffer is enabled. + Enabled bool `yaml:"enabled"` +} + +// validate returns an error if the configuration is invalid. +func (c *dnsDBConfig) validate() (err error) { + switch { + case c == nil: + return errNilConfig + case c.MaxSize <= 0: + return newMustBePositiveError("size", c.MaxSize) + default: + return nil + } +} + +// toInternal builds and returns an anonymous statistics collector. +func (c *dnsDBConfig) toInternal(errColl agd.ErrorCollector) (d dnsdb.Interface) { + if !c.Enabled { + return dnsdb.Empty{} + } + + db := dnsdb.New(&dnsdb.DefaultConfig{ + ErrColl: errColl, + MaxSize: c.MaxSize, + }) + + return db +} diff --git a/internal/cmd/env.go b/internal/cmd/env.go index fd8d26c..defd1f2 100644 --- a/internal/cmd/env.go +++ b/internal/cmd/env.go @@ -24,18 +24,22 @@ import ( // environments represents the configuration that is kept in the environment. type environments struct { - BackendEndpoint *agdhttp.URL `env:"BACKEND_ENDPOINT,notEmpty"` + AdultBlockingURL *agdhttp.URL `env:"ADULT_BLOCKING_URL,notEmpty"` + BillStatURL *agdhttp.URL `env:"BILLSTAT_URL,notEmpty"` BlockedServiceIndexURL *agdhttp.URL `env:"BLOCKED_SERVICE_INDEX_URL,notEmpty"` ConsulAllowlistURL *agdhttp.URL `env:"CONSUL_ALLOWLIST_URL,notEmpty"` ConsulDNSCheckKVURL *agdhttp.URL `env:"CONSUL_DNSCHECK_KV_URL"` ConsulDNSCheckSessionURL *agdhttp.URL `env:"CONSUL_DNSCHECK_SESSION_URL"` FilterIndexURL *agdhttp.URL `env:"FILTER_INDEX_URL,notEmpty"` GeneralSafeSearchURL *agdhttp.URL `env:"GENERAL_SAFE_SEARCH_URL,notEmpty"` - YoutubeSafeSearchURL *agdhttp.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"` + LinkedIPTargetURL *agdhttp.URL `env:"LINKED_IP_TARGET_URL"` + NewRegDomainsURL *agdhttp.URL `env:"NEW_REG_DOMAINS_URL,notEmpty"` + ProfilesURL *agdhttp.URL `env:"PROFILES_URL,notEmpty"` RuleStatURL *agdhttp.URL `env:"RULESTAT_URL"` + SafeBrowsingURL *agdhttp.URL `env:"SAFE_BROWSING_URL,notEmpty"` + YoutubeSafeSearchURL *agdhttp.URL `env:"YOUTUBE_SAFE_SEARCH_URL,notEmpty"` ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"` - DNSDBPath string `env:"DNSDB_PATH"` FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"` ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.json"` GeoIPASNPath string `env:"GEOIP_ASN_PATH" envDefault:"./asn.mmdb"` @@ -51,6 +55,7 @@ type environments struct { LogTimestamp strictBool `env:"LOG_TIMESTAMP" envDefault:"1"` LogVerbose strictBool `env:"VERBOSE" envDefault:"0"` ResearchMetrics strictBool `env:"RESEARCH_METRICS" envDefault:"0"` + ResearchLogs strictBool `env:"RESEARCH_LOGS" envDefault:"0"` } // readEnvs reads the configuration. @@ -97,41 +102,6 @@ func (envs *environments) buildErrColl() (errColl agd.ErrorCollector, err error) return errcoll.NewSentryErrorCollector(cli), nil } -// buildDNSDB builds and returns an anonymous statistics collector and register -// its refresher in sigHdlr, if needed. -func (envs *environments) buildDNSDB( - sigHdlr signalHandler, - errColl agd.ErrorCollector, -) (d dnsdb.Interface, err error) { - if envs.DNSDBPath == "" { - return dnsdb.Empty{}, nil - } - - b := dnsdb.NewBolt(&dnsdb.BoltConfig{ - Path: envs.DNSDBPath, - ErrColl: errColl, - }) - - refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ - Context: ctxWithDefaultTimeout, - Refresher: b, - ErrColl: errColl, - Name: "dnsdb", - // TODO(ameshkov): Consider making configurable. - Interval: 15 * time.Minute, - RefreshOnShutdown: true, - RoutineLogsAreDebug: false, - }) - err = refr.Start() - if err != nil { - return nil, fmt.Errorf("starting dnsdb refresher: %w", err) - } - - sigHdlr.add(refr) - - return b, nil -} - // geoIP returns an GeoIP database implementation from environment. func (envs *environments) geoIP( c *geoIPConfig, @@ -139,10 +109,12 @@ func (envs *environments) geoIP( log.Debug("using geoip files %q and %q", envs.GeoIPASNPath, envs.GeoIPCountryPath) g, err = geoip.NewFile(&geoip.FileConfig{ - ASNPath: envs.GeoIPASNPath, - CountryPath: envs.GeoIPCountryPath, - HostCacheSize: c.HostCacheSize, - IPCacheSize: c.IPCacheSize, + ASNPath: envs.GeoIPASNPath, + CountryPath: envs.GeoIPCountryPath, + HostCacheSize: c.HostCacheSize, + IPCacheSize: c.IPCacheSize, + AllTopASNs: geoip.DefaultTopASNs, + CountryTopASNs: geoip.DefaultCountryTopASNs, }) if err != nil { return nil, err diff --git a/internal/cmd/filter.go b/internal/cmd/filter.go index 7e8511c..44fa1a9 100644 --- a/internal/cmd/filter.go +++ b/internal/cmd/filter.go @@ -11,6 +11,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/timeutil" + "github.com/c2h5oh/datasize" ) // Filters configuration @@ -18,6 +19,9 @@ import ( // filtersConfig contains the configuration for the filter lists and filtering // storage to be used. type filtersConfig struct { + // RuleListCache is the cache settings for the filtering rule-list. + RuleListCache *fltRuleListCache `yaml:"rule_list_cache"` + // CustomFilterCacheSize is the size of the LRU cache of compiled filtering // engines for profiles with custom filtering rules. CustomFilterCacheSize int `yaml:"custom_filter_cache_size"` @@ -25,10 +29,6 @@ type filtersConfig struct { // SafeSearchCacheSize is the size of the LRU cache of safe-search results. SafeSearchCacheSize int `yaml:"safe_search_cache_size"` - // RuleListCacheSize defines the size of the LRU cache of rule-list - // filtering results. - RuleListCacheSize int `yaml:"rule_list_cache_size"` - // ResponseTTL is the TTL to set for DNS responses to requests for filtered // domains. ResponseTTL timeutil.Duration `yaml:"response_ttl"` @@ -42,8 +42,8 @@ type filtersConfig struct { // 30s timeout. RefreshTimeout timeutil.Duration `yaml:"refresh_timeout"` - // UseRuleListCache, if true, enables rule list cache. - UseRuleListCache bool `yaml:"use_rule_list_cache"` + // MaxSize is the maximum size of the downloadable filtering rule-list. + MaxSize datasize.ByteSize `yaml:"max_size"` } // toInternal converts c to the filter storage configuration for the DNS server. @@ -54,6 +54,7 @@ func (c *filtersConfig) toInternal( envs *environments, safeBrowsing *hashprefix.Filter, adultBlocking *hashprefix.Filter, + newRegDomains *hashprefix.Filter, ) (conf *filter.DefaultStorageConfig) { return &filter.DefaultStorageConfig{ FilterIndexURL: netutil.CloneURL(&envs.FilterIndexURL.URL), @@ -62,6 +63,7 @@ func (c *filtersConfig) toInternal( YoutubeSafeSearchRulesURL: netutil.CloneURL(&envs.YoutubeSafeSearchURL.URL), SafeBrowsing: safeBrowsing, AdultBlocking: adultBlocking, + NewRegDomains: newRegDomains, Now: time.Now, ErrColl: errColl, Resolver: resolver, @@ -70,9 +72,10 @@ func (c *filtersConfig) toInternal( SafeSearchCacheSize: c.SafeSearchCacheSize, // TODO(a.garipov): Consider making this configurable. SafeSearchCacheTTL: 1 * time.Hour, - RuleListCacheSize: c.RuleListCacheSize, + RuleListCacheSize: c.RuleListCache.Size, RefreshIvl: c.RefreshIvl.Duration, - UseRuleListCache: c.UseRuleListCache, + UseRuleListCache: c.RuleListCache.Enabled, + MaxRuleListSize: int64(c.MaxSize.Bytes()), } } @@ -83,14 +86,43 @@ func (c *filtersConfig) validate() (err error) { return errNilConfig case c.SafeSearchCacheSize <= 0: return newMustBePositiveError("safe_search_cache_size", c.SafeSearchCacheSize) - case c.RuleListCacheSize <= 0: - return newMustBePositiveError("rule_list_cache_size", c.RuleListCacheSize) case c.ResponseTTL.Duration <= 0: return newMustBePositiveError("response_ttl", c.ResponseTTL) case c.RefreshIvl.Duration <= 0: return newMustBePositiveError("refresh_interval", c.RefreshIvl) case c.RefreshTimeout.Duration <= 0: return newMustBePositiveError("refresh_timeout", c.RefreshTimeout) + case c.MaxSize <= 0: + return newMustBePositiveError("max_size", c.MaxSize) + default: + // Go on. + } + + err = c.RuleListCache.validate() + if err != nil { + return fmt.Errorf("rule_list_cache: %w", err) + } + + return nil +} + +// fltRuleListCache contains filtering rule-list cache configuration. +type fltRuleListCache struct { + // Size defines the size of the LRU cache of rule-list filtering results. + Size int `yaml:"size"` + + // Enabled shows if the rule-list cache is enabled. If it is false, the + // rest of the settings are ignored. + Enabled bool `yaml:"enabled"` +} + +// validate returns an error if the rule-list cache configuration is invalid. +func (c *fltRuleListCache) validate() (err error) { + switch { + case c == nil: + return errNilConfig + case c.Size <= 0: + return newMustBePositiveError("size", c.Size) default: return nil } diff --git a/internal/cmd/filteringgroup.go b/internal/cmd/filteringgroup.go index e1d03cf..7efbc2e 100644 --- a/internal/cmd/filteringgroup.go +++ b/internal/cmd/filteringgroup.go @@ -71,6 +71,14 @@ type fltGrpParental struct { type fltGrpSafeBrowsing struct { // Enabled shows if the general safe browsing filtering should be enforced. Enabled bool `yaml:"enabled"` + + // BlockDangerousDomains shows whether the dangerous domains safe browsing + // filtering should be enforced. + BlockDangerousDomains bool `yaml:"block_dangerous_domains"` + + // BlockNewlyRegisteredDomains shows whether the newly registered domains + // safe browsing filtering should be enforced. + BlockNewlyRegisteredDomains bool `yaml:"block_newly_registered_domains"` } // validate returns an error if the filtering group is invalid. @@ -128,16 +136,18 @@ func (groups filteringGroups) toInternal( id := agd.FilteringGroupID(g.ID) fltGrps[id] = &agd.FilteringGroup{ - ID: id, - RuleListsEnabled: g.RuleLists.Enabled, - RuleListIDs: filterIDs, - ParentalEnabled: g.Parental.Enabled, - BlockAdult: g.Parental.BlockAdult, - SafeBrowsingEnabled: g.SafeBrowsing.Enabled, - GeneralSafeSearch: g.Parental.GeneralSafeSearch, - YoutubeSafeSearch: g.Parental.YoutubeSafeSearch, - BlockPrivateRelay: g.BlockPrivateRelay, - BlockFirefoxCanary: g.BlockFirefoxCanary, + ID: id, + RuleListsEnabled: g.RuleLists.Enabled, + RuleListIDs: filterIDs, + ParentalEnabled: g.Parental.Enabled, + BlockAdult: g.Parental.BlockAdult, + SafeBrowsingEnabled: g.SafeBrowsing.Enabled, + BlockDangerousDomains: g.SafeBrowsing.BlockDangerousDomains, + BlockNewlyRegisteredDomains: g.SafeBrowsing.BlockNewlyRegisteredDomains, + GeneralSafeSearch: g.Parental.GeneralSafeSearch, + YoutubeSafeSearch: g.Parental.YoutubeSafeSearch, + BlockPrivateRelay: g.BlockPrivateRelay, + BlockFirefoxCanary: g.BlockFirefoxCanary, } } diff --git a/internal/cmd/ifacelistener.go b/internal/cmd/ifacelistener.go index 092115b..707c0c4 100644 --- a/internal/cmd/ifacelistener.go +++ b/internal/cmd/ifacelistener.go @@ -2,9 +2,9 @@ package cmd import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps" "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/mapsutil" ) // Network interface listener configuration @@ -35,7 +35,7 @@ func (c *interfaceListenersConfig) toInternal( ChannelBufferSize: c.ChannelBufferSize, }) - err = agdmaps.OrderedRangeError( + err = mapsutil.OrderedRangeError( c.List, func(id bindtodevice.ID, l *interfaceListener) (addErr error) { return errors.Annotate(m.Add(id, l.Interface, l.Port, ctrlConf), "adding listener %q: %w", id) @@ -67,7 +67,7 @@ func (c *interfaceListenersConfig) validate() (err error) { // Go on. } - err = agdmaps.OrderedRangeError( + err = mapsutil.OrderedRangeError( c.List, func(id bindtodevice.ID, l *interfaceListener) (lsnrErr error) { return errors.Annotate(l.validate(), "interface %q: %w", id) diff --git a/internal/cmd/network.go b/internal/cmd/network.go index 232784a..5f50373 100644 --- a/internal/cmd/network.go +++ b/internal/cmd/network.go @@ -3,19 +3,18 @@ package cmd import ( "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" + "github.com/c2h5oh/datasize" ) // network defines the network settings. -// -// TODO(a.garipov): Use [datasize.ByteSize] for sizes. type network struct { - // SndBufSize defines the size of socket send buffer in bytes. Default is - // zero (uses system settings). - SndBufSize int `yaml:"so_sndbuf"` + // SndBufSize defines the size of socket send buffer. Default is zero (uses + // system settings). + SndBufSize datasize.ByteSize `yaml:"so_sndbuf"` - // RcvBufSize defines the size of socket receive buffer in bytes. Default - // is zero (uses system settings). - RcvBufSize int `yaml:"so_rcvbuf"` + // RcvBufSize defines the size of socket receive buffer. Default is zero + // (uses system settings). + RcvBufSize datasize.ByteSize `yaml:"so_rcvbuf"` } // validate returns an error if the network configuration is invalid. @@ -24,14 +23,6 @@ func (n *network) validate() (err error) { return errNilConfig } - if n.SndBufSize < 0 { - return newMustBeNonNegativeError("so_sndbuf", n.SndBufSize) - } - - if n.RcvBufSize < 0 { - return newMustBeNonNegativeError("so_rcvbuf", n.RcvBufSize) - } - return nil } @@ -39,12 +30,12 @@ func (n *network) validate() (err error) { // extension control configuration. func (n *network) toInternal() (bc *bindtodevice.ControlConfig, nc *netext.ControlConfig) { bc = &bindtodevice.ControlConfig{ - SndBufSize: n.SndBufSize, - RcvBufSize: n.RcvBufSize, + SndBufSize: int(n.SndBufSize.Bytes()), + RcvBufSize: int(n.RcvBufSize.Bytes()), } nc = &netext.ControlConfig{ - SndBufSize: n.SndBufSize, - RcvBufSize: n.RcvBufSize, + SndBufSize: int(n.SndBufSize.Bytes()), + RcvBufSize: int(n.RcvBufSize.Bytes()), } return bc, nc diff --git a/internal/cmd/safebrowsing.go b/internal/cmd/safebrowsing.go index 55f9503..d86f72e 100644 --- a/internal/cmd/safebrowsing.go +++ b/internal/cmd/safebrowsing.go @@ -17,9 +17,6 @@ import ( // safeBrowsingConfig is the configuration for one of the safe browsing filters. type safeBrowsingConfig struct { - // URL is the URL used to update the filter. - URL *agdhttp.URL `yaml:"url"` - // BlockHost is the hostname with which to respond to any requests that // match the filter. // @@ -43,7 +40,9 @@ func (c *safeBrowsingConfig) toInternal( errColl agd.ErrorCollector, resolver agdnet.Resolver, id agd.FilterListID, + url *agdhttp.URL, cacheDir string, + maxSize int64, ) (fltConf *hashprefix.FilterConfig, err error) { hashes, err := hashprefix.NewStorage("") if err != nil { @@ -52,7 +51,7 @@ func (c *safeBrowsingConfig) toInternal( return &hashprefix.FilterConfig{ Hashes: hashes, - URL: netutil.CloneURL(&c.URL.URL), + URL: netutil.CloneURL(&url.URL), ErrColl: errColl, Resolver: resolver, ID: id, @@ -61,6 +60,7 @@ func (c *safeBrowsingConfig) toInternal( Staleness: c.RefreshIvl.Duration, CacheTTL: c.CacheTTL.Duration, CacheSize: c.CacheSize, + MaxSize: maxSize, }, nil } @@ -70,8 +70,6 @@ func (c *safeBrowsingConfig) validate() (err error) { switch { case c == nil: return errNilConfig - case c.URL == nil: - return errors.Error("no url") case c.BlockHost == "": return errors.Error("no block_host") case c.CacheSize <= 0: @@ -91,11 +89,13 @@ func setupHashPrefixFilter( conf *safeBrowsingConfig, resolver *agdnet.CachingResolver, id agd.FilterListID, + url *agdhttp.URL, cachePath string, + maxSize int64, sigHdlr signalHandler, errColl agd.ErrorCollector, ) (strg *hashprefix.Storage, flt *hashprefix.Filter, err error) { - fltConf, err := conf.toInternal(errColl, resolver, id, cachePath) + fltConf, err := conf.toInternal(errColl, resolver, id, url, cachePath, maxSize) if err != nil { return nil, nil, fmt.Errorf("configuring hash prefix filter %s: %w", id, err) } diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 1aea133..f43227a 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -204,17 +204,23 @@ func (s *server) bindData( ifaces := s.BindInterfaces bindData = make([]*agd.ServerBindData, 0, len(ifaces)) - for i, iface := range s.BindInterfaces { - var lc netext.ListenConfig - lc, err = btdMgr.ListenConfig(iface.ID, iface.Subnet) - if err != nil { - return nil, fmt.Errorf("bind_interface at index %d: %w", i, err) - } + for i, iface := range ifaces { + address := string(iface.ID) - bindData = append(bindData, &agd.ServerBindData{ - ListenConfig: lc, - Address: string(iface.ID), - }) + for j, subnet := range iface.Subnets { + var lc netext.ListenConfig + lc, err = btdMgr.ListenConfig(iface.ID, subnet) + if err != nil { + const errStr = "bind_interface at index %d: subnet at index %d: %w" + + return nil, fmt.Errorf(errStr, i, j, err) + } + + bindData = append(bindData, &agd.ServerBindData{ + ListenConfig: lc, + Address: address, + }) + } } return bindData, nil @@ -288,8 +294,8 @@ func (s *server) validateBindData() (err error) { // serverBindInterface contains the data for a network interface binding. type serverBindInterface struct { - ID bindtodevice.ID `yaml:"id"` - Subnet netip.Prefix `yaml:"subnet"` + ID bindtodevice.ID `yaml:"id"` + Subnets []netip.Prefix `yaml:"subnets"` } // validate returns an error if the network interface binding configuration is @@ -300,9 +306,26 @@ func (c *serverBindInterface) validate() (err error) { return errNilConfig case c.ID == "": return errors.Error("no id") - case !c.Subnet.IsValid(): - return errors.Error("bad subnet") + case len(c.Subnets) == 0: + return errors.Error("no subnets") default: - return nil + // Go on. } + + set := map[netip.Prefix]struct{}{} + + for i, subnet := range c.Subnets { + if !subnet.IsValid() { + return fmt.Errorf("bad subnet at index %d", i) + } + + _, ok := set[subnet] + if ok { + return fmt.Errorf("duplicate subnet %s at index %d", subnet, i) + } + + set[subnet] = struct{}{} + } + + return nil } diff --git a/internal/cmd/websvc.go b/internal/cmd/websvc.go index be3e6a0..337aeec 100644 --- a/internal/cmd/websvc.go +++ b/internal/cmd/websvc.go @@ -67,17 +67,16 @@ func (c *webConfig) toInternal( } conf = &websvc.Config{ - LinkedIPBackendURL: netutil.CloneURL(&envs.BackendEndpoint.URL), - DNSCheck: dnsCk, - ErrColl: errColl, - Timeout: c.Timeout.Duration, + DNSCheck: dnsCk, + ErrColl: errColl, + Timeout: c.Timeout.Duration, } if c.RootRedirectURL != nil { conf.RootRedirectURL = netutil.CloneURL(&c.RootRedirectURL.URL) } - conf.LinkedIP, err = c.LinkedIP.toInternal() + conf.LinkedIP, err = c.LinkedIP.toInternal(envs.LinkedIPTargetURL) if err != nil { return nil, fmt.Errorf("converting linked_ip: %w", err) } @@ -179,7 +178,9 @@ type linkedIPServer struct { // toInternal converts s to a linkedIP server configuration. s is assumed to be // valid. -func (s *linkedIPServer) toInternal() (srv *websvc.LinkedIPServer, err error) { +func (s *linkedIPServer) toInternal( + targetURL *agdhttp.URL, +) (srv *websvc.LinkedIPServer, err error) { if s == nil { return nil, nil } @@ -190,6 +191,12 @@ func (s *linkedIPServer) toInternal() (srv *websvc.LinkedIPServer, err error) { return nil, fmt.Errorf("converting bind: %w", err) } + if targetURL == nil { + return nil, fmt.Errorf("env variable LINKED_IP_TARGET_URL must be set for using linked_ip") + } + + srv.TargetURL = netutil.CloneURL(&targetURL.URL) + return srv, nil } diff --git a/internal/connlimiter/limiter_test.go b/internal/connlimiter/limiter_test.go index 568e974..840f4ce 100644 --- a/internal/connlimiter/limiter_test.go +++ b/internal/connlimiter/limiter_test.go @@ -6,11 +6,11 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/connlimiter" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" + "github.com/AdguardTeam/golibs/testutil/fakenet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -36,7 +36,7 @@ func TestLimiter(t *testing.T) { }) require.NoError(t, err) - conn := &agdtest.Conn{ + conn := &fakenet.Conn{ OnClose: func() (err error) { return nil }, OnLocalAddr: func() (laddr net.Addr) { panic("not implemented") }, OnRead: func(b []byte) (n int, err error) { panic("not implemented") }, @@ -52,7 +52,7 @@ func TestLimiter(t *testing.T) { OnWrite: func(b []byte) (n int, err error) { panic("not implemented") }, } - lsnr := &agdtest.Listener{ + lsnr := &fakenet.Listener{ OnAccept: func() (c net.Conn, err error) { return conn, nil }, OnAddr: func() (addr net.Addr) { return &net.TCPAddr{ diff --git a/internal/connlimiter/listenconfig_test.go b/internal/connlimiter/listenconfig_test.go index 58a2e4e..e2833fb 100644 --- a/internal/connlimiter/listenconfig_test.go +++ b/internal/connlimiter/listenconfig_test.go @@ -9,12 +9,13 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/connlimiter" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/golibs/testutil/fakenet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestListenConfig(t *testing.T) { - pc := &agdtest.PacketConn{ + pc := &fakenet.PacketConn{ OnClose: func() (err error) { panic("not implemented") }, OnLocalAddr: func() (laddr net.Addr) { panic("not implemented") }, OnReadFrom: func(b []byte) (n int, addr net.Addr, err error) { panic("not implemented") }, @@ -24,7 +25,7 @@ func TestListenConfig(t *testing.T) { OnWriteTo: func(b []byte, addr net.Addr) (n int, err error) { panic("not implemented") }, } - lsnr := &agdtest.Listener{ + lsnr := &fakenet.Listener{ OnAccept: func() (c net.Conn, err error) { panic("not implemented") }, OnAddr: func() (addr net.Addr) { panic("not implemented") }, OnClose: func() (err error) { return nil }, diff --git a/internal/debugsvc/debugsvc.go b/internal/debugsvc/debugsvc.go index 8d4ab42..39e7b12 100644 --- a/internal/debugsvc/debugsvc.go +++ b/internal/debugsvc/debugsvc.go @@ -6,11 +6,11 @@ import ( "fmt" "io" "net/http" - "net/http/pprof" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/pprofutil" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -143,7 +143,7 @@ func (svc *Service) addHandler(serviceName string, mux *http.ServeMux) { case "health-check": healthMux(mux) case "pprof": - pprofMux(mux) + pprofutil.RoutePprof(mux) case "prometheus": promMux(mux) default: @@ -166,23 +166,6 @@ func healthMux(mux *http.ServeMux) { ) } -// pprofMux adds handler func to the mux from args for the pprof service. -func pprofMux(mux *http.ServeMux) { - mux.HandleFunc("/debug/pprof/", pprof.Index) - - mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - - mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs")) - mux.Handle("/debug/pprof/block", pprof.Handler("block")) - mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) - mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) - mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex")) - mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) -} - // promMux adds handler func to the mux from args for the prometheus service. func promMux(mux *http.ServeMux) { mux.Handle("/metrics", promhttp.Handler()) diff --git a/internal/dnscheck/dnscheck.go b/internal/dnscheck/dnscheck.go index 87ffed7..ed9e63a 100644 --- a/internal/dnscheck/dnscheck.go +++ b/internal/dnscheck/dnscheck.go @@ -98,8 +98,14 @@ func isInvalidRandomIDRune(r rune) (ok bool) { // isValidRandomIDRune returns true if r is a valid random ID rune. func isValidRandomIDRune(r rune) (ok bool) { - return (r >= 'a' && r <= 'z') || - (r >= 'A' && r <= 'Z') || - (r >= '0' && r <= '9') || - r == '-' + switch { + case + r >= 'a' && r <= 'z', + r >= 'A' && r <= 'Z', + r >= '0' && r <= '9', + r == '-': + return true + default: + return false + } } diff --git a/internal/dnsdb/bolt.go b/internal/dnsdb/bolt.go deleted file mode 100644 index f953cd5..0000000 --- a/internal/dnsdb/bolt.go +++ /dev/null @@ -1,286 +0,0 @@ -package dnsdb - -import ( - "context" - "fmt" - "os" - "path/filepath" - "sync" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" - "github.com/miekg/dns" - "go.etcd.io/bbolt" -) - -// BoldDB-Based DNSDB - -// recordBuffer is an in-memory buffer of DNSDB records. -type recordBuffer map[string][]*record - -// Bolt is BoltDB-based DNSDB implementation. -type Bolt struct { - // dumpMu is used to make sure that the CSV endpoint is only used by one - // client at a time, as well as that the periodic flushing doesn't interfere - // with it. - // - // TODO(a.garipov): Consider adding rate limiting. - dumpMu *sync.Mutex - - bufferMu *sync.Mutex - buffer recordBuffer - - errColl agd.ErrorCollector - - path string -} - -// BoltConfig is the BoltDB-based DNSDB configuration structure. -type BoltConfig struct { - // ErrColl is used to collect HTTP errors as well as - ErrColl agd.ErrorCollector - - // Path is the path to the BoltDB file. - Path string -} - -// NewBolt creates a new BoltDB-based DNSDB. c must not be nil. -func NewBolt(c *BoltConfig) (db *Bolt) { - db = &Bolt{ - dumpMu: &sync.Mutex{}, - - bufferMu: &sync.Mutex{}, - buffer: recordBuffer{}, - - errColl: c.ErrColl, - - path: c.Path, - } - - return db -} - -// type check -var _ Interface = (*Bolt)(nil) - -// Record implements the Interface interface for *Bolt. It saves a DNS response -// to its in-memory buffer. -func (db *Bolt) Record(ctx context.Context, m *dns.Msg, ri *agd.RequestInfo) { - if isIgnoredMessage(m) { - return - } - - q := m.Question[0] - if isIgnoredQuestion(q) { - return - } - - key := requestKey(ri.Host, q.Qtype) - recs := toDBRecords(q.Qtype, ri.Host, m.Answer, dnsmsg.RCode(m.Rcode)) - db.saveToBuffer(key, recs) -} - -// saveToBuffer saves recs to the in-memory buffer. -func (db *Bolt) saveToBuffer(key string, recs []*record) { - db.bufferMu.Lock() - defer db.bufferMu.Unlock() - - prevRecs, ok := db.buffer[key] - if !ok { - db.buffer[key] = recs - metrics.DNSDBBufferSize.Inc() - - return - } - - // Consider that new answers either don't appear between rotations of the - // database or don't matter, and so don't merge new records with the old - // ones. Just bump the hit counters. - for _, r := range prevRecs { - r.Hits++ - } -} - -// type check -var _ agd.Refresher = (*Bolt)(nil) - -// Refresh implements the agd.Refresher interface for *Bolt. It flushes the -// current in-memory buffer data to disk. -func (db *Bolt) Refresh(ctx context.Context) (err error) { - db.dumpMu.Lock() - defer db.dumpMu.Unlock() - - _, err = db.flush(ctx, false) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return err - } - - return nil -} - -// flush saves the buffered records to the local BoltDB database. If rotate is -// true, it also rotates the database file and returns the path to the previous -// database file. -func (db *Bolt) flush(ctx context.Context, rotate bool) (prev string, err error) { - start := time.Now() - // TODO(a.garipov): Consider only replacing the buffer with an empty one if - // the write was successful. - buffer := db.replaceBuffer() - bdb, err := openBolt(db.path) - if err != nil { - return "", fmt.Errorf("opening boltdb: %w", err) - } - defer func() { err = errors.WithDeferred(err, bdb.Close()) }() - - err = bdb.Batch(boltFlushFunc(ctx, buffer, db.errColl)) - if err != nil { - return "", fmt.Errorf("saving: %w", err) - } - - if rotate { - prev, err = db.rotate() - if err != nil { - return prev, fmt.Errorf("rotating: %w", err) - } - } - - metrics.DNSDBSaveDuration.Observe(time.Since(start).Seconds()) - - return prev, nil -} - -// replaceBuffer replaced db's current buffer with a new one and returns the -// previous one. -func (db *Bolt) replaceBuffer() (prev recordBuffer) { - db.bufferMu.Lock() - defer db.bufferMu.Unlock() - - prev, db.buffer = db.buffer, recordBuffer{} - metrics.DNSDBBufferSize.Set(0) - - return prev -} - -// recordsBucket is the name of the bucket with the DNSDB records. -const recordsBucket = "records" - -// openBolt opens and initializes the BoltDB file. -func openBolt(dbPath string) (bdb *bbolt.DB, err error) { - bdb, err = bbolt.Open(dbPath, agd.DefaultPerm, nil) - if err != nil { - return nil, fmt.Errorf("opening file: %w", err) - } - - err = bdb.Update(func(tx *bbolt.Tx) (ferr error) { - _, ferr = tx.CreateBucketIfNotExists([]byte(recordsBucket)) - - return ferr - }) - if err != nil { - return nil, fmt.Errorf("initializing: %w", err) - } - - return bdb, nil -} - -// boltTxFunc is an alias for the type consumed by bbolt.(*DB).Batch. -type boltTxFunc = func(tx *bbolt.Tx) (err error) - -// boltFlushFunc returns a function that reads data from the existing DNSDB -// file, updates the current buffer, and writes it back. -// -// f always returns nil; all errors are reported using errColl. -func boltFlushFunc( - ctx context.Context, - buffer recordBuffer, - errColl agd.ErrorCollector, -) (f boltTxFunc) { - return func(tx *bbolt.Tx) (err error) { - b := tx.Bucket([]byte(recordsBucket)) - - for rk, recs := range buffer { - k := []byte(rk) - err = addExisting(b, k, recs) - if err != nil { - agd.Collectf(ctx, errColl, "dnsdb: adding existing data for %s: %w", rk, err) - - // Consider errors from reading the previous database - // non-critical. - } - - var dbData []byte - dbData, err = encode(recs) - if err != nil { - agd.Collectf(ctx, errColl, "dnsdb: encoding data for %s: %w", rk, err) - - continue - } - - err = b.Put(k, dbData) - if err != nil { - agd.Collectf(ctx, errColl, "dnsdb: writing data for %s: %w", rk, err) - - // Consider errors from writing a single key non-critical. - } - } - - metrics.DNSDBSize.Set(float64(b.Stats().KeyN)) - - return nil - } -} - -// addExisting looks up previous data for key in b and updates recs with those -// data if there are any. -func addExisting(b *bbolt.Bucket, key []byte, recs []*record) (err error) { - prevData := b.Get(key) - if len(prevData) == 0 { - return nil - } - - var prevRecs []*record - prevRecs, err = decode(prevData) - if err != nil { - return fmt.Errorf("decoding previous value: %w", err) - } - - if len(prevRecs) == 0 { - return nil - } - - // Use only the first Hits value, because all records share it. - prevHits := prevRecs[0].Hits - for _, r := range recs { - r.Hits += prevHits - } - - return nil -} - -// requestKey returns a key for identifying a request. -func requestKey(name string, qt dnsmsg.RRType) (key string) { - return name + "_" + dns.TypeToString[qt] -} - -// rotate moves the current DB file to a temporary file and returns the path to -// that temporary file. -func (db *Bolt) rotate() (prev string, err error) { - prevBase := fmt.Sprintf("%s.%d", filepath.Base(db.path), time.Now().Unix()) - prev = filepath.Join(os.TempDir(), prevBase) - err = os.Rename(db.path, prev) - if err != nil { - return "", fmt.Errorf("renaming prev db: %w", err) - } - - log.Info("dnsdb: renamed %q to %q", db.path, prev) - - metrics.DNSDBSize.Set(0) - metrics.DNSDBRotateTime.SetToCurrentTime() - - return prev, nil -} diff --git a/internal/dnsdb/bolthttp.go b/internal/dnsdb/bolthttp.go deleted file mode 100644 index 0f32a15..0000000 --- a/internal/dnsdb/bolthttp.go +++ /dev/null @@ -1,139 +0,0 @@ -package dnsdb - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/csv" - "encoding/gob" - "fmt" - "io" - "net/http" - "os" - "strings" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/httphdr" - "go.etcd.io/bbolt" -) - -// BoltDB-Based DNSDB HTTP Handler - -// type check -var _ http.Handler = (*Bolt)(nil) - -// ServeHTTP implements the http.Handler interface for *Bolt. -func (db *Bolt) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - db.dumpMu.Lock() - defer db.dumpMu.Unlock() - - dbPath, err := db.flush(ctx, true) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - - return - } - - h := w.Header() - h.Add(httphdr.ContentType, agdhttp.HdrValTextCSV) - - if dbPath == "" { - // No data. - w.WriteHeader(http.StatusOK) - - return - } - - h.Set(httphdr.Trailer, httphdr.XError) - defer func() { - if err != nil { - h.Set(httphdr.XError, err.Error()) - agd.Collectf(ctx, db.errColl, "dnsdb: http handler error: %w", err) - } - }() - - defer func() { err = errors.WithDeferred(err, os.Remove(dbPath)) }() - - var rw io.Writer = w - // TODO(a.garipov): Consider parsing the quality value. - if strings.Contains(r.Header.Get(httphdr.AcceptEncoding), "gzip") { - h.Set(httphdr.ContentEncoding, "gzip") - gw := gzip.NewWriter(w) - defer func() { err = errors.WithDeferred(err, gw.Close()) }() - - rw = gw - } - - w.WriteHeader(http.StatusOK) - - err = db.dumpToCSV(ctx, rw, dbPath) -} - -// dumpToCSV converts the DNSDB at dbPath to CSV and writes it into w. It -// writes every record as it processes it. -func (db *Bolt) dumpToCSV(ctx context.Context, w io.Writer, dbPath string) (err error) { - bdb, err := openBolt(dbPath) - if errors.Is(err, os.ErrNotExist) { - return nil - } else if err != nil { - return fmt.Errorf("opening boltdb: %w", err) - } - defer func() { err = errors.WithDeferred(err, bdb.Close()) }() - - err = bdb.Batch(boltDumpFunc(ctx, w, db.errColl)) - if err != nil { - return fmt.Errorf("dumping db: %w", err) - } - - return nil -} - -// boltDumpFunc returns a function that reads data from the existing DNSDB file -// and writes it into w as CSV records, one at a time. -// -// Decoding errors are reported using errColl. -func boltDumpFunc(ctx context.Context, w io.Writer, errColl agd.ErrorCollector) (f boltTxFunc) { - return func(tx *bbolt.Tx) (err error) { - b := tx.Bucket([]byte(recordsBucket)) - if b == nil { - return errors.Error("records bucket not found") - } - - csvw := csv.NewWriter(w) - defer csvw.Flush() - - c := b.Cursor() - for rk, v := c.First(); rk != nil; rk, v = c.Next() { - var recs []*record - err = gob.NewDecoder(bytes.NewReader(v)).Decode(&recs) - if err != nil { - agd.Collectf(ctx, errColl, "dnsdb: decoding data for %s: %w", rk, err) - - continue - } - - err = writeCSVRecs(csvw, recs) - if err != nil { - return fmt.Errorf("writing record for key %s: %w", rk, err) - } - } - - return nil - } -} - -// writeCSVRecs writes the CSV representation of recs into w. -func writeCSVRecs(w *csv.Writer, recs []*record) (err error) { - for i, r := range recs { - err = w.Write(r.csv()) - if err != nil { - return fmt.Errorf("record at index %d: %w", i, err) - } - } - - return nil -} diff --git a/internal/dnsdb/buffer.go b/internal/dnsdb/buffer.go new file mode 100644 index 0000000..78f4ce5 --- /dev/null +++ b/internal/dnsdb/buffer.go @@ -0,0 +1,106 @@ +package dnsdb + +import ( + "sync" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/miekg/dns" +) + +// buffer contains the approximate statistics for DNS answers. It saves data +// until it reaches maxSize, upon which it can only increase the hits of the +// previous records. +type buffer struct { + // mu protects entries. + mu *sync.Mutex + + // entries is the data of the statistics. + entries map[recordKey]*recordValue + + // maxSize is the maximum length of entries. + maxSize int +} + +// add increments the records for all answers. +func (b *buffer) add(target string, answers []dns.RR, qt dnsmsg.RRType, rc dnsmsg.RCode) { + b.mu.Lock() + defer b.mu.Unlock() + + // Do nothing if the buffer is already full. + l := len(b.entries) + if l >= b.maxSize { + return + } + + key := recordKey{ + target: target, + qt: qt, + } + + prev, ok := b.entries[key] + if ok { + prev.hits++ + + // Note, that only the first set of answers is stored in the buffer. + // If a more detailed response is needed, maps.Copy can be used to + // achieve that. + + return + } + + b.entries[key] = &recordValue{ + answers: toAnswerSet(answers, rc), + hits: 1, + } + + metrics.DNSDBBufferSize.Set(float64(l + 1)) +} + +// all returns buffered records. +func (b *buffer) all() (records []*record) { + b.mu.Lock() + defer b.mu.Unlock() + + for key, val := range b.entries { + if len(val.answers) == 0 { + records = append(records, &record{ + target: key.target, + hits: val.hits, + rrType: key.qt, + }) + + continue + } + + for a := range val.answers { + records = append(records, &record{ + target: key.target, + answer: a.value, + hits: val.hits, + rrType: a.rrType, + rcode: a.rcode, + }) + } + } + + return records +} + +// toAnswerSet converts a slice of [dns.RR] to a map that can easier be +// serialized to a csv. +func toAnswerSet(answers []dns.RR, rc dnsmsg.RCode) (answerSet map[recordAnswer]unit) { + answerSet = map[recordAnswer]unit{} + for _, a := range answers { + ansStr := answerString(a) + if ansStr != "" { + answerSet[recordAnswer{ + value: ansStr, + rrType: a.Header().Rrtype, + rcode: rc, + }] = unit{} + } + } + + return answerSet +} diff --git a/internal/dnsdb/dnsdb.go b/internal/dnsdb/dnsdb.go index 29c9d16..0d51ac7 100644 --- a/internal/dnsdb/dnsdb.go +++ b/internal/dnsdb/dnsdb.go @@ -1,12 +1,19 @@ // Package dnsdb contains types and utilities for collecting anonymous // statistics about the Internet. +// +// TODO(a.garipov): This needs way more tests. package dnsdb import ( "context" + "sync" + "sync/atomic" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/miekg/dns" ) @@ -27,7 +34,77 @@ var _ Interface = Empty{} // Record implements the Interface interface for Empty. func (Empty) Record(_ context.Context, _ *dns.Msg, _ *agd.RequestInfo) {} -// isIgnoredMessage returns true if m must be ignored by DNSDB. +// Default is the default DNSDB implementation. +type Default struct { + buffer *atomic.Pointer[buffer] + errColl agd.ErrorCollector + maxSize int +} + +// DefaultConfig is the default DNS database configuration structure. +type DefaultConfig struct { + // ErrColl is used to collect HTTP errors. + ErrColl agd.ErrorCollector + + // MaxSize is the maximum amount of records in the memory buffer. + MaxSize int +} + +// New creates a new default DNS database. c must not be nil. +func New(c *DefaultConfig) (db *Default) { + db = &Default{ + buffer: &atomic.Pointer[buffer]{}, + errColl: c.ErrColl, + maxSize: c.MaxSize, + } + + db.buffer.Store(&buffer{ + mu: &sync.Mutex{}, + entries: map[recordKey]*recordValue{}, + maxSize: db.maxSize, + }) + + return db +} + +// type check +var _ Interface = (*Default)(nil) + +// Record implements the Interface interface for *Default. It saves a DNS +// response to its in-memory buffer. +func (db *Default) Record(ctx context.Context, m *dns.Msg, ri *agd.RequestInfo) { + if isIgnoredMessage(m) { + return + } + + q := m.Question[0] + if isIgnoredQuestion(q) { + return + } + + db.buffer.Load().add(ri.Host, m.Answer, q.Qtype, dnsmsg.RCode(m.Rcode)) +} + +// reset returns buffered records and resets the database. +func (db *Default) reset() (records []*record) { + start := time.Now() + + prevBuf := db.buffer.Swap(&buffer{ + mu: &sync.Mutex{}, + entries: map[recordKey]*recordValue{}, + maxSize: db.maxSize, + }) + + records = prevBuf.all() + + metrics.DNSDBBufferSize.Set(0) + metrics.DNSDBRotateTime.SetToCurrentTime() + metrics.DNSDBSaveDuration.Observe(time.Since(start).Seconds()) + + return records +} + +// isIgnoredMessage returns true if m must be ignored by DNS database. func isIgnoredMessage(m *dns.Msg) (ok bool) { return m == nil || !m.Response || @@ -35,7 +112,7 @@ func isIgnoredMessage(m *dns.Msg) (ok bool) { m.Rcode != dns.RcodeSuccess } -// isIgnoredQuestion returns true if q must be ignored by DNSDB. +// isIgnoredQuestion returns true if q must be ignored by DNS database. func isIgnoredQuestion(q dns.Question) (ok bool) { return (q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA) || // Android metric domain must be ignored by DNSDB to avoid filling it diff --git a/internal/dnsdb/http.go b/internal/dnsdb/http.go new file mode 100644 index 0000000..f63aafd --- /dev/null +++ b/internal/dnsdb/http.go @@ -0,0 +1,68 @@ +package dnsdb + +import ( + "compress/gzip" + "encoding/csv" + "fmt" + "io" + "net/http" + "strings" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/httphdr" +) + +// Default DNS database HTTP Handler + +// type check +var _ http.Handler = (*Default)(nil) + +// ServeHTTP implements the http.Handler interface for *Default. +func (db *Default) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var err error + ctx := r.Context() + + records := db.reset() + + h := w.Header() + h.Add(httphdr.ContentType, agdhttp.HdrValTextCSV) + + h.Set(httphdr.Trailer, httphdr.XError) + defer func() { + if err != nil { + h.Set(httphdr.XError, err.Error()) + agd.Collectf(ctx, db.errColl, "dnsdb: http handler error: %w", err) + } + }() + + var rw io.Writer = w + // TODO(a.garipov): Consider parsing the quality value. + if strings.Contains(r.Header.Get(httphdr.AcceptEncoding), "gzip") { + h.Set(httphdr.ContentEncoding, "gzip") + gw := gzip.NewWriter(w) + defer func() { err = errors.WithDeferred(err, gw.Close()) }() + + rw = gw + } + + w.WriteHeader(http.StatusOK) + + csvw := csv.NewWriter(rw) + defer csvw.Flush() + + err = writeCSVRecs(csvw, records) +} + +// writeCSVRecs writes the CSV representation of recs into w. +func writeCSVRecs(w *csv.Writer, recs []*record) (err error) { + for i, r := range recs { + err = w.Write(r.csv()) + if err != nil { + return fmt.Errorf("record at index %d: %w", i, err) + } + } + + return nil +} diff --git a/internal/dnsdb/bolt_test.go b/internal/dnsdb/http_test.go similarity index 65% rename from internal/dnsdb/bolt_test.go rename to internal/dnsdb/http_test.go index a5bc228..6bec674 100644 --- a/internal/dnsdb/bolt_test.go +++ b/internal/dnsdb/http_test.go @@ -1,13 +1,14 @@ package dnsdb_test import ( + "bytes" "compress/gzip" "context" "io" + "net" "net/http" "net/http/httptest" "net/url" - "os" "strings" "testing" @@ -22,23 +23,9 @@ import ( "github.com/stretchr/testify/require" ) -// newTmpBolt creates a *dnsdb.Bolt with temporary DB file. -func newTmpBolt(t *testing.T) (db *dnsdb.Bolt) { - tmpFile, err := os.CreateTemp(t.TempDir(), "*") - require.NoError(t, err) - - conf := &dnsdb.BoltConfig{ - ErrColl: &agdtest.ErrorCollector{ - OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, - }, - Path: tmpFile.Name(), - } - - return dnsdb.NewBolt(conf) -} - -func TestBolt_ServeHTTP(t *testing.T) { +func TestDefault_ServeHTTP(t *testing.T) { const dname = "some-domain.name" + testIP := net.IP{1, 2, 3, 4} successHdr := http.Header{ httphdr.ContentType: []string{agdhttp.HdrValTextCSV}, @@ -47,21 +34,27 @@ func TestBolt_ServeHTTP(t *testing.T) { } newMsg := func(rcode int, name string, qtype uint16) (m *dns.Msg) { - return dnsservertest.NewResp(rcode, dnsservertest.NewReq(name, qtype, dns.ClassINET)) + return dnsservertest.NewResp( + rcode, + dnsservertest.NewReq(name, qtype, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewA(dname, 0, testIP), + }, + ) } testCases := []struct { name string msgs []*dns.Msg wantHdr http.Header - wantResp []byte + wantResp [][]byte }{{ name: "single", msgs: []*dns.Msg{ newMsg(dns.RcodeSuccess, dname, dns.TypeA), }, wantHdr: successHdr, - wantResp: []byte(dname + `,A,NOERROR,,1` + "\n"), + wantResp: [][]byte{[]byte(dname + `,A,NOERROR,` + testIP.String() + `,1`)}, }, { name: "existing", msgs: []*dns.Msg{ @@ -69,7 +62,7 @@ func TestBolt_ServeHTTP(t *testing.T) { newMsg(dns.RcodeSuccess, dname, dns.TypeA), }, wantHdr: successHdr, - wantResp: []byte(dname + `,A,NOERROR,,2` + "\n"), + wantResp: [][]byte{[]byte(dname + `,A,NOERROR,` + testIP.String() + `,2`)}, }, { name: "different", msgs: []*dns.Msg{ @@ -77,8 +70,10 @@ func TestBolt_ServeHTTP(t *testing.T) { newMsg(dns.RcodeSuccess, "sub."+dname, dns.TypeA), }, wantHdr: successHdr, - wantResp: []byte(dname + `,A,NOERROR,,1` + "\n" + - "sub." + dname + `,A,NOERROR,,1` + "\n"), + wantResp: [][]byte{ + []byte("sub." + dname + `,A,NOERROR,` + testIP.String() + `,1`), + []byte(dname + `,A,NOERROR,` + testIP.String() + `,1`), + }, }, { name: "non-recordable", msgs: []*dns.Msg{ @@ -90,15 +85,12 @@ func TestBolt_ServeHTTP(t *testing.T) { newMsg(dns.RcodeSuccess, dname+"-dnsotls-ds.metric.gstatic.com.", dns.TypeA), }, wantHdr: successHdr, - wantResp: []byte{}, + wantResp: [][]byte{}, }} - recordAndRefresh := func( + record := func( t *testing.T, - db interface { - dnsdb.Interface - agd.Refresher - }, + db dnsdb.Interface, msgs []*dns.Msg, ) { t.Helper() @@ -111,9 +103,6 @@ func TestBolt_ServeHTTP(t *testing.T) { // See [dnssvc.initMw.newRequestInfo]. Host: strings.TrimSuffix(m.Question[0].Name, "."), }) - - err := db.Refresh(context.Background()) - require.NoError(t, err) } } @@ -125,11 +114,16 @@ func TestBolt_ServeHTTP(t *testing.T) { r.Header.Add(httphdr.AcceptEncoding, "gzip") for _, tc := range testCases { - db := newTmpBolt(t) + db := dnsdb.New(&dnsdb.DefaultConfig{ + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + MaxSize: 100, + }) rw := httptest.NewRecorder() t.Run(tc.name, func(t *testing.T) { - recordAndRefresh(t, db, tc.msgs) + record(t, db, tc.msgs) db.ServeHTTP(rw, r) require.Equal(t, http.StatusOK, rw.Code) @@ -143,25 +137,8 @@ func TestBolt_ServeHTTP(t *testing.T) { decResp, err = io.ReadAll(gzipr) require.NoError(t, err) - assert.Equal(t, tc.wantResp, decResp) + lines := bytes.Split(decResp, []byte("\n")) + assert.ElementsMatch(t, tc.wantResp, lines[:len(lines)-1]) }) } - - t.Run("bad_db_path", func(t *testing.T) { - db := dnsdb.NewBolt(&dnsdb.BoltConfig{ - Path: "bad/path", - ErrColl: &agdtest.ErrorCollector{ - OnCollect: func(ctx context.Context, err error) { panic("not implemented") }, - }, - }) - - w := httptest.NewRecorder() - - db.ServeHTTP(w, r) - assert.Equal( - t, - "opening boltdb: opening file: open bad/path: no such file or directory\n", - w.Body.String(), - ) - }) } diff --git a/internal/dnsdb/record.go b/internal/dnsdb/record.go index 490534c..8975034 100644 --- a/internal/dnsdb/record.go +++ b/internal/dnsdb/record.go @@ -1,8 +1,6 @@ package dnsdb import ( - "bytes" - "encoding/gob" "strconv" "strings" @@ -10,35 +8,30 @@ import ( "github.com/miekg/dns" ) -// DNSDB Records - -// record is a single DNSDB record as it is stored in the BoldDB database. -// -// DO NOT change the names of the fields, since gob is used to encode it. +// record is a single DNSDB record as it is stored in the record's database. type record struct { - // DomainName is the question FQDN from the request. - DomainName string + // target is the question target from the request. + target string - // Answer is either the IP address (for A and AAAA responses) or the + // answer is either the IP address (for A and AAAA responses) or the // hostname (for CNAME responses). // // If there are no answers, this field is empty. - Answer string + answer string - // Hits shows how many times this domain was requested. All records with - // the same DomainName share this value. - Hits uint64 + // hits shows how many times this domain was requested. + hits uint64 - // RRType is the resource record type of the answer. Currently, only A, + // rrType is the resource record type of the answer. Currently, only A, // AAAA, and CNAME responses are recorded. // - // If there are no answers, RRType is the type of the resource record type + // If there are no answers, rrType is the type of the resource record type // of the question instead. - RRType dnsmsg.RRType + rrType dnsmsg.RRType - // RCode is the response code. Currently we only record successful queries, + // rcode is the response code. Currently we only record successful queries, // but that may change if the future. - RCode dnsmsg.RCode + rcode dnsmsg.RCode } // csv returns CSV fields containing the record's information in the predefined @@ -47,86 +40,46 @@ func (r *record) csv() (fields []string) { // DO NOT change the order of fields, since other parts of the system depend // on it. return []string{ - r.DomainName, - dns.TypeToString[r.RRType], - dns.RcodeToString[int(r.RCode)], - r.Answer, - strconv.FormatUint(r.Hits, 10), + r.target, + dns.TypeToString[r.rrType], + dns.RcodeToString[int(r.rcode)], + r.answer, + strconv.FormatUint(r.hits, 10), } } -// encode encodes a slice of DNSDB records using gob. -func encode(recs []*record) (b []byte, err error) { - buf := &bytes.Buffer{} - err = gob.NewEncoder(buf).Encode(recs) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// decode decodes a slice of DNSDB records from gob data. -func decode(b []byte) (recs []*record, err error) { - r := bytes.NewReader(b) - err = gob.NewDecoder(r).Decode(&recs) - if err != nil { - return nil, err - } - - return recs, nil -} - -// toDBRecords converts DNS query data into a slice of DNSDB records. -func toDBRecords(qt dnsmsg.RRType, name string, ans []dns.RR, rc dnsmsg.RCode) (recs []*record) { - if len(ans) == 0 { - return []*record{newDBRecord(qt, name, nil, rc)} - } - - for _, rr := range ans { - if isAcceptedRRType(rr) { - recs = append(recs, newDBRecord(qt, name, rr, rc)) - } - } - - return recs -} - -// isAcceptedRRType returns true if rr has one of the accepted answer resource -// record types. -func isAcceptedRRType(rr dns.RR) (ok bool) { - switch rr.(type) { - case *dns.A, *dns.AAAA, *dns.CNAME: - return true - default: - return false - } -} - -// newDBRecord converts the DNS message data to a DNSDB record. -func newDBRecord(qt dnsmsg.RRType, name string, rr dns.RR, rc dnsmsg.RCode) (rec *record) { - rec = &record{ - DomainName: name, - Hits: 1, - RCode: rc, - } - - if rr == nil { - rec.RRType = qt - - return rec - } - - rec.RRType = rr.Header().Rrtype - +// answerString returns a string representation of an answer record. +func answerString(rr dns.RR) (s string) { switch v := rr.(type) { case *dns.A: - rec.Answer = v.A.String() + return v.A.String() case *dns.AAAA: - rec.Answer = v.AAAA.String() + return v.AAAA.String() case *dns.CNAME: - rec.Answer = strings.TrimSuffix(v.Target, ".") + return strings.TrimSuffix(v.Target, ".") + default: + return "" } - - return rec +} + +// recordKey is the key a DNSDB entry. +type recordKey struct { + target string + qt dnsmsg.RRType +} + +// unit is a convenient alias for struct{}. +type unit = struct{} + +// recordValue contains the values for a single record key. +type recordValue struct { + answers map[recordAnswer]unit + hits uint64 +} + +// recordAnswer contains a single piece of the answer data. +type recordAnswer struct { + value string + rrType dnsmsg.RRType + rcode dnsmsg.RCode } diff --git a/internal/dnsmsg/dnsmsg.go b/internal/dnsmsg/dnsmsg.go index 530c866..7b415b3 100644 --- a/internal/dnsmsg/dnsmsg.go +++ b/internal/dnsmsg/dnsmsg.go @@ -6,8 +6,10 @@ package dnsmsg import ( "fmt" + "math" "net/netip" + "github.com/AdguardTeam/golibs/mathutil" "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" ) @@ -143,3 +145,66 @@ func ecsData(esn *dns.EDNS0_SUBNET) (subnet netip.Prefix, scope uint8, err error return subnet, esn.SourceScope, nil } + +// SetMinTTL overrides TTL values of all answer records according to the min +// TTL. +func SetMinTTL(r *dns.Msg, minTTL uint32) { + for _, rr := range r.Answer { + h := rr.Header() + + // TODO(d.kolyshev): Use built-in max in go 1.21. + h.Ttl = mathutil.Max(h.Ttl, minTTL) + } +} + +// ServFailMaxCacheTTL is the maximum time-to-live value for caching +// SERVFAIL responses in seconds. It's consistent with the upper constraint +// of 5 minutes given by RFC 2308. +// +// See https://datatracker.ietf.org/doc/html/rfc2308#section-7.1. +const ServFailMaxCacheTTL = 30 + +// FindLowestTTL gets the lowest TTL among all DNS message's RRs. +func FindLowestTTL(msg *dns.Msg) (ttl uint32) { + // Use the maximum value as a guard value. If the inner loop is entered, + // it's going to be rewritten with an actual TTL value that is lower than + // MaxUint32. If the inner loop isn't entered, catch that and return zero. + ttl = math.MaxUint32 + for _, rrs := range [][]dns.RR{msg.Answer, msg.Ns, msg.Extra} { + for _, rr := range rrs { + ttl = getTTLIfLower(rr, ttl) + if ttl == 0 { + return 0 + } + } + } + + switch { + case msg.Rcode == dns.RcodeServerFailure && ttl > ServFailMaxCacheTTL: + return ServFailMaxCacheTTL + case ttl == math.MaxUint32: + return 0 + default: + return ttl + } +} + +// getTTLIfLower is a helper function that checks the TTL of the specified RR +// and returns it if it's lower than the one passed in the arguments. +func getTTLIfLower(r dns.RR, ttl uint32) (res uint32) { + switch r := r.(type) { + case *dns.OPT: + // Don't even consider the OPT RRs TTL. + return ttl + case *dns.SOA: + if r.Minttl > 0 && r.Minttl < ttl { + // Per RFC 2308, the TTL of a SOA RR is the minimum of SOA.MINIMUM + // field and the header's value. + ttl = r.Minttl + } + default: + // Go on. + } + + return mathutil.Min(r.Header().Ttl, ttl) +} diff --git a/internal/dnsserver/cache/cache.go b/internal/dnsserver/cache/cache.go index 3b07f3a..fbf9829 100644 --- a/internal/dnsserver/cache/cache.go +++ b/internal/dnsserver/cache/cache.go @@ -14,17 +14,26 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/mathutil" "github.com/bluele/gcache" "github.com/miekg/dns" ) // Middleware is a simple DNS caching middleware with no ECS support. +// +// TODO(a.garipov): Extract cache logic to golibs. type Middleware struct { // metrics is a listener for the middleware events. metrics MetricsListener // cache is the underlying LRU cache. cache gcache.Cache + + // cacheMinTTL is the minimum supported TTL for cache items. + cacheMinTTL time.Duration + + // useTTLOverride shows if the TTL overrides logic should be used. + useTTLOverride bool } // MiddlewareConfig is the configuration structure for NewMiddleware. @@ -37,6 +46,12 @@ type MiddlewareConfig struct { // Size is the number of entities to hold in the cache. It must be greater // than zero. Size int + + // MinTTL is the minimum supported TTL for cache items. + MinTTL time.Duration + + // UseTTLOverride shows if the TTL overrides logic should be used. + UseTTLOverride bool } // NewMiddleware initializes a new LRU caching middleware. c must not be nil. @@ -49,8 +64,10 @@ func NewMiddleware(c *MiddlewareConfig) (m *Middleware) { } return &Middleware{ - metrics: metrics, - cache: gcache.New(c.Size).LRU().Build(), + metrics: metrics, + cache: gcache.New(c.Size).LRU().Build(), + cacheMinTTL: c.MinTTL, + useTTLOverride: c.UseTTLOverride, } } @@ -128,15 +145,22 @@ func (m *Middleware) set(msg *dns.Msg) (err error) { return nil } - ttl := m.findLowestTTL(msg) + ttl := findLowestTTL(msg) if ttl == 0 || !isCacheable(msg) { return nil } + exp := time.Duration(ttl) * time.Second + if m.useTTLOverride && msg.Rcode != dns.RcodeServerFailure { + // TODO(d.kolyshev): Use built-in max in go 1.21. + exp = mathutil.Max(exp, m.cacheMinTTL) + setMinTTL(msg, uint32(exp.Seconds())) + } + key := toCacheKey(msg) i := m.toCacheItem(msg) - return m.cache.SetWithExpire(key, i, time.Duration(ttl)*time.Second) + return m.cache.SetWithExpire(key, i, exp) } // toCacheKey returns the cache key for msg. msg must have one question record. @@ -236,8 +260,19 @@ func isCacheableNOERROR(resp *dns.Msg) (ok bool) { return false } +// setMinTTL overrides TTL values of all answer records according to the min +// TTL. +func setMinTTL(r *dns.Msg, minTTL uint32) { + for _, rr := range r.Answer { + h := rr.Header() + + // TODO(d.kolyshev): Use built-in max in go 1.21. + h.Ttl = mathutil.Max(h.Ttl, minTTL) + } +} + // findLowestTTL gets the lowest TTL among all DNS message's RRs. -func (m *Middleware) findLowestTTL(msg *dns.Msg) (ttl uint32) { +func findLowestTTL(msg *dns.Msg) (ttl uint32) { // servFailMaxCacheTTL is the maximum time-to-live value for caching // SERVFAIL responses in seconds. It's consistent with the upper constraint // of 5 minutes given by the RFC 2308. @@ -321,7 +356,7 @@ func (m *Middleware) fromCacheItem(item cacheItem, req *dns.Msg) (msg *dns.Msg) // Update all the TTL of all depending on when the item was cached. If it's // already expired, update TTL to 0. - newTTL := m.findLowestTTL(item.msg) + newTTL := findLowestTTL(item.msg) if timeLeft := math.Round(float64(newTTL) - time.Since(item.when).Seconds()); timeLeft > 0 { newTTL = uint32(timeLeft) } diff --git a/internal/dnsserver/cache/cache_test.go b/internal/dnsserver/cache/cache_test.go index 6673096..615808c 100644 --- a/internal/dnsserver/cache/cache_test.go +++ b/internal/dnsserver/cache/cache_test.go @@ -4,6 +4,7 @@ import ( "context" "net" "testing" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache" @@ -15,75 +16,101 @@ import ( func TestMiddleware_Wrap(t *testing.T) { const ( + servFailMaxCacheTTL = 30 + reqHostname = "example.com" reqCname = "cname.example.com" reqNs1 = "ns1.example.com" reqNs2 = "ns2.example.com" + + defaultTTL uint32 = 3600 ) + testTTL := 60 * time.Second + aReq := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET) cnameReq := dnsservertest.NewReq(reqHostname, dns.TypeCNAME, dns.ClassINET) - cnameAns := dnsservertest.SectionAnswer{dnsservertest.NewCNAME(reqHostname, 3600, reqCname)} - soaNs := dnsservertest.SectionNs{dnsservertest.NewSOA(reqHostname, 3600, reqNs1, reqNs2)} + cnameAns := dnsservertest.SectionAnswer{dnsservertest.NewCNAME(reqHostname, defaultTTL, reqCname)} + soaNs := dnsservertest.SectionNs{dnsservertest.NewSOA(reqHostname, defaultTTL, reqNs1, reqNs2)} const N = 5 testCases := []struct { req *dns.Msg resp *dns.Msg name string + minTTL *time.Duration wantNumReq int + wantTTL uint32 }{{ req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 3600, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4}), }), name: "simple_a", wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq), name: "empty_answer", wantNumReq: N, + minTTL: nil, + wantTTL: 0, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, soaNs), name: "authoritative_nodata", wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, cnameAns, soaNs), name: "nodata_with_cname", wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, cnameAns), name: "nodata_with_cname_no_soa", wantNumReq: N, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNameError, aReq, dnsservertest.SectionNs{ - dnsservertest.NewNS(reqHostname, 3600, reqNs1), + dnsservertest.NewNS(reqHostname, defaultTTL, reqNs1), }), name: "non_authoritative_nxdomain", // TODO(ameshkov): Consider https://datatracker.ietf.org/doc/html/rfc2308#section-3. wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNameError, aReq, soaNs), name: "authoritative_nxdomain", wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq), name: "simple_server_failure", wantNumReq: 1, + minTTL: nil, + wantTTL: servFailMaxCacheTTL, }, { req: cnameReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, cnameReq, dnsservertest.SectionAnswer{ - dnsservertest.NewCNAME(reqHostname, 3600, reqCname), + dnsservertest.NewCNAME(reqHostname, defaultTTL, reqCname), }), name: "simple_cname_ans", wantNumReq: 1, + minTTL: nil, + wantTTL: defaultTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ @@ -91,11 +118,51 @@ func TestMiddleware_Wrap(t *testing.T) { }), name: "expired_one", wantNumReq: N, + minTTL: nil, + wantTTL: 0, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 10, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_ok", + wantNumReq: 1, + minTTL: &testTTL, + wantTTL: uint32(testTTL.Seconds()), + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 1000, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_max", + wantNumReq: 1, + minTTL: &testTTL, + wantTTL: 1000, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_zero", + wantNumReq: N, + minTTL: &testTTL, + wantTTL: 0, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, servFailMaxCacheTTL, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_servfail", + wantNumReq: 1, + minTTL: nil, + wantTTL: servFailMaxCacheTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNotImplemented, aReq, soaNs), name: "unexpected_response", wantNumReq: N, + minTTL: nil, + wantTTL: defaultTTL, }} for _, tc := range testCases { @@ -109,10 +176,17 @@ func TestMiddleware_Wrap(t *testing.T) { }, ) + var minTTL time.Duration + if tc.minTTL != nil { + minTTL = *tc.minTTL + } + withCache := dnsserver.WithMiddlewares( handler, cache.NewMiddleware(&cache.MiddlewareConfig{ - Size: 100, + Size: 100, + MinTTL: minTTL, + UseTTLOverride: tc.minTTL != nil, }), ) @@ -126,8 +200,13 @@ func TestMiddleware_Wrap(t *testing.T) { require.NoError(t, err) - assert.Equal(t, tc.resp, nrw.Msg()) + m := nrw.Msg() + assert.Equal(t, tc.resp, m) assert.Equal(t, tc.wantNumReq, numReq) + + if len(m.Answer) > 0 { + assert.Equal(t, tc.wantTTL, m.Answer[0].Header().Ttl) + } }) } } diff --git a/internal/dnsserver/dnsservertest/msg.go b/internal/dnsserver/dnsservertest/msg.go index 8481ced..272562d 100644 --- a/internal/dnsserver/dnsservertest/msg.go +++ b/internal/dnsserver/dnsservertest/msg.go @@ -168,6 +168,30 @@ func NewAAAA(name string, ttl uint32, aaaa net.IP) (rr dns.RR) { } } +// NewHTTPS constructs the new resource record of type HTTPS with IPv4 and IPv6 +// hint records from provided v4Hint and v6Hint parameters. +// +// TODO(d.kolyshev): Add "alpn" and other SVCB key-value pairs. +func NewHTTPS(name string, ttl uint32, v4Hint, v6Hint []net.IP) (rr dns.RR) { + svcb := dns.SVCB{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypeHTTPS, + Class: dns.ClassINET, + Ttl: ttl, + }, + Target: dns.Fqdn(name), + Value: []dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: v4Hint}, + &dns.SVCBIPv6Hint{Hint: v6Hint}, + }, + } + + return &dns.HTTPS{ + SVCB: svcb, + } +} + // NewSOA constructs the new resource record of type SOA. func NewSOA(name string, ttl uint32, ns, mbox string) (rr dns.RR) { return &dns.SOA{ diff --git a/internal/dnsserver/forward/forward.go b/internal/dnsserver/forward/forward.go index c6e3fa8..b6c11da 100644 --- a/internal/dnsserver/forward/forward.go +++ b/internal/dnsserver/forward/forward.go @@ -23,7 +23,6 @@ import ( "context" "fmt" "io" - "math/rand" "net" "net/netip" "sync/atomic" @@ -32,6 +31,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/errors" "github.com/miekg/dns" + "golang.org/x/exp/rand" ) // Handler is a struct that implements [dnsserver.Handler] and forwards DNS @@ -44,6 +44,11 @@ type Handler struct { // upstream is the main upstream where this handler forwards DNS queries. upstream Upstream + // rand is a random-number generator that is not cryptographically secure + // and is used for randomized upstream selection and other non-sensitive + // tasks. + rand *rand.Rand + // hcDomainTmpl is the template for domains used to perform healthcheck // requests. hcDomainTmpl string @@ -123,11 +128,14 @@ func NewHandler(c *HandlerConfig) (h *Handler) { Network: c.Network, Address: c.Address, }), + rand: rand.New(&rand.LockedSource{}), hcDomainTmpl: c.HealthcheckDomainTmpl, timeout: c.Timeout, hcBackoff: c.HealthcheckBackoffDuration, } + h.rand.Seed(uint64(time.Now().UnixNano())) + if l := c.MetricsListener; l != nil { h.metrics = l } else { @@ -196,9 +204,7 @@ func (h *Handler) ServeDNS( } if useFallbacks && len(h.fallbacks) > 0 { - // #nosec G404 -- We don't need a real random for a simple fallbacks - // rotation, we just need a simple fast pseudo-random. - i := rand.Intn(len(h.fallbacks)) + i := h.rand.Intn(len(h.fallbacks)) f := h.fallbacks[i] resp, err = h.exchange(ctx, f, req) } diff --git a/internal/dnsserver/forward/healthcheck.go b/internal/dnsserver/forward/healthcheck.go index d873efe..4cf1282 100644 --- a/internal/dnsserver/forward/healthcheck.go +++ b/internal/dnsserver/forward/healthcheck.go @@ -3,7 +3,6 @@ package forward import ( "context" "fmt" - "math/rand" "strconv" "strings" "time" @@ -74,9 +73,7 @@ const randomPlaceholder = "${RANDOM}" func (h *Handler) healthcheck(ctx context.Context) (err error) { domain := h.hcDomainTmpl if strings.Contains(domain, randomPlaceholder) { - // #nosec G404 -- We don't need a real random for generating randomized - // domain names here. - randStr := strconv.FormatUint(rand.Uint64(), 16) + randStr := strconv.FormatUint(h.rand.Uint64(), 16) domain = strings.ReplaceAll(domain, randomPlaceholder, randStr) } diff --git a/internal/dnsserver/forward/upstreamplain.go b/internal/dnsserver/forward/upstreamplain.go index edf3e24..41fb49f 100644 --- a/internal/dnsserver/forward/upstreamplain.go +++ b/internal/dnsserver/forward/upstreamplain.go @@ -159,7 +159,7 @@ func (u *UpstreamPlain) exchangeUDP( // // Thus, non-network errors are considered being related to the // response. It may also happen the received response is intended for - // another timeouted request sent from the same source port, but falling + // another timed out request sent from the same source port, but falling // back to TCP in this case shouldn't hurt. fallbackToTCP = !isExpectedConnErr(err) diff --git a/internal/dnsserver/forward/upstreamplain_test.go b/internal/dnsserver/forward/upstreamplain_test.go index 22e60a7..f08e348 100644 --- a/internal/dnsserver/forward/upstreamplain_test.go +++ b/internal/dnsserver/forward/upstreamplain_test.go @@ -5,7 +5,6 @@ import ( "net/netip" "testing" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" @@ -188,16 +187,16 @@ func TestUpstreamPlain_Exchange_fallbackSuccess(t *testing.T) { // Prepare malformed responses. - badIDResp := dnsmsg.Clone(resp) + badIDResp := resp.Copy() badIDResp.Id = ^req.Id - badQNumResp := dnsmsg.Clone(resp) + badQNumResp := resp.Copy() badQNumResp.Question = append(badQNumResp.Question, req.Question[0]) - badQnameResp := dnsmsg.Clone(resp) + badQnameResp := resp.Copy() badQnameResp.Question[0].Name = badDomain - badQtypeResp := dnsmsg.Clone(resp) + badQtypeResp := resp.Copy() badQtypeResp.Question[0].Qtype = dns.TypeMX testCases := []struct { @@ -218,9 +217,9 @@ func TestUpstreamPlain_Exchange_fallbackSuccess(t *testing.T) { }} for _, tc := range testCases { - clonedReq := dnsmsg.Clone(req) - badResp := dnsmsg.Clone(tc.udpResp) - goodResp := dnsmsg.Clone(resp) + clonedReq := req.Copy() + badResp := tc.udpResp.Copy() + goodResp := resp.Copy() // Use only unbuffered channels to block until received and validated. netCh := make(chan string) diff --git a/internal/dnsserver/go.mod b/internal/dnsserver/go.mod index 38d5bd0..3a83fb1 100644 --- a/internal/dnsserver/go.mod +++ b/internal/dnsserver/go.mod @@ -3,19 +3,19 @@ module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver go 1.20 require ( - github.com/AdguardTeam/golibs v0.13.2 - github.com/ameshkov/dnscrypt/v2 v2.2.5 + github.com/AdguardTeam/golibs v0.13.6 + github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/ameshkov/dnsstamps v1.0.3 github.com/bluele/gcache v0.0.2 - github.com/miekg/dns v1.1.52 - github.com/panjf2000/ants/v2 v2.7.1 + github.com/miekg/dns v1.1.55 + github.com/panjf2000/ants/v2 v2.7.5 github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible - github.com/prometheus/client_golang v1.14.0 - github.com/quic-go/quic-go v0.33.0 - github.com/stretchr/testify v1.8.2 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/net v0.8.0 - golang.org/x/sys v0.6.0 + github.com/prometheus/client_golang v1.15.1 + github.com/quic-go/quic-go v0.35.1 + github.com/stretchr/testify v1.8.4 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/net v0.12.0 + golang.org/x/sys v0.10.0 ) require ( @@ -24,25 +24,24 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect - github.com/kr/pretty v0.2.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/onsi/ginkgo/v2 v2.9.0 // indirect + github.com/onsi/ginkgo/v2 v2.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.41.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.2.1 // indirect - github.com/quic-go/qtls-go1-20 v0.1.1 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/text v0.11.0 // indirect + golang.org/x/tools v0.10.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/dnsserver/go.sum b/internal/dnsserver/go.sum index 0dd980d..7b09c3b 100644 --- a/internal/dnsserver/go.sum +++ b/internal/dnsserver/go.sum @@ -1,10 +1,11 @@ -github.com/AdguardTeam/golibs v0.13.2 h1:BPASsyQKmb+b8VnvsNOHp7bKfcZl9Z+Z2UhPjOiupSc= +github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ= +github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk= 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= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= -github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY= -github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM= +github.com/ameshkov/dnscrypt/v2 v2.2.7 h1:aEitLIR8HcxVodZ79mgRcCiC0A0I5kZPBuWGFwwulAw= +github.com/ameshkov/dnscrypt/v2 v2.2.7/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -17,110 +18,108 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c= -github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= -github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= -github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= -github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M= -github.com/panjf2000/ants/v2 v2.7.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs= +github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU= +github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= github.com/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.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw= -github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= -github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= -github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= +github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/dnssvc/debug.go b/internal/dnssvc/debug.go index e40c320..626e985 100644 --- a/internal/dnssvc/debug.go +++ b/internal/dnssvc/debug.go @@ -21,6 +21,7 @@ const ( hdrNameRuleListID = "rule-list-id" hdrNameRule = "rule" hdrNameClientIP = "client-ip" + hdrNameServerIP = "server-ip" hdrNameDeviceID = "device-id" hdrNameProfileID = "profile-id" hdrNameCountry = "country" @@ -55,6 +56,15 @@ func (svc *Service) writeDebugResponse( return fmt.Errorf("adding %s extra: %w", hdrNameClientIP, err) } + lAddr := rw.LocalAddr() + localIP, _ := netutil.IPAndPortFromAddr(lAddr) + + setQuestionName(debugReq, "", hdrNameServerIP) + err = svc.messages.AppendDebugExtra(debugReq, resp, localIP.String()) + if err != nil { + return fmt.Errorf("adding %s extra: %w", hdrNameServerIP, err) + } + err = svc.appendDebugExtraFromContext(ctx, debugReq, resp) if err != nil { // Don't wrap the error, because it's informative enough as is. diff --git a/internal/dnssvc/debug_internal_test.go b/internal/dnssvc/debug_internal_test.go index 46c9547..574d18a 100644 --- a/internal/dnssvc/debug_internal_test.go +++ b/internal/dnssvc/debug_internal_test.go @@ -48,6 +48,7 @@ func TestService_writeDebugResponse(t *testing.T) { ) clientIPStr := testClientIP.String() + serverIPStr := testServerAddr.String() testCases := []struct { name string ri *agd.RequestInfo @@ -61,6 +62,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"resp.res-type.adguard-dns.com.", "normal"}, }), }, { @@ -70,6 +72,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"req.res-type.adguard-dns.com.", "blocked"}, {"req.rule.adguard-dns.com.", "||example.com^"}, {"req.rule-list-id.adguard-dns.com.", "fl1"}, @@ -81,6 +84,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: &filter.ResultBlocked{List: fltListID2, Rule: blockRule}, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"resp.res-type.adguard-dns.com.", "blocked"}, {"resp.rule.adguard-dns.com.", "||example.com^"}, {"resp.rule-list-id.adguard-dns.com.", "fl2"}, @@ -92,6 +96,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"req.res-type.adguard-dns.com.", "allowed"}, {"req.rule.adguard-dns.com.", ""}, {"req.rule-list-id.adguard-dns.com.", ""}, @@ -103,6 +108,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: &filter.ResultAllowed{}, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"resp.res-type.adguard-dns.com.", "allowed"}, {"resp.rule.adguard-dns.com.", ""}, {"resp.rule-list-id.adguard-dns.com.", ""}, @@ -116,6 +122,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"req.res-type.adguard-dns.com.", "modified"}, {"req.rule.adguard-dns.com.", "||example.com^$dnsrewrite=REFUSED"}, {"req.rule-list-id.adguard-dns.com.", ""}, @@ -127,6 +134,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"device-id.adguard-dns.com.", testDeviceID}, {"resp.res-type.adguard-dns.com.", "normal"}, }), @@ -139,6 +147,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"profile-id.adguard-dns.com.", testProfileID}, {"resp.res-type.adguard-dns.com.", "normal"}, }), @@ -149,6 +158,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"country.adguard-dns.com.", string(agd.CountryAD)}, {"asn.adguard-dns.com.", "0"}, {"resp.res-type.adguard-dns.com.", "normal"}, @@ -162,6 +172,7 @@ func TestService_writeDebugResponse(t *testing.T) { respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, + {"server-ip.adguard-dns.com.", serverIPStr}, {"country.adguard-dns.com.", string(agd.CountryAD)}, {"asn.adguard-dns.com.", "0"}, {"subdivision.adguard-dns.com.", "CA"}, @@ -171,7 +182,7 @@ func TestService_writeDebugResponse(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) + rw := dnsserver.NewNonWriterResponseWriter(testLocalAddr, testRAddr) ctx := agd.ContextWithRequestInfo(context.Background(), tc.ri) diff --git a/internal/dnssvc/dnssvc.go b/internal/dnssvc/dnssvc.go index ca715dc..fb09152 100644 --- a/internal/dnssvc/dnssvc.go +++ b/internal/dnssvc/dnssvc.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "net/http" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" @@ -110,12 +111,22 @@ type Config struct { // CacheSize is the size of the DNS cache for domain names that don't // support ECS. + // + // TODO(a.garipov): Extract this and following fields to cache configuration + // struct. CacheSize int // ECSCacheSize is the size of the DNS cache for domain names that support // ECS. ECSCacheSize int + // CacheMinTTL is the minimum supported TTL for cache items. This setting + // is used when UseCacheTTLOverride set to true. + CacheMinTTL time.Duration + + // UseCacheTTLOverride shows if the TTL overrides logic should be used. + UseCacheTTLOverride bool + // UseECSCache shows if the EDNS Client Subnet (ECS) aware cache should be // used. UseECSCache bool @@ -124,6 +135,12 @@ type Config struct { // This is a set of metrics that we may need temporary, so its collection is // controlled by a separate setting. ResearchMetrics bool + + // ResearchLogs controls whether logging of additional info for research + // purposes is enabled. These logs may be overly verbose and are only + // required temporary, that's why it's controlled by a separate setting. + // This setting will only be used when ResearchMetrics is also set to true. + ResearchLogs bool } // New returns a new DNS service. @@ -143,11 +160,13 @@ func New(c *Config) (svc *Service, err error) { // Configure the pre-upstream middleware common for all servers of all // groups. preUps := &preUpstreamMw{ - db: c.DNSDB, - geoIP: c.GeoIP, - cacheSize: c.CacheSize, - ecsCacheSize: c.ECSCacheSize, - useECSCache: c.UseECSCache, + db: c.DNSDB, + geoIP: c.GeoIP, + cacheSize: c.CacheSize, + ecsCacheSize: c.ECSCacheSize, + useECSCache: c.UseECSCache, + cacheMinTTL: c.CacheMinTTL, + useCacheTTLOverride: c.UseCacheTTLOverride, } handler = preUps.Wrap(handler) @@ -163,6 +182,7 @@ func New(c *Config) (svc *Service, err error) { ruleStat: c.RuleStat, groups: groups, researchMetrics: c.ResearchMetrics, + researchLog: c.ResearchLogs, } for i, srvGrp := range c.ServerGroups { @@ -216,15 +236,23 @@ var _ agd.Service = (*Service)(nil) // Service is the main DNS service of AdGuard DNS. type Service struct { - messages *dnsmsg.Constructor - billStat billstat.Recorder - errColl agd.ErrorCollector - fltStrg filter.Storage - geoIP geoip.Interface - queryLog querylog.Interface - ruleStat rulestat.Interface - groups []*serverGroup + messages *dnsmsg.Constructor + billStat billstat.Recorder + errColl agd.ErrorCollector + fltStrg filter.Storage + geoIP geoip.Interface + queryLog querylog.Interface + ruleStat rulestat.Interface + groups []*serverGroup + + // researchMetrics enables reporting metrics that may be needed for research + // purposes. researchMetrics bool + + // researchLog enables logging of additional information that may be needed + // for research purposes. It will only be used when researchMetrics is set + // to true. + researchLog bool } // mustStartListener starts l and panics on any error. diff --git a/internal/dnssvc/dnssvc_internal_test.go b/internal/dnssvc/dnssvc_internal_test.go index 5138cd9..9af7cdb 100644 --- a/internal/dnssvc/dnssvc_internal_test.go +++ b/internal/dnssvc/dnssvc_internal_test.go @@ -17,6 +17,10 @@ var ( testClientAddr = testClientAddrPort.Addr() testServerAddr = netip.MustParseAddr("5.6.7.8") + testLocalAddr = &net.TCPAddr{ + IP: testServerAddr.AsSlice(), + Port: 54321, + } ) // testDeviceID is the common device ID for tests diff --git a/internal/dnssvc/initmw.go b/internal/dnssvc/initmw.go index 4272a59..541ac87 100644 --- a/internal/dnssvc/initmw.go +++ b/internal/dnssvc/initmw.go @@ -174,7 +174,7 @@ func (mw *initMw) addProfile( return err } - optlog.Debug2("init mw: got device id %q and ip %s", id, ri.RemoteIP) + optlog.Debug3("init mw: got device id %q, raddr %s, and laddr %s", id, ri.RemoteIP, localIP) prof, dev, byWhat, err := mw.profile(ctx, localIP, ri.RemoteIP, id) if err != nil { diff --git a/internal/dnssvc/middleware.go b/internal/dnssvc/middleware.go index f1a3294..b32805e 100644 --- a/internal/dnssvc/middleware.go +++ b/internal/dnssvc/middleware.go @@ -93,7 +93,7 @@ func (mh *svcHandler) ServeDNS( origResp := nwrw.Msg() respRes, elapsedResp := mh.svc.filterResponse(ctx, req, origResp, flt, reqInfo, modReq) - mh.svc.reportMetrics(reqInfo, reqRes, respRes, elapsedReq+elapsedResp) + mh.svc.reportMetrics(reqInfo, reqRes, respRes, origResp, elapsedReq+elapsedResp) if isDebug { return mh.svc.writeDebugResponse(ctx, rw, req, origResp, reqRes, respRes) @@ -196,6 +196,7 @@ func (svc *Service) reportMetrics( ri *agd.RequestInfo, reqRes filter.Result, respRes filter.Result, + origResp *dns.Msg, elapsedFiltering time.Duration, ) { var ctry, cont string @@ -221,7 +222,7 @@ func (svc *Service) reportMetrics( metrics.DNSSvcUsersCountUpdate(ri.RemoteIP) if svc.researchMetrics { - metrics.ReportResearchMetrics(ri, id, isBlocked) + metrics.ReportResearch(ri, origResp, id, isBlocked, svc.researchLog) } } diff --git a/internal/dnssvc/presvcmw_test.go b/internal/dnssvc/presvcmw_internal_test.go similarity index 100% rename from internal/dnssvc/presvcmw_test.go rename to internal/dnssvc/presvcmw_internal_test.go diff --git a/internal/dnssvc/preupstreammw.go b/internal/dnssvc/preupstreammw.go index 7fa5fdc..c21deac 100644 --- a/internal/dnssvc/preupstreammw.go +++ b/internal/dnssvc/preupstreammw.go @@ -3,6 +3,7 @@ package dnssvc import ( "context" "fmt" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" @@ -23,11 +24,13 @@ import ( // preUpstreamMw is a middleware that prepares records for caching and upstream // handling as well as records anonymous DNS statistics. type preUpstreamMw struct { - db dnsdb.Interface - geoIP geoip.Interface - cacheSize int - ecsCacheSize int - useECSCache bool + db dnsdb.Interface + geoIP geoip.Interface + cacheMinTTL time.Duration + cacheSize int + ecsCacheSize int + useECSCache bool + useCacheTTLOverride bool } // type check @@ -41,14 +44,18 @@ func (mw *preUpstreamMw) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) { var cacheMw dnsserver.Middleware if mw.useECSCache { cacheMw = ecscache.NewMiddleware(&ecscache.MiddlewareConfig{ - GeoIP: mw.geoIP, - Size: mw.cacheSize, - ECSSize: mw.ecsCacheSize, + GeoIP: mw.geoIP, + Size: mw.cacheSize, + ECSSize: mw.ecsCacheSize, + MinTTL: mw.cacheMinTTL, + UseTTLOverride: mw.useCacheTTLOverride, }) } else { cacheMw = cache.NewMiddleware(&cache.MiddlewareConfig{ MetricsListener: &prometheus.CacheMetricsListener{}, Size: mw.cacheSize, + MinTTL: mw.cacheMinTTL, + UseTTLOverride: mw.useCacheTTLOverride, }) } diff --git a/internal/dnssvc/preupstreammw_test.go b/internal/dnssvc/preupstreammw_internal_test.go similarity index 100% rename from internal/dnssvc/preupstreammw_test.go rename to internal/dnssvc/preupstreammw_internal_test.go diff --git a/internal/ecscache/cache.go b/internal/ecscache/cache.go index 883a74c..ced15bf 100644 --- a/internal/ecscache/cache.go +++ b/internal/ecscache/cache.go @@ -140,7 +140,7 @@ func (mw *Middleware) toCacheKey(cr *cacheRequest, respIsECSDependent bool) (key // set saves resp to the cache if it's cacheable. If msg cannot be cached, it // is ignored. func (mw *Middleware) set(resp *dns.Msg, cr *cacheRequest, respIsECSDependent bool) { - ttl := findLowestTTL(resp) + ttl := dnsmsg.FindLowestTTL(resp) if ttl == 0 || !isCacheable(resp) { return } @@ -150,10 +150,17 @@ func (mw *Middleware) set(resp *dns.Msg, cr *cacheRequest, respIsECSDependent bo cache = mw.ecsCache } + exp := time.Duration(ttl) * time.Second + if mw.useTTLOverride && resp.Rcode != dns.RcodeServerFailure { + // TODO(d.kolyshev): Use built-in max in go 1.21. + exp = mathutil.Max(exp, mw.cacheMinTTL) + dnsmsg.SetMinTTL(resp, uint32(exp.Seconds())) + } + key := mw.toCacheKey(cr, respIsECSDependent) item := toCacheItem(resp, cr.host) - err := cache.SetWithExpire(key, item, time.Duration(ttl)*time.Second) + err := cache.SetWithExpire(key, item, exp) if err != nil { // Shouldn't happen, since we don't set a serialization function. panic(fmt.Errorf("ecs-cache: setting cache item: %w", err)) @@ -187,7 +194,7 @@ func toCacheItem(msg *dns.Msg, host string) (item *cacheItem) { func fromCacheItem(item *cacheItem, req *dns.Msg, reqDO bool) (msg *dns.Msg) { // Update the TTL depending on when the item was cached. If it's already // expired, update TTL to 0. - newTTL := findLowestTTL(item.msg) + newTTL := dnsmsg.FindLowestTTL(item.msg) if timeLeft := time.Duration(newTTL)*time.Second - time.Since(item.when); timeLeft > 0 { newTTL = uint32(roundDiv(timeLeft, time.Second)) } else { diff --git a/internal/ecscache/ecscache.go b/internal/ecscache/ecscache.go index 173e6f0..a484dae 100644 --- a/internal/ecscache/ecscache.go +++ b/internal/ecscache/ecscache.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "sync" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" @@ -35,6 +36,12 @@ type Middleware struct { // cacheReqPool is a pool of cache requests. cacheReqPool *sync.Pool + + // cacheMinTTL is the minimum supported TTL for cache items. + cacheMinTTL time.Duration + + // useTTLOverride shows if the TTL overrides logic should be used. + useTTLOverride bool } // MiddlewareConfig is the configuration structure for NewMiddleware. @@ -43,6 +50,9 @@ type MiddlewareConfig struct { // not be nil. GeoIP geoip.Interface + // MinTTL is the minimum supported TTL for cache items. + MinTTL time.Duration + // Size is the number of entities to hold in the cache for hosts that don't // support ECS. It must be greater than zero. Size int @@ -50,15 +60,20 @@ type MiddlewareConfig struct { // ECSSize is the number of entities to hold in the cache for hosts that // support ECS. It must be greater than zero. ECSSize int + + // UseTTLOverride shows if the TTL overrides logic should be used. + UseTTLOverride bool } // NewMiddleware initializes a new ECS-aware LRU caching middleware. c must not // be nil. func NewMiddleware(c *MiddlewareConfig) (m *Middleware) { return &Middleware{ - cache: gcache.New(c.Size).LRU().Build(), - ecsCache: gcache.New(c.ECSSize).LRU().Build(), - geoIP: c.GeoIP, + cache: gcache.New(c.Size).LRU().Build(), + ecsCache: gcache.New(c.ECSSize).LRU().Build(), + cacheMinTTL: c.MinTTL, + useTTLOverride: c.UseTTLOverride, + geoIP: c.GeoIP, cacheReqPool: &sync.Pool{ New: func() (req any) { return &cacheRequest{} diff --git a/internal/ecscache/ecscache_test.go b/internal/ecscache/ecscache_test.go index 79309b6..3b8a281 100644 --- a/internal/ecscache/ecscache_test.go +++ b/internal/ecscache/ecscache_test.go @@ -49,10 +49,13 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { dnsservertest.NewSOA(reqHostname, defaultTTL, reqNS1, reqNS2), } + testTTL := 60 * time.Second + const N = 5 testCases := []struct { req *dns.Msg resp *dns.Msg + minTTL *time.Duration name string wantNumReq int wantTTL uint32 @@ -64,30 +67,35 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { name: "simple_a", wantNumReq: 1, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq), name: "empty_answer", wantNumReq: N, wantTTL: 0, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, soaNS), name: "authoritative_nodata", wantNumReq: 1, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, cnameAns, soaNS), name: "nodata_with_cname", wantNumReq: 1, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, cnameAns), name: "nodata_with_cname_no_soa", wantNumReq: N, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNameError, aReq, dnsservertest.SectionNs{ @@ -97,18 +105,21 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { // TODO(ameshkov): Consider https://datatracker.ietf.org/doc/html/rfc2308#section-3. wantNumReq: 1, wantTTL: 0, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNameError, aReq, soaNS), name: "authoritative_nxdomain", wantNumReq: 1, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq), name: "simple_server_failure", wantNumReq: 1, - wantTTL: ecscache.ServFailMaxCacheTTL, + wantTTL: dnsmsg.ServFailMaxCacheTTL, + minTTL: nil, }, { req: cnameReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, cnameReq, dnsservertest.SectionAnswer{ @@ -117,6 +128,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { name: "simple_cname_ans", wantNumReq: 1, wantTTL: defaultTTL, + minTTL: nil, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ @@ -125,12 +137,50 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { name: "expired_one", wantNumReq: N, wantTTL: 0, + minTTL: nil, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 10, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_ok", + wantNumReq: 1, + minTTL: &testTTL, + wantTTL: uint32(testTTL.Seconds()), + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 1000, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_max", + wantNumReq: 1, + minTTL: &testTTL, + wantTTL: 1000, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_zero", + wantNumReq: N, + minTTL: &testTTL, + wantTTL: 0, + }, { + req: aReq, + resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq, dnsservertest.SectionAnswer{ + dnsservertest.NewA(reqHostname, dnsmsg.ServFailMaxCacheTTL, net.IP{1, 2, 3, 4}), + }), + name: "override_ttl_servfail", + wantNumReq: 1, + minTTL: nil, + wantTTL: dnsmsg.ServFailMaxCacheTTL, }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeNotImplemented, aReq, soaNS), name: "unexpected_response", wantNumReq: N, wantTTL: 0, + minTTL: nil, }} for _, tc := range testCases { @@ -144,11 +194,18 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, ) + var minTTL time.Duration + if tc.minTTL != nil { + minTTL = *tc.minTTL + } + withCache := newWithCache( t, handler, agd.CountryNone, netutil.ZeroPrefix(netutil.AddrFamilyIPv4), + minTTL, + tc.minTTL != nil, ) ri := &agd.RequestInfo{ Host: tc.req.Question[0].Name, @@ -284,7 +341,7 @@ func TestMiddleware_Wrap_ecs(t *testing.T) { }, ) - withCache := newWithCache(t, handler, ctry, tc.ctrySubnet) + withCache := newWithCache(t, handler, ctry, tc.ctrySubnet, 0, false) ri := &agd.RequestInfo{ Location: &agd.Location{ Country: ctry, @@ -493,7 +550,7 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) { }} for _, tc := range testCases { - withCache := newWithCache(t, handler, ctry, ctrySubnet) + withCache := newWithCache(t, handler, ctry, ctrySubnet, 0, false) t.Run(tc.name, func(t *testing.T) { for i, req := range tc.sequence { @@ -554,6 +611,8 @@ func newWithCache( h dnsserver.Handler, wantCtry agd.Country, geoIPNet netip.Prefix, + minTTL time.Duration, + useTTLOverride bool, ) (wrapped dnsserver.Handler) { t.Helper() @@ -578,9 +637,11 @@ func newWithCache( return dnsserver.WithMiddlewares( h, ecscache.NewMiddleware(&ecscache.MiddlewareConfig{ - GeoIP: geoIP, - Size: 100, - ECSSize: 100, + GeoIP: geoIP, + Size: 100, + ECSSize: 100, + MinTTL: minTTL, + UseTTLOverride: useTTLOverride, }), ) } diff --git a/internal/ecscache/msg.go b/internal/ecscache/msg.go index 5e7611d..4f17036 100644 --- a/internal/ecscache/msg.go +++ b/internal/ecscache/msg.go @@ -2,13 +2,11 @@ package ecscache import ( "fmt" - "math" "net" "net/netip" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/golibs/mathutil" "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" ) @@ -220,55 +218,3 @@ func isCacheableNOERROR(resp *dns.Msg) (ok bool) { return false } - -// ServFailMaxCacheTTL is the maximum time-to-live value for caching -// SERVFAIL responses in seconds. It's consistent with the upper constraint -// of 5 minutes given by RFC 2308. -// -// See https://datatracker.ietf.org/doc/html/rfc2308#section-7.1. -const ServFailMaxCacheTTL = 30 - -// findLowestTTL gets the lowest TTL among all DNS message's RRs. -func findLowestTTL(msg *dns.Msg) (ttl uint32) { - // Use the maximum value as a guard value. If the inner loop is entered, - // it's going to be rewritten with an actual TTL value that is lower than - // MaxUint32. If the inner loop isn't entered, catch that and return zero. - ttl = math.MaxUint32 - for _, rrs := range [][]dns.RR{msg.Answer, msg.Ns, msg.Extra} { - for _, rr := range rrs { - ttl = getTTLIfLower(rr, ttl) - if ttl == 0 { - return 0 - } - } - } - - switch { - case msg.Rcode == dns.RcodeServerFailure && ttl > ServFailMaxCacheTTL: - return ServFailMaxCacheTTL - case ttl == math.MaxUint32: - return 0 - default: - return ttl - } -} - -// getTTLIfLower is a helper function that checks the TTL of the specified RR -// and returns it if it's lower than the one passed in the arguments. -func getTTLIfLower(r dns.RR, ttl uint32) (res uint32) { - switch r := r.(type) { - case *dns.OPT: - // Don't even consider the OPT RRs TTL. - return ttl - case *dns.SOA: - if r.Minttl > 0 && r.Minttl < ttl { - // Per RFC 2308, the TTL of a SOA RR is the minimum of SOA.MINIMUM - // field and the header's value. - ttl = r.Minttl - } - default: - // Go on. - } - - return mathutil.Min(r.Header().Ttl, ttl) -} diff --git a/internal/errcoll/sentry.go b/internal/errcoll/sentry.go index 86f828e..cc9b603 100644 --- a/internal/errcoll/sentry.go +++ b/internal/errcoll/sentry.go @@ -168,7 +168,7 @@ func tagsFromCtx(ctx context.Context) (tags sentryTags) { var reqID agd.RequestID if ri, ok := agd.RequestInfoFromContext(ctx); ok { tags["filtering_group_id"] = string(ri.FilteringGroup.ID) - tags["request_id"] = string(ri.ID) + tags["request_id"] = ri.ID.String() if p := ri.Profile; p != nil { tags["profile_id"] = string(p.ID) @@ -179,7 +179,7 @@ func tagsFromCtx(ctx context.Context) (tags sentryTags) { } else if reqID, ok = agd.RequestIDFromContext(ctx); ok { // This context could be from the part of the pipeline where the request // ID hasn't yet been resurfaced. - tags["request_id"] = string(reqID) + tags["request_id"] = reqID.String() } if si, ok := dnsserver.ServerInfoFromContext(ctx); ok { diff --git a/internal/errcoll/sentry_test.go b/internal/errcoll/sentry_test.go index 5ad81f7..fdee6df 100644 --- a/internal/errcoll/sentry_test.go +++ b/internal/errcoll/sentry_test.go @@ -67,7 +67,8 @@ func TestSentryErrorCollector(t *testing.T) { const devID = "dev1234" const fltGrpID = "fg1234" const profID = "prof1234" - const reqID = "req5678" + + reqID := agd.NewRequestID() ctx := context.Background() ctx = agd.ContextWithRequestInfo(ctx, &agd.RequestInfo{ @@ -103,7 +104,7 @@ func TestSentryErrorCollector(t *testing.T) { "device_id": devID, "filtering_group_id": fltGrpID, "profile_id": profID, - "request_id": reqID, + "request_id": reqID.String(), } assert.Equal(t, wantTags, gotTags) } diff --git a/internal/filter/filter_test.go b/internal/filter/filter_test.go index de8edb6..af54a68 100644 --- a/internal/filter/filter_test.go +++ b/internal/filter/filter_test.go @@ -18,6 +18,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/require" ) @@ -36,9 +37,6 @@ const testDeviceName agd.DeviceName = "My Device" // testSvcID is the standard ID of the blocked service for tests. const testSvcID agd.BlockedServiceID = "service" -// testRefreshIvl is the standard refresh interval for tests. -const testRefreshIvl = 1 * time.Hour - // Common constants. Keep in sync with ./testdata/filter and // ./safesearchhost.csv. const ( @@ -185,16 +183,18 @@ func prepareConf(t testing.TB) (c *filter.DefaultStorageConfig) { YoutubeSafeSearchRulesURL: ssURL, SafeBrowsing: &hashprefix.Filter{}, AdultBlocking: &hashprefix.Filter{}, + NewRegDomains: &hashprefix.Filter{}, Now: time.Now, ErrColl: nil, Resolver: nil, CacheDir: cacheDir, CustomFilterCacheSize: 100, SafeSearchCacheSize: 100, - SafeSearchCacheTTL: 1 * time.Hour, + SafeSearchCacheTTL: filtertest.CacheTTL, RuleListCacheSize: 100, - RefreshIvl: testRefreshIvl, + RefreshIvl: filtertest.Staleness, UseRuleListCache: false, + MaxRuleListSize: filtertest.FilterMaxSize, } } diff --git a/internal/filter/hashprefix/filter.go b/internal/filter/hashprefix/filter.go index c1d8708..b2ce57d 100644 --- a/internal/filter/hashprefix/filter.go +++ b/internal/filter/hashprefix/filter.go @@ -9,6 +9,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/resultcache" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" @@ -56,6 +57,9 @@ type FilterConfig struct { // CacheSize is the size of the filter's result cache. CacheSize int + + // MaxSize is the maximum size in bytes of the downloadable rule-list. + MaxSize int64 } // Filter is a filter that matches hosts by their hashes based on a @@ -82,14 +86,15 @@ func NewFilter(c *FilterConfig) (f *Filter, err error) { repHost: c.ReplacementHost, } - f.refr = internal.NewRefreshable( - &agd.FilterList{ - ID: id, - URL: c.URL, - RefreshIvl: c.Staleness, - }, - c.CachePath, - ) + f.refr = internal.NewRefreshable(&internal.RefreshableConfig{ + URL: c.URL, + ID: id, + CachePath: c.CachePath, + Staleness: c.Staleness, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + MaxSize: c.MaxSize, + }) err = f.refresh(context.Background(), true) if err != nil { @@ -124,8 +129,8 @@ func (f *Filter) FilterRequest( return rm.CloneForReq(req), nil } - fam := netutil.AddrFamilyFromRRType(qt) - if fam == netutil.AddrFamilyNone { + fam, ok := isFilterable(qt) + if !ok { return nil, nil } @@ -148,17 +153,10 @@ func (f *Filter) FilterRequest( ctx, cancel := context.WithTimeout(ctx, internal.DefaultResolveTimeout) defer cancel() - var result *dns.Msg - ips, err := f.resolver.LookupIP(ctx, fam, f.repHost) + result, err := f.filteredResponse(ctx, req, ri, fam) if err != nil { - agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err) - - result = ri.Messages.NewMsgSERVFAIL(req) - } else { - result, err = ri.Messages.NewIPRespMsg(req, ips...) - if err != nil { - return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err) - } + // Don't wrap the error, because it's informative enough as is. + return nil, err } rm = &internal.ResultModified{ @@ -177,6 +175,58 @@ func (f *Filter) FilterRequest( return rm, nil } +// isFilterable returns true if the question type is filterable. If the type is +// filterable with a blocked page, fam is the address family for the IP +// addresses of the blocked page; otherwise fam is [netutil.AddrFamilyNone]. +func isFilterable(qt dnsmsg.RRType) (fam netutil.AddrFamily, ok bool) { + if qt == dns.TypeHTTPS { + return netutil.AddrFamilyNone, true + } + + fam = netutil.AddrFamilyFromRRType(qt) + + return fam, fam != netutil.AddrFamilyNone +} + +// filteredResponse returns a filtered response. +func (f *Filter) filteredResponse( + ctx context.Context, + req *dns.Msg, + ri *agd.RequestInfo, + fam netutil.AddrFamily, +) (resp *dns.Msg, err error) { + if fam == netutil.AddrFamilyNone { + // This is an HTTPS query. For them, just return NODATA or other + // blocked response. See AGDNS-1551. + // + // TODO(ameshkov): Consider putting the resolved IP addresses into hints + // to show the blocked page here as well. + resp, err = ri.Messages.NewBlockedRespMsg(req) + if err != nil { + return nil, fmt.Errorf("filter %s: creating blocked result: %w", f.id, err) + } + + return resp, nil + } + + ctx, cancel := context.WithTimeout(ctx, internal.DefaultResolveTimeout) + defer cancel() + + ips, err := f.resolver.LookupIP(ctx, fam, f.repHost) + if err != nil { + agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err) + + return ri.Messages.NewMsgSERVFAIL(req), nil + } + + resp, err = ri.Messages.NewIPRespMsg(req, ips...) + if err != nil { + return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err) + } + + return resp, nil +} + // updateCacheSizeMetrics updates cache size metrics. func (f *Filter) updateCacheSizeMetrics(size int) { switch id := f.id; id { @@ -184,6 +234,8 @@ func (f *Filter) updateCacheSizeMetrics(size int) { metrics.HashPrefixFilterSafeBrowsingCacheSize.Set(float64(size)) case agd.FilterListIDAdultBlocking: metrics.HashPrefixFilterAdultBlockingCacheSize.Set(float64(size)) + case agd.FilterListIDNewRegDomains: + metrics.HashPrefixFilterNewRegDomainsCacheSize.Set(float64(size)) default: panic(fmt.Errorf("unsupported FilterListID %s", id)) } @@ -199,6 +251,9 @@ func (f *Filter) updateCacheLookupsMetrics(hit bool) { case agd.FilterListIDAdultBlocking: hitsMetric = metrics.HashPrefixFilterCacheAdultBlockingHits missesMetric = metrics.HashPrefixFilterCacheAdultBlockingMisses + case agd.FilterListIDNewRegDomains: + hitsMetric = metrics.HashPrefixFilterCacheNewRegDomainsHits + missesMetric = metrics.HashPrefixFilterCacheNewRegDomainsMisses default: panic(fmt.Errorf("unsupported filter list id %s", id)) } diff --git a/internal/filter/hashprefix/filter_test.go b/internal/filter/hashprefix/filter_test.go index 386ced8..0111b31 100644 --- a/internal/filter/hashprefix/filter_test.go +++ b/internal/filter/hashprefix/filter_test.go @@ -49,9 +49,10 @@ func TestFilter_FilterRequest(t *testing.T) { ID: agd.FilterListIDAdultBlocking, CachePath: cachePath, ReplacementHost: "repl.example", - Staleness: 1 * time.Minute, - CacheTTL: 1 * time.Minute, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 1, + MaxSize: filtertest.FilterMaxSize, }) require.NoError(t, err) @@ -157,6 +158,30 @@ func TestFilter_FilterRequest(t *testing.T) { assert.Nil(t, r) }) + + t.Run("https", func(t *testing.T) { + req := dnsservertest.NewReq(dns.Fqdn(testHost), dns.TypeHTTPS, dns.ClassINET) + ri := &agd.RequestInfo{ + Messages: messages, + Host: testHost, + QType: dns.TypeHTTPS, + } + + ctx, cancel := context.WithTimeout(context.Background(), filtertest.Timeout) + t.Cleanup(cancel) + + var r internal.Result + r, err = f.FilterRequest(ctx, req, ri) + require.NoError(t, err) + require.NotNil(t, r) + + m := testutil.RequireTypeAssert[*internal.ResultModified](t, r) + require.NotNil(t, m.Msg) + require.Len(t, m.Msg.Question, 1) + + assert.Equal(t, m.Msg.Question[0].Qtype, dns.TypeHTTPS) + assert.Len(t, m.Msg.Answer, 0) + }) } // newModifiedResult is a helper for creating modified results for tests. @@ -203,9 +228,10 @@ func TestFilter_Refresh(t *testing.T) { ID: agd.FilterListIDAdultBlocking, CachePath: cachePath, ReplacementHost: "", - Staleness: 1 * time.Minute, - CacheTTL: 1 * time.Minute, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 1, + MaxSize: filtertest.FilterMaxSize, }) require.NoError(t, err) @@ -257,9 +283,10 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { ID: agd.FilterListIDAdultBlocking, CachePath: cachePath, ReplacementHost: "repl.example", - Staleness: 1 * time.Minute, - CacheTTL: 1 * time.Minute, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 1, + MaxSize: filtertest.FilterMaxSize, } f, err := hashprefix.NewFilter(fconf) require.NoError(t, err) diff --git a/internal/filter/index.go b/internal/filter/index.go new file mode 100644 index 0000000..5b46200 --- /dev/null +++ b/internal/filter/index.go @@ -0,0 +1,66 @@ +package filter + +import ( + "context" + "net/url" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" +) + +// filterIndexResp is the struct for the JSON response from a filter index API. +type filterIndexResp struct { + Filters []*filterIndexRespFilter `json:"filters"` +} + +// filterIndexRespFilter is the struct for a filter from the JSON response from +// a filter index API. +type filterIndexRespFilter struct { + DownloadURL string `json:"downloadUrl"` + ID string `json:"filterId"` +} + +// filterIndexFilterData is the data of a single item in the filtering-rule +// index response. +type filterIndexFilterData struct { + url *url.URL + id agd.FilterListID +} + +// toInternal converts the filters from the index to []*filterIndexFilterData. +func (r *filterIndexResp) toInternal( + ctx context.Context, + errColl agd.ErrorCollector, +) (fls []*filterIndexFilterData) { + fls = make([]*filterIndexFilterData, 0, len(r.Filters)) + for _, rf := range r.Filters { + id, err := agd.NewFilterListID(rf.ID) + if err != nil { + agd.Collectf(ctx, errColl, "%s: validating id %q: %w", strgLogPrefix, rf.ID, err) + + continue + } + + var u *url.URL + u, err = agdhttp.ParseHTTPURL(rf.DownloadURL) + if err != nil { + agd.Collectf( + ctx, + errColl, + "%s: validating url %q: %w", + strgLogPrefix, + rf.DownloadURL, + err, + ) + + continue + } + + fls = append(fls, &filterIndexFilterData{ + url: u, + id: id, + }) + } + + return fls +} diff --git a/internal/filter/internal/composite/composite.go b/internal/filter/internal/composite/composite.go index e480c6f..6974db8 100644 --- a/internal/filter/internal/composite/composite.go +++ b/internal/filter/internal/composite/composite.go @@ -24,8 +24,9 @@ import ( // An empty composite filter is a filter that always returns a nil filtering // result. type Filter struct { - safeBrowsing *hashprefix.Filter - adultBlocking *hashprefix.Filter + safeBrowsing *hashprefix.Filter + newRegisteredDomains *hashprefix.Filter + adultBlocking *hashprefix.Filter genSafeSearch *safesearch.Filter ytSafeSearch *safesearch.Filter @@ -50,6 +51,10 @@ type Config struct { // AdultBlocking is the adult-content filter to apply, if any. AdultBlocking *hashprefix.Filter + // NewRegisteredDomains is the newly registered domains filter to apply, if + // any. + NewRegisteredDomains *hashprefix.Filter + // GeneralSafeSearch is the general safe-search filter to apply, if any. GeneralSafeSearch *safesearch.Filter @@ -76,13 +81,14 @@ func New(c *Config) (f *Filter) { } return &Filter{ - safeBrowsing: c.SafeBrowsing, - adultBlocking: c.AdultBlocking, - genSafeSearch: c.GeneralSafeSearch, - ytSafeSearch: c.YouTubeSafeSearch, - custom: c.Custom, - ruleLists: c.RuleLists, - svcLists: c.ServiceLists, + safeBrowsing: c.SafeBrowsing, + adultBlocking: c.AdultBlocking, + genSafeSearch: c.GeneralSafeSearch, + ytSafeSearch: c.YouTubeSafeSearch, + custom: c.Custom, + ruleLists: c.RuleLists, + svcLists: c.ServiceLists, + newRegisteredDomains: c.NewRegisteredDomains, } } @@ -143,6 +149,9 @@ func (f *Filter) FilterRequest( }, { filter: nullify(f.ytSafeSearch), id: agd.FilterListIDYoutubeSafeSearch, + }, { + filter: nullify(f.newRegisteredDomains), + id: agd.FilterListIDNewRegDomains, }} for _, rf := range reqFilters { @@ -179,7 +188,7 @@ func nullify[T *safesearch.Filter | *hashprefix.Filter](flt T) (fr internal.Requ // returns the action created from the filter list network rule with the highest // priority. If f is empty, it returns nil with no error. func (f *Filter) FilterResponse( - ctx context.Context, + _ context.Context, resp *dns.Msg, ri *agd.RequestInfo, ) (r internal.Result, err error) { @@ -188,6 +197,13 @@ func (f *Filter) FilterResponse( } for _, ans := range resp.Answer { + if rr, ok := ans.(*dns.HTTPS); ok { + r = f.filterHTTPSRecords(rr, ri, resp) + if r != nil { + return r, nil + } + } + host, rrType, ok := parseRespAnswer(ans) if !ok { continue @@ -202,6 +218,45 @@ func (f *Filter) FilterResponse( return r, nil } +// filterHTTPSRecords filters HTTPS answers information through all rule list +// filters of the composite filter. +func (f *Filter) filterHTTPSRecords( + rr *dns.HTTPS, + ri *agd.RequestInfo, + resp *dns.Msg, +) (r internal.Result) { + for _, kv := range rr.Value { + switch kv.Key() { + case dns.SVCB_IPV4HINT, dns.SVCB_IPV6HINT: + r = f.filterSVCBHint(kv.String(), ri, resp) + if r != nil { + return r + } + default: + // Go on. + } + } + + return nil +} + +// filterSVCBHint filters SVCB hint information through all rule list filters of +// the composite filter. +func (f *Filter) filterSVCBHint( + hint string, + ri *agd.RequestInfo, + resp *dns.Msg, +) (r internal.Result) { + for _, s := range strings.Split(hint, ",") { + r = f.filterWithRuleLists(ri, s, dns.TypeHTTPS, resp, true) + if r != nil { + return r + } + } + + return nil +} + // parseRespAnswer parses hostname and rrType from the answer if there are any. // If ans is of a type that doesn't have an IP address or a hostname in it, ok // is false. @@ -226,6 +281,7 @@ func (f *Filter) isEmpty() (ok bool) { f.genSafeSearch == nil && f.ytSafeSearch == nil && f.custom == nil && + f.newRegisteredDomains == nil && len(f.ruleLists) == 0 && len(f.svcLists) == 0) } diff --git a/internal/filter/internal/composite/composite_test.go b/internal/filter/internal/composite/composite_test.go index 8e517d4..20a751a 100644 --- a/internal/filter/internal/composite/composite_test.go +++ b/internal/filter/internal/composite/composite_test.go @@ -5,7 +5,6 @@ import ( "net" "net/http" "net/netip" - "path/filepath" "testing" "time" @@ -371,9 +370,13 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { const fltListID = agd.FilterListIDGeneralSafeSearch gen := safesearch.New(&safesearch.Config{ - List: &agd.FilterList{ - URL: srvURL, - ID: fltListID, + Refreshable: &internal.RefreshableConfig{ + URL: srvURL, + ID: fltListID, + CachePath: cachePath, + Staleness: filtertest.Staleness, + Timeout: filtertest.Timeout, + MaxSize: filtertest.FilterMaxSize, }, Resolver: &agdtest.Resolver{ OnLookupIP: func( @@ -389,7 +392,6 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { panic("not implemented") }, }, - CacheDir: filepath.Dir(cachePath), CacheTTL: 1 * time.Minute, CacheSize: 100, }) @@ -451,12 +453,14 @@ func TestFilter_FilterResponse(t *testing.T) { const ( blockedCNAME = filtertest.ReqHost + passedIPv4Str = "1.1.1.1" blockedIPv4Str = "1.2.3.4" blockedIPv6Str = "1234::cdef" blockRules = blockedCNAME + "\n" + blockedIPv4Str + "\n" + blockedIPv6Str + "\n" ) var ( + passedIPv4 net.IP = netip.MustParseAddr(passedIPv4Str).AsSlice() blockedIPv4 net.IP = netip.MustParseAddr(blockedIPv4Str).AsSlice() blockedIPv6 net.IP = netip.MustParseAddr(blockedIPv6Str).AsSlice() ) @@ -475,6 +479,14 @@ func TestFilter_FilterResponse(t *testing.T) { respAns dnsservertest.SectionAnswer qType dnsmsg.RRType }{{ + name: "pass", + reqFQDN: filtertest.ReqFQDN, + wantRule: "", + respAns: dnsservertest.SectionAnswer{ + dnsservertest.NewA(filtertest.ReqFQDN, ttl, passedIPv4), + }, + qType: dns.TypeA, + }, { name: "cname", reqFQDN: cnameReqFQDN, wantRule: filtertest.ReqHost, @@ -499,6 +511,43 @@ func TestFilter_FilterResponse(t *testing.T) { dnsservertest.NewAAAA(filtertest.ReqFQDN, ttl, blockedIPv6), }, qType: dns.TypeAAAA, + }, { + name: "ipv4hint", + reqFQDN: filtertest.ReqFQDN, + wantRule: blockedIPv4Str, + respAns: dnsservertest.SectionAnswer{ + dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{blockedIPv4}, []net.IP{}), + }, + qType: dns.TypeHTTPS, + }, { + name: "ipv6hint", + reqFQDN: filtertest.ReqFQDN, + wantRule: blockedIPv6Str, + respAns: dnsservertest.SectionAnswer{ + dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{}, []net.IP{blockedIPv6}), + }, + qType: dns.TypeHTTPS, + }, { + name: "ipv4_ipv6_hints", + reqFQDN: filtertest.ReqFQDN, + wantRule: blockedIPv4Str, + respAns: dnsservertest.SectionAnswer{ + dnsservertest.NewHTTPS( + filtertest.ReqFQDN, + ttl, + []net.IP{blockedIPv4}, + []net.IP{blockedIPv6}, + ), + }, + qType: dns.TypeHTTPS, + }, { + name: "pass_hints", + reqFQDN: filtertest.ReqFQDN, + wantRule: "", + respAns: dnsservertest.SectionAnswer{ + dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{passedIPv4}, []net.IP{}), + }, + qType: dns.TypeHTTPS, }} for _, tc := range testCases { @@ -511,6 +560,12 @@ func TestFilter_FilterResponse(t *testing.T) { res, err := f.FilterResponse(ctx, resp, ri) require.NoError(t, err) + if tc.wantRule == "" { + assert.Nil(t, res) + + return + } + want := &internal.ResultBlocked{ List: testFltListID1, Rule: tc.wantRule, diff --git a/internal/filter/internal/filtertest/filtertest.go b/internal/filter/internal/filtertest/filtertest.go index 61082cc..dd67877 100644 --- a/internal/filter/internal/filtertest/filtertest.go +++ b/internal/filter/internal/filtertest/filtertest.go @@ -16,6 +16,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/testutil" + "github.com/c2h5oh/datasize" "github.com/stretchr/testify/require" ) @@ -35,9 +36,19 @@ const ReqFQDN = ReqHost + "." // ServerName is the common server name for filtering tests. const ServerName = "testServer/1.0" +// CacheTTL is the common long cache-TTL for filtering tests. +const CacheTTL = 1 * time.Hour + +// Staleness is the common long staleness for filtering tests. +const Staleness = 1 * time.Hour + // Timeout is the common timeout for filtering tests. const Timeout = 1 * time.Second +// FilterMaxSize is the maximum size of the downloadable rule-list for filtering +// tests. +const FilterMaxSize = 640 * int64(datasize.KB) + // PrepareRefreshable launches an HTTP server serving the given text and code, // as well as creates a cache file. If code is zero, the server isn't started. // If reqCh not nil, a signal is sent every time the server is called. The diff --git a/internal/filter/internal/internal.go b/internal/filter/internal/internal.go index 1db7315..bceed64 100644 --- a/internal/filter/internal/internal.go +++ b/internal/filter/internal/internal.go @@ -9,7 +9,6 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/c2h5oh/datasize" "github.com/miekg/dns" ) @@ -29,13 +28,8 @@ type Interface interface { FilterResponse(ctx context.Context, resp *dns.Msg, ri *agd.RequestInfo) (r Result, err error) } -// maxFilterSize is the maximum size of downloaded filters. -const maxFilterSize = 256 * int64(datasize.MB) - // DefaultFilterRefreshTimeout is the default timeout to use when fetching // filter lists data. -// -// TODO(a.garipov): Consider making timeouts where they are used configurable. const DefaultFilterRefreshTimeout = 3 * time.Minute // DefaultResolveTimeout is the default timeout for resolving hosts for diff --git a/internal/filter/internal/refreshable.go b/internal/filter/internal/refreshable.go index 57bc5f7..e850576 100644 --- a/internal/filter/internal/refreshable.go +++ b/internal/filter/internal/refreshable.go @@ -18,45 +18,61 @@ import ( "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/log" - "github.com/google/renameio" + renameio "github.com/google/renameio/v2" ) // Refreshable contains entities common to filters that can refresh themselves // from a file and a URL. type Refreshable struct { - // http is the HTTP client used to refresh the filter. - http *agdhttp.Client - - // url is the URL used to refresh the filter. - url *url.URL - - // id is the filter list ID, if any. - id agd.FilterListID - - // cachePath is the path to the file containing the cached filter rules. + http *agdhttp.Client + url *url.URL + id agd.FilterListID cachePath string - - // staleness is the time after which a file is considered stale. staleness time.Duration + maxSize int64 } -// NewRefreshable returns a new refreshable filter. All parameters must be -// non-zero. -func NewRefreshable(l *agd.FilterList, cachePath string) (f *Refreshable) { +// RefreshableConfig is the configuration structure for a refreshable filter. +type RefreshableConfig struct { + // URL is the URL used to refresh the filter. + URL *url.URL + + // ID is the filter list ID for this filter. + ID agd.FilterListID + + // CachePath is the path to the file containing the cached filter rules. + CachePath string + + // Staleness is the time after which a file is considered stale. + Staleness time.Duration + + // Timeout is the timeout for the HTTP client used by this refreshable + // filter. + Timeout time.Duration + + // MaxSize is the maximum size in bytes of the downloadable filter content. + MaxSize int64 +} + +// NewRefreshable returns a new refreshable filter. c must not be nil. +func NewRefreshable(c *RefreshableConfig) (f *Refreshable) { return &Refreshable{ http: agdhttp.NewClient(&agdhttp.ClientConfig{ - Timeout: DefaultFilterRefreshTimeout, + Timeout: c.Timeout, }), - url: l.URL, - id: l.ID, - cachePath: cachePath, - staleness: l.RefreshIvl, + url: c.URL, + id: c.ID, + cachePath: c.CachePath, + staleness: c.Staleness, + maxSize: c.MaxSize, } } // Refresh reloads the filter data. If acceptStale is true, refresh doesn't try // to load the filter data from its URL when there is already a file in the // cache directory, regardless of its staleness. +// +// TODO(a.garipov): Consider making refresh return a reader instead of a string. func (f *Refreshable) Refresh( ctx context.Context, acceptStale bool, @@ -77,6 +93,8 @@ func (f *Refreshable) Refresh( if err != nil { return "", fmt.Errorf("refreshing from url %q: %w", f.url, err) } + } else { + log.Info("%s: using cached data from file %q", f.id, f.cachePath) } return text, nil @@ -163,7 +181,7 @@ func (f *Refreshable) refreshFromURL( b := &strings.Builder{} mw := io.MultiWriter(b, tmpFile) - _, err = io.Copy(mw, agdio.LimitReader(resp.Body, maxFilterSize)) + _, err = io.Copy(mw, agdio.LimitReader(resp.Body, f.maxSize)) if err != nil { return "", agdhttp.WrapServerError(fmt.Errorf("reading into file: %w", err), resp) } diff --git a/internal/filter/internal/refreshable_test.go b/internal/filter/internal/refreshable_test.go index 146098a..c453d9f 100644 --- a/internal/filter/internal/refreshable_test.go +++ b/internal/filter/internal/refreshable_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/testutil" @@ -20,12 +19,13 @@ import ( // refrID is the ID of a [agd.FilterList] used for testing. const refrID = "test_id" -func TestRefreshable_Refresh(t *testing.T) { - const ( - defaultFileText = "||filefilter.example\n" - defaultURLText = "||urlfilter.example\n" - ) +// Default texts for tests. +const ( + testFileText = "||filefilter.example\n" + testURLText = "||urlfilter.example\n" +) +func TestRefreshable_Refresh(t *testing.T) { testCases := []struct { name string wantText string @@ -38,9 +38,9 @@ func TestRefreshable_Refresh(t *testing.T) { useCacheFile bool }{{ name: "no_file", - wantText: defaultURLText, + wantText: testURLText, wantErrMsg: "", - srvText: defaultURLText, + srvText: testURLText, staleness: 0, srvCode: http.StatusOK, acceptStale: true, @@ -71,19 +71,19 @@ func TestRefreshable_Refresh(t *testing.T) { useCacheFile: false, }, { name: "file", - wantText: defaultFileText, + wantText: testFileText, wantErrMsg: "", srvText: "", - staleness: 1 * time.Hour, + staleness: filtertest.Staleness, srvCode: 0, acceptStale: true, expectReq: false, useCacheFile: true, }, { name: "file_stale", - wantText: defaultURLText, + wantText: testURLText, wantErrMsg: "", - srvText: defaultURLText, + srvText: testURLText, staleness: -1 * time.Hour, srvCode: http.StatusOK, acceptStale: false, @@ -91,7 +91,7 @@ func TestRefreshable_Refresh(t *testing.T) { useCacheFile: true, }, { name: "file_stale_accept", - wantText: defaultFileText, + wantText: testFileText, wantErrMsg: "", srvText: "", staleness: -1 * time.Hour, @@ -103,32 +103,25 @@ func TestRefreshable_Refresh(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - var err error - reqCh := make(chan struct{}, 1) - var cachePath string realCachePath, srvURL := filtertest.PrepareRefreshable(t, reqCh, tc.srvText, tc.srvCode) - if tc.useCacheFile { - cachePath = realCachePath + cachePath := prepareCachePath(t, realCachePath, tc.useCacheFile) - err = os.WriteFile(cachePath, []byte(defaultFileText), 0o600) - require.NoError(t, err) - } else { - cachePath = filepath.Join(t.TempDir(), "does_not_exist") + c := &internal.RefreshableConfig{ + URL: srvURL, + ID: refrID, + CachePath: cachePath, + Staleness: tc.staleness, + Timeout: filtertest.Timeout, + MaxSize: filtertest.FilterMaxSize, } - fl := &agd.FilterList{ - URL: srvURL, - ID: refrID, - RefreshIvl: tc.staleness, - } - f := internal.NewRefreshable(fl, cachePath) + f := internal.NewRefreshable(c) ctx, cancel := context.WithTimeout(context.Background(), filtertest.Timeout) t.Cleanup(cancel) - var gotText string - gotText, err = f.Refresh(ctx, tc.acceptStale) + gotText, err := f.Refresh(ctx, tc.acceptStale) if tc.expectReq { testutil.RequireReceive(t, reqCh, filtertest.Timeout) } @@ -145,21 +138,38 @@ func TestRefreshable_Refresh(t *testing.T) { } } +// prepareCachePath is a helper that either returns a non-existing file (if +// useCacheFile is false) or prepares a cache file using realCachePath and +// [testFileText]. +func prepareCachePath(t *testing.T, realCachePath string, useCacheFile bool) (cachePath string) { + t.Helper() + + if !useCacheFile { + return filepath.Join(t.TempDir(), "does_not_exist") + } + + err := os.WriteFile(realCachePath, []byte(testFileText), 0o600) + require.NoError(t, err) + + return realCachePath +} + func TestRefreshable_Refresh_properStaleness(t *testing.T) { - const ( - responseDur = time.Second / 5 - staleness = time.Hour - ) + const responseDur = time.Second / 5 reqCh := make(chan struct{}) cachePath, addr := filtertest.PrepareRefreshable(t, reqCh, filtertest.BlockRule, http.StatusOK) - fl := &agd.FilterList{ - URL: addr, - ID: refrID, - RefreshIvl: staleness, + c := &internal.RefreshableConfig{ + URL: addr, + ID: refrID, + CachePath: cachePath, + Staleness: filtertest.Staleness, + Timeout: filtertest.Timeout, + MaxSize: filtertest.FilterMaxSize, } - f := internal.NewRefreshable(fl, cachePath) + + f := internal.NewRefreshable(c) ctx, cancel := context.WithTimeout(context.Background(), filtertest.Timeout) t.Cleanup(cancel) diff --git a/internal/filter/internal/rulelist/refreshable.go b/internal/filter/internal/rulelist/refreshable.go index 52d7031..625ff24 100644 --- a/internal/filter/internal/rulelist/refreshable.go +++ b/internal/filter/internal/rulelist/refreshable.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/netip" - "path/filepath" "sync" "github.com/AdguardTeam/AdGuardDNS/internal/agd" @@ -32,21 +31,28 @@ type Refreshable struct { } // NewRefreshable returns a new refreshable DNS request and response filter -// based on the provided rule list. l must be non-nil. The initial refresh +// based on the provided rule list. c must be non-nil. The initial refresh // should be called explicitly if necessary. func NewRefreshable( - l *agd.FilterList, - fileCacheDir string, + c *internal.RefreshableConfig, memCacheSize int, useMemCache bool, ) (f *Refreshable) { f = &Refreshable{ - mu: &sync.RWMutex{}, - refr: internal.NewRefreshable(l, filepath.Join(fileCacheDir, string(l.ID))), + mu: &sync.RWMutex{}, + refr: internal.NewRefreshable(&internal.RefreshableConfig{ + URL: c.URL, + ID: c.ID, + CachePath: c.CachePath, + Staleness: c.Staleness, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + MaxSize: c.MaxSize, + }), } var err error - f.filter, err = newFilter("", l.ID, "", memCacheSize, useMemCache) + f.filter, err = newFilter("", c.ID, "", memCacheSize, useMemCache) if err != nil { // Should never happen, since text is empty. panic(fmt.Errorf("unexpected filter error: %w", err)) diff --git a/internal/filter/internal/rulelist/refreshable_test.go b/internal/filter/internal/rulelist/refreshable_test.go index 01086ee..93ceff8 100644 --- a/internal/filter/internal/rulelist/refreshable_test.go +++ b/internal/filter/internal/rulelist/refreshable_test.go @@ -4,11 +4,10 @@ import ( "context" "net/http" "net/netip" - "path/filepath" "testing" - "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/golibs/testutil" @@ -82,12 +81,13 @@ func TestRefreshable_ID(t *testing.T) { func TestRefreshable_Refresh(t *testing.T) { cachePath, srvURL := filtertest.PrepareRefreshable(t, nil, testBlockRule, http.StatusOK) rl := rulelist.NewRefreshable( - &agd.FilterList{ - URL: srvURL, - ID: testFltListID, - RefreshIvl: 1 * time.Hour, + &internal.RefreshableConfig{ + URL: srvURL, + ID: testFltListID, + CachePath: cachePath, + Staleness: filtertest.Staleness, + MaxSize: filtertest.FilterMaxSize, }, - filepath.Dir(cachePath), 100, true, ) diff --git a/internal/filter/internal/rulelist/rulelist.go b/internal/filter/internal/rulelist/rulelist.go index 114e50e..c2c0913 100644 --- a/internal/filter/internal/rulelist/rulelist.go +++ b/internal/filter/internal/rulelist/rulelist.go @@ -18,7 +18,7 @@ import ( // newURLFilterID returns a new random ID for the urlfilter DNS engine to use. func newURLFilterID() (id int) { // #nosec G404 -- Do not use cryptographically random ID generation, since - // these are only used in one place, internal/filter.compFilter.filterMsg, + // these are only used in ../composite/Filter.mustRuleListDataByURLFilterID // and are not used in any security-sensitive context. // // Despite the fact that the type of integer filter list IDs in module diff --git a/internal/filter/internal/safesearch/safesearch.go b/internal/filter/internal/safesearch/safesearch.go index 1a50da1..3acac2d 100644 --- a/internal/filter/internal/safesearch/safesearch.go +++ b/internal/filter/internal/safesearch/safesearch.go @@ -32,8 +32,9 @@ type Filter struct { // Config contains configuration for the safe-search filter. type Config struct { - // List is the filtering-rule list used to filter requests. - List *agd.FilterList + // Refreshable is the configuration of the refreshable filter-list within + // the safe-search filter. + Refreshable *internal.RefreshableConfig // Resolver is used to resolve the IP addresses of replacement hosts. Resolver agdnet.Resolver @@ -41,10 +42,6 @@ type Config struct { // ErrColl is used to report errors of replacement-host resolving. ErrColl agd.ErrorCollector - // CacheDir is the path to the directory where the cached filter files are - // put. The directory must exist. - CacheDir string - // CacheTTL is the time to live of the result cache-items. // //lint:ignore U1000 TODO(a.garipov): Currently unused. See AGDNS-398. @@ -57,15 +54,13 @@ type Config struct { // New returns a new safe-search filter. c must not be nil. The initial // refresh should be called explicitly if necessary. func New(c *Config) (f *Filter) { - id := c.List.ID - return &Filter{ resCache: resultcache.New[*internal.ResultModified](c.CacheSize), // Don't use the rule list cache, since safeSearch already has its own. - flt: rulelist.NewRefreshable(c.List, c.CacheDir, 0, false), + flt: rulelist.NewRefreshable(c.Refreshable, 0, false), resolver: c.Resolver, errColl: c.ErrColl, - id: id, + id: c.Refreshable.ID, } } diff --git a/internal/filter/internal/safesearch/safesearch_test.go b/internal/filter/internal/safesearch/safesearch_test.go index 1bb4015..fcaf66e 100644 --- a/internal/filter/internal/safesearch/safesearch_test.go +++ b/internal/filter/internal/safesearch/safesearch_test.go @@ -60,9 +60,13 @@ func TestFilter(t *testing.T) { require.NoError(t, err) f := safesearch.New(&safesearch.Config{ - List: &agd.FilterList{ - ID: id, - URL: srvURL, + Refreshable: &internal.RefreshableConfig{ + ID: id, + URL: srvURL, + CachePath: cachePath, + Staleness: filtertest.Staleness, + Timeout: filtertest.Timeout, + MaxSize: filtertest.FilterMaxSize, }, Resolver: &agdtest.Resolver{ OnLookupIP: func( @@ -85,7 +89,6 @@ func TestFilter(t *testing.T) { panic("not implemented") }, }, - CacheDir: filepath.Dir(cachePath), CacheTTL: 1 * time.Minute, CacheSize: 100, }) diff --git a/internal/filter/internal/serviceblock/serviceblock.go b/internal/filter/internal/serviceblock/serviceblock.go index de378fd..d812fee 100644 --- a/internal/filter/internal/serviceblock/serviceblock.go +++ b/internal/filter/internal/serviceblock/serviceblock.go @@ -7,27 +7,22 @@ import ( "context" "encoding/json" "fmt" - "net/http" - "net/url" + "strings" "sync" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" ) // Filter is a service-blocking filter that uses rule lists that it gets from an // index. type Filter struct { - // url is the URL from which the services are fetched. - url *url.URL - - // http is the HTTP client used to refresh the filter. - http *agdhttp.Client + // refr is the helper entity containing the refreshable part of the index + // refresh and caching logic. + refr *internal.Refreshable // mu protects services. mu *sync.RWMutex @@ -39,18 +34,16 @@ type Filter struct { errColl agd.ErrorCollector } -// serviceRuleLists is convenient alias for a ID to filter mapping. +// serviceRuleLists is convenient alias for an ID to filter mapping. type serviceRuleLists = map[agd.BlockedServiceID]*rulelist.Immutable // New returns a fully initialized service blocker. -func New(indexURL *url.URL, errColl agd.ErrorCollector) (f *Filter) { +func New(refr *internal.Refreshable, errColl agd.ErrorCollector) (f *Filter) { return &Filter{ - url: indexURL, - http: agdhttp.NewClient(&agdhttp.ClientConfig{ - Timeout: internal.DefaultFilterRefreshTimeout, - }), - mu: &sync.RWMutex{}, - errColl: errColl, + refr: refr, + mu: &sync.RWMutex{}, + services: serviceRuleLists{}, + errColl: errColl, } } @@ -69,22 +62,23 @@ func (f *Filter) RuleLists( for _, id := range ids { rl := f.services[id] - if rl != nil { + if rl == nil { + log.Info("service filter: warning: no service with id %s", id) + } else { rls = append(rls, rl) - - continue } - - reportErr := fmt.Errorf("service filter: no service with id %s", id) - f.errColl.Collect(ctx, reportErr) - log.Info("warning: %s", reportErr) } return rls } // Refresh loads new service data from the index URL. -func (f *Filter) Refresh(ctx context.Context, cacheSize int, useCache bool) (err error) { +func (f *Filter) Refresh( + ctx context.Context, + cacheSize int, + useCache bool, + acceptStale bool, +) (err error) { fltIDStr := string(agd.FilterListIDBlockedService) defer func() { if err != nil { @@ -92,7 +86,7 @@ func (f *Filter) Refresh(ctx context.Context, cacheSize int, useCache bool) (err } }() - resp, err := f.loadIndex(ctx) + resp, err := f.loadIndex(ctx, acceptStale) if err != nil { // Don't wrap the error, because it's informative enough as is. return err @@ -122,25 +116,16 @@ func (f *Filter) Refresh(ctx context.Context, cacheSize int, useCache bool) (err } // loadIndex fetches, decodes, and returns the blocked service index data. -func (f *Filter) loadIndex(ctx context.Context) (resp *indexResp, err error) { - defer func() { err = errors.Annotate(err, "loading blocked service index from %q: %w", f.url) }() - - httpResp, err := f.http.Get(ctx, f.url) +func (f *Filter) loadIndex(ctx context.Context, acceptStale bool) (resp *indexResp, err error) { + text, err := f.refr.Refresh(ctx, acceptStale) if err != nil { - return nil, fmt.Errorf("requesting: %w", err) - } - defer func() { err = errors.WithDeferred(err, httpResp.Body.Close()) }() - - err = agdhttp.CheckStatus(httpResp, http.StatusOK) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err + return nil, fmt.Errorf("loading index: %w", err) } resp = &indexResp{} - err = json.NewDecoder(httpResp.Body).Decode(resp) + err = json.NewDecoder(strings.NewReader(text)).Decode(resp) if err != nil { - return nil, agdhttp.WrapServerError(fmt.Errorf("decoding: %w", err), httpResp) + return nil, fmt.Errorf("decoding index: %w", err) } log.Debug("service filter: loaded index with %d blocked services", len(resp.BlockedServices)) diff --git a/internal/filter/internal/serviceblock/serviceblock_test.go b/internal/filter/internal/serviceblock/serviceblock_test.go index e93d24b..ef03f7c 100644 --- a/internal/filter/internal/serviceblock/serviceblock_test.go +++ b/internal/filter/internal/serviceblock/serviceblock_test.go @@ -7,6 +7,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/serviceblock" "github.com/AdguardTeam/golibs/testutil" @@ -14,15 +15,20 @@ import ( "github.com/stretchr/testify/require" ) +func TestMain(m *testing.M) { + testutil.DiscardLogOutput(m) +} + // Common blocked service IDs for tests. const ( - testSvcID1 agd.BlockedServiceID = "svc_1" - testSvcID2 agd.BlockedServiceID = "svc_2" + testSvcID1 agd.BlockedServiceID = "svc_1" + testSvcID2 agd.BlockedServiceID = "svc_2" + testSvcIDNotPresent agd.BlockedServiceID = "svc_not_present" ) // testData is a sample of a service index response. // -// See https://github.com/atropnikov/HostlistsRegistry/blob/main/assets/services.json. +// See https://github.com/AdguardTeam/HostlistsRegistry/blob/main/assets/services.json. const testData string = `{ "blocked_services": [ { @@ -44,7 +50,7 @@ const testData string = `{ func TestFilter(t *testing.T) { reqCh := make(chan struct{}, 1) - _, srvURL := filtertest.PrepareRefreshable(t, reqCh, testData, http.StatusOK) + cachePath, srvURL := filtertest.PrepareRefreshable(t, reqCh, testData, http.StatusOK) errColl := &agdtest.ErrorCollector{ OnCollect: func(ctx context.Context, err error) { @@ -52,23 +58,40 @@ func TestFilter(t *testing.T) { }, } - f := serviceblock.New(srvURL, errColl) + refr := internal.NewRefreshable(&internal.RefreshableConfig{ + URL: srvURL, + ID: agd.FilterListIDBlockedService, + CachePath: cachePath, + Staleness: filtertest.Staleness, + Timeout: filtertest.Timeout, + MaxSize: filtertest.FilterMaxSize, + }) + + f := serviceblock.New(refr, errColl) ctx := context.Background() - err := f.Refresh(ctx, 0, false) + err := f.Refresh(ctx, 0, false, false) require.NoError(t, err) testutil.RequireReceive(t, reqCh, filtertest.Timeout) - svcIDs := []agd.BlockedServiceID{testSvcID1, testSvcID2} - rls := f.RuleLists(ctx, svcIDs) + rls := f.RuleLists(ctx, []agd.BlockedServiceID{ + testSvcID1, + testSvcID2, + testSvcIDNotPresent, + }) require.Len(t, rls, 2) + wantSvcIDs := []agd.BlockedServiceID{ + testSvcID1, + testSvcID2, + } + gotFltIDs := make([]agd.FilterListID, 2) gotSvcIDs := make([]agd.BlockedServiceID, 2) gotFltIDs[0], gotSvcIDs[0] = rls[0].ID() gotFltIDs[1], gotSvcIDs[1] = rls[1].ID() assert.Equal(t, agd.FilterListIDBlockedService, gotFltIDs[0]) assert.Equal(t, agd.FilterListIDBlockedService, gotFltIDs[1]) - assert.ElementsMatch(t, svcIDs, gotSvcIDs) + assert.ElementsMatch(t, wantSvcIDs, gotSvcIDs) } diff --git a/internal/filter/storage.go b/internal/filter/storage.go index d4f9fe3..4a09032 100644 --- a/internal/filter/storage.go +++ b/internal/filter/storage.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" "fmt" - "net/http" "net/url" + "path/filepath" + "strings" "sync" "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" @@ -20,13 +20,10 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/serviceblock" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" - "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/bluele/gcache" ) -// Filter storage - // Storage is a storage for filters. type Storage interface { // FilterFromContext returns a filter combining rules and types of filtering @@ -42,19 +39,15 @@ type Storage interface { // based on rule lists, custom filters of profiles, safe browsing, and safe // search ones. type DefaultStorage struct { + // refr is the helper entity containing the refreshable part of the index + // refresh and caching logic. + refr *internal.Refreshable + // mu protects ruleLists. mu *sync.RWMutex - // URL is the URL of the filtering rule index document. See filterIndexResp - // and related types. - url *url.URL - - // http is the HTTP client used to update the rule list filters from the - // index. - http *agdhttp.Client - // ruleLists are the filter list ID to a rule list filter map. - ruleLists map[agd.FilterListID]*rulelist.Refreshable + ruleLists filteringRuleLists // services is the service blocking filter. services *serviceblock.Filter @@ -65,6 +58,9 @@ type DefaultStorage struct { // adultBlocking is the adult content blocking safe browsing filter. adultBlocking *hashprefix.Filter + // newRegDomains is the newly registered domains filter. + newRegDomains *hashprefix.Filter + // genSafeSearch is the general safe search filter. genSafeSearch *safesearch.Filter @@ -93,10 +89,25 @@ type DefaultStorage struct { // filtering results. ruleListCacheSize int + // maxRuleListSize is the maximum size in bytes of the downloadable + // rule-list content. + maxRuleListSize int64 + // useRuleListCache, if true, enables rule list cache. useRuleListCache bool } +// filteringRuleLists is convenient alias for an ID to filter mapping. +type filteringRuleLists = map[agd.FilterListID]*rulelist.Refreshable + +// Filenames for filter indexes. +// +// TODO(ameshkov): Consider making configurable. +const ( + ruleListIndexFilename = "filters.json" + serviceIndexFilename = "services.json" +) + // DefaultStorageConfig contains configuration for a filter storage based on // rule lists. type DefaultStorageConfig struct { @@ -122,6 +133,10 @@ type DefaultStorageConfig struct { // browsing filter. It must not be nil. AdultBlocking *hashprefix.Filter + // NewRegDomains is the configuration for the newly registered domains safe + // browsing filter. It must not be nil. + NewRegDomains *hashprefix.Filter + // Now is a function that returns current time. Now func() (now time.Time) @@ -153,49 +168,84 @@ type DefaultStorageConfig struct { // RefreshIvl is the refresh interval for this storage. It defines how // often the filter rule lists are updated from the index. + // + // TODO(a.garipov): This value is used both for refreshes and for filter + // staleness, which can cause issues. Consider splitting the two. RefreshIvl time.Duration // UseRuleListCache, if true, enables rule list cache. UseRuleListCache bool + + // MaxRuleListSize is the maximum size in bytes of the downloadable + // rule-list content. + MaxRuleListSize int64 } // NewDefaultStorage returns a new filter storage. c must not be nil. func NewDefaultStorage(c *DefaultStorageConfig) (s *DefaultStorage, err error) { genSafeSearch := safesearch.New(&safesearch.Config{ - List: &agd.FilterList{ - URL: c.GeneralSafeSearchRulesURL, - ID: agd.FilterListIDGeneralSafeSearch, - RefreshIvl: c.RefreshIvl, + Refreshable: &internal.RefreshableConfig{ + URL: c.GeneralSafeSearchRulesURL, + ID: agd.FilterListIDGeneralSafeSearch, + CachePath: filepath.Join(c.CacheDir, string(agd.FilterListIDGeneralSafeSearch)), + Staleness: c.RefreshIvl, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + MaxSize: c.MaxRuleListSize, }, Resolver: c.Resolver, ErrColl: c.ErrColl, - CacheDir: c.CacheDir, CacheTTL: c.SafeSearchCacheTTL, CacheSize: c.SafeSearchCacheSize, }) ytSafeSearch := safesearch.New(&safesearch.Config{ - List: &agd.FilterList{ - URL: c.YoutubeSafeSearchRulesURL, - ID: agd.FilterListIDYoutubeSafeSearch, - RefreshIvl: c.RefreshIvl, + Refreshable: &internal.RefreshableConfig{ + URL: c.YoutubeSafeSearchRulesURL, + ID: agd.FilterListIDYoutubeSafeSearch, + CachePath: filepath.Join(c.CacheDir, string(agd.FilterListIDYoutubeSafeSearch)), + Staleness: c.RefreshIvl, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + MaxSize: c.MaxRuleListSize, }, Resolver: c.Resolver, ErrColl: c.ErrColl, - CacheDir: c.CacheDir, CacheTTL: c.SafeSearchCacheTTL, CacheSize: c.SafeSearchCacheSize, }) + ruleListIdxRefr := internal.NewRefreshable(&internal.RefreshableConfig{ + URL: c.FilterIndexURL, + // TODO(a.garipov): Consider adding special IDs for indexes. + ID: "rule_list_index", + CachePath: filepath.Join(c.CacheDir, ruleListIndexFilename), + Staleness: c.RefreshIvl, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + // TODO(a.garipov): Consider using a different limit here. + MaxSize: c.MaxRuleListSize, + }) + + svcIdxRefr := internal.NewRefreshable(&internal.RefreshableConfig{ + URL: c.BlockedServiceIndexURL, + // TODO(a.garipov): Consider adding special IDs for indexes. + ID: "blocked_service_index", + CachePath: filepath.Join(c.CacheDir, serviceIndexFilename), + Staleness: c.RefreshIvl, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + // TODO(a.garipov): Consider using a different limit here. + MaxSize: c.MaxRuleListSize, + }) + s = &DefaultStorage{ - mu: &sync.RWMutex{}, - url: c.FilterIndexURL, - http: agdhttp.NewClient(&agdhttp.ClientConfig{ - Timeout: internal.DefaultFilterRefreshTimeout, - }), - services: serviceblock.New(c.BlockedServiceIndexURL, c.ErrColl), + refr: ruleListIdxRefr, + mu: &sync.RWMutex{}, + services: serviceblock.New(svcIdxRefr, c.ErrColl), safeBrowsing: c.SafeBrowsing, adultBlocking: c.AdultBlocking, + newRegDomains: c.NewRegDomains, genSafeSearch: genSafeSearch, ytSafeSearch: ytSafeSearch, now: c.Now, @@ -208,6 +258,7 @@ func NewDefaultStorage(c *DefaultStorageConfig) (s *DefaultStorage, err error) { refreshIvl: c.RefreshIvl, ruleListCacheSize: c.RuleListCacheSize, useRuleListCache: c.UseRuleListCache, + maxRuleListSize: c.MaxRuleListSize, } err = s.refresh(context.Background(), true) @@ -234,7 +285,7 @@ func (s *DefaultStorage) FilterFromContext(ctx context.Context, ri *agd.RequestI c.RuleLists = s.filters(g.RuleListIDs) } - c.SafeBrowsing, c.AdultBlocking = s.safeBrowsingForGroup(g) + c.SafeBrowsing, c.AdultBlocking, c.NewRegisteredDomains = s.safeBrowsingForGroup(g) c.GeneralSafeSearch, c.YouTubeSafeSearch = s.safeSearchForGroup(g) return composite.New(c) @@ -267,7 +318,7 @@ func (s *DefaultStorage) filterForProfile(ctx context.Context, ri *agd.RequestIn c.ServiceLists = s.serviceFilters(ctx, p, parentalEnabled) - c.SafeBrowsing, c.AdultBlocking = s.safeBrowsingForProfile(p, parentalEnabled) + c.SafeBrowsing, c.AdultBlocking, c.NewRegisteredDomains = s.safeBrowsingForProfile(p, parentalEnabled) c.GeneralSafeSearch, c.YouTubeSafeSearch = s.safeSearchForProfile(p, parentalEnabled) return composite.New(c) @@ -302,16 +353,22 @@ func (s *DefaultStorage) pcBySchedule(sch *agd.ParentalProtectionSchedule) (ok b func (s *DefaultStorage) safeBrowsingForProfile( p *agd.Profile, parentalEnabled bool, -) (safeBrowsing, adultBlocking *hashprefix.Filter) { - if p.SafeBrowsingEnabled { - safeBrowsing = s.safeBrowsing +) (safeBrowsing, adultBlocking, newRegDomains *hashprefix.Filter) { + if p.SafeBrowsing != nil && p.SafeBrowsing.Enabled { + if p.SafeBrowsing.BlockDangerousDomains { + safeBrowsing = s.safeBrowsing + } + + if p.SafeBrowsing.BlockNewlyRegisteredDomains { + newRegDomains = s.newRegDomains + } } if parentalEnabled && p.Parental.BlockAdult { adultBlocking = s.adultBlocking } - return safeBrowsing, adultBlocking + return safeBrowsing, adultBlocking, newRegDomains } // safeSearchForProfile returns safe search filters based on the information in @@ -339,16 +396,22 @@ func (s *DefaultStorage) safeSearchForProfile( // in the filtering group. g must not be nil. func (s *DefaultStorage) safeBrowsingForGroup( g *agd.FilteringGroup, -) (safeBrowsing, adultBlocking *hashprefix.Filter) { +) (safeBrowsing, adultBlocking, newRegDomains *hashprefix.Filter) { if g.SafeBrowsingEnabled { - safeBrowsing = s.safeBrowsing + if g.BlockDangerousDomains { + safeBrowsing = s.safeBrowsing + } + + if g.BlockNewlyRegisteredDomains { + newRegDomains = s.newRegDomains + } } if g.ParentalEnabled && g.BlockAdult { adultBlocking = s.adultBlocking } - return safeBrowsing, adultBlocking + return safeBrowsing, adultBlocking, newRegDomains } // safeSearchForGroup returns safe search filters based on the information in @@ -414,9 +477,7 @@ func (s *DefaultStorage) Refresh(ctx context.Context) (err error) { // refreshes the index from the index URL and updates all rule list filters, as // well as the service filters. func (s *DefaultStorage) refresh(ctx context.Context, acceptStale bool) (err error) { - log.Info("%s: requesting %s", strgLogPrefix, s.url) - - resp, err := s.loadIndex(ctx) + resp, err := s.loadIndex(ctx, acceptStale) if err != nil { // Don't wrap the error, because it's informative enough as is. return err @@ -424,50 +485,18 @@ func (s *DefaultStorage) refresh(ctx context.Context, acceptStale bool) (err err log.Info("%s: got %d filters from index", strgLogPrefix, len(resp.Filters)) - fls := resp.toInternal(ctx, s.errColl, s.refreshIvl) + fls := resp.toInternal(ctx, s.errColl) log.Info("%s: got %d filter lists from index after validations", strgLogPrefix, len(fls)) - ruleLists := make(map[agd.FilterListID]*rulelist.Refreshable, len(resp.Filters)) + ruleLists := make(filteringRuleLists, len(resp.Filters)) for _, fl := range fls { - if _, ok := ruleLists[fl.ID]; ok { - agd.Collectf(ctx, s.errColl, "%s: duplicated id %q", strgLogPrefix, fl.ID) - - continue - } - - fltIDStr := string(fl.ID) - rl := rulelist.NewRefreshable( - fl, - s.cacheDir, - s.ruleListCacheSize, - s.useRuleListCache, - ) - err = rl.Refresh(ctx, acceptStale) - if err == nil { - ruleLists[fl.ID] = rl - - metrics.FilterUpdatedStatus.WithLabelValues(fltIDStr).Set(1) - metrics.FilterUpdatedTime.WithLabelValues(fltIDStr).SetToCurrentTime() - metrics.FilterRulesTotal.WithLabelValues(fltIDStr).Set(float64(rl.RulesCount())) - - continue - } - - agd.Collectf(ctx, s.errColl, "%s: refreshing %q: %w", strgLogPrefix, fl.ID, err) - metrics.FilterUpdatedStatus.WithLabelValues(fltIDStr).Set(0) - - // If we can't get the new filter, and there is an old version of the - // same rule list, use it. - rls := s.filters([]agd.FilterListID{fl.ID}) - if len(rls) > 0 { - ruleLists[fl.ID] = rls[0] - } + s.addRuleList(ctx, ruleLists, fl, acceptStale) } log.Info("%s: got %d filter lists from index after compilation", strgLogPrefix, len(ruleLists)) - err = s.services.Refresh(ctx, s.ruleListCacheSize, s.useRuleListCache) + err = s.services.Refresh(ctx, s.ruleListCacheSize, s.useRuleListCache, acceptStale) if err != nil { const errFmt = "refreshing blocked services: %w" agd.Collectf(ctx, s.errColl, errFmt, err) @@ -490,90 +519,80 @@ func (s *DefaultStorage) refresh(ctx context.Context, acceptStale bool) (err err return nil } +// addRuleList adds the data from fl to ruleLists and handles all validations +// and errors. +func (s *DefaultStorage) addRuleList( + ctx context.Context, + ruleLists filteringRuleLists, + fl *filterIndexFilterData, + acceptStale bool, +) { + if _, ok := ruleLists[fl.id]; ok { + agd.Collectf(ctx, s.errColl, "%s: duplicated id %q", strgLogPrefix, fl.id) + + return + } + + fltIDStr := string(fl.id) + rl := rulelist.NewRefreshable( + &internal.RefreshableConfig{ + URL: fl.url, + ID: fl.id, + CachePath: filepath.Join(s.cacheDir, fltIDStr), + Staleness: s.refreshIvl, + // TODO(ameshkov): Consider making configurable. + Timeout: internal.DefaultFilterRefreshTimeout, + MaxSize: s.maxRuleListSize, + }, + s.ruleListCacheSize, + s.useRuleListCache, + ) + err := rl.Refresh(ctx, acceptStale) + if err == nil { + ruleLists[fl.id] = rl + + metrics.FilterUpdatedStatus.WithLabelValues(fltIDStr).Set(1) + metrics.FilterUpdatedTime.WithLabelValues(fltIDStr).SetToCurrentTime() + metrics.FilterRulesTotal.WithLabelValues(fltIDStr).Set(float64(rl.RulesCount())) + + return + } + + agd.Collectf(ctx, s.errColl, "%s: refreshing %q: %w", strgLogPrefix, fl.id, err) + metrics.FilterUpdatedStatus.WithLabelValues(fltIDStr).Set(0) + + // If we can't get the new filter, and there is an old version of the same + // rule list, use it. + rls := s.filters([]agd.FilterListID{fl.id}) + if len(rls) > 0 { + ruleLists[fl.id] = rls[0] + } +} + // loadIndex fetches, decodes, and returns the filter list index data of the // storage. -func (s *DefaultStorage) loadIndex(ctx context.Context) (resp *filterIndexResp, err error) { - defer func() { err = errors.Annotate(err, "loading filter index from %q: %w", s.url) }() - - httpResp, err := s.http.Get(ctx, s.url) +func (s *DefaultStorage) loadIndex( + ctx context.Context, + acceptStale bool, +) (resp *filterIndexResp, err error) { + text, err := s.refr.Refresh(ctx, acceptStale) if err != nil { - return nil, fmt.Errorf("requesting: %w", err) - } - defer func() { err = errors.WithDeferred(err, httpResp.Body.Close()) }() - - err = agdhttp.CheckStatus(httpResp, http.StatusOK) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err + return nil, fmt.Errorf("loading index: %w", err) } resp = &filterIndexResp{} - err = json.NewDecoder(httpResp.Body).Decode(resp) + err = json.NewDecoder(strings.NewReader(text)).Decode(resp) if err != nil { return nil, fmt.Errorf("decoding: %w", err) } - log.Debug("%s: loaded index with %d filters", strgLogPrefix, len(resp.Filters)) - return resp, nil } // setRuleLists replaces the storage's rule lists. -func (s *DefaultStorage) setRuleLists(ruleLists map[agd.FilterListID]*rulelist.Refreshable) { +func (s *DefaultStorage) setRuleLists(ruleLists filteringRuleLists) { s.mu.Lock() defer s.mu.Unlock() s.ruleLists = ruleLists } - -// filterIndexResp is the struct for the JSON response from a filter index API. -type filterIndexResp struct { - Filters []*filterIndexRespFilter `json:"filters"` -} - -// toInternal converts the filters from the index to []*agd.FilterList. -func (r *filterIndexResp) toInternal( - ctx context.Context, - errColl agd.ErrorCollector, - refreshIvl time.Duration, -) (fls []*agd.FilterList) { - fls = make([]*agd.FilterList, 0, len(r.Filters)) - for _, rf := range r.Filters { - id, err := agd.NewFilterListID(rf.ID) - if err != nil { - agd.Collectf(ctx, errColl, "%s: validating id %q: %w", strgLogPrefix, rf.ID, err) - - continue - } - - var u *url.URL - u, err = agdhttp.ParseHTTPURL(rf.DownloadURL) - if err != nil { - agd.Collectf( - ctx, - errColl, - "%s: validating url %q: %w", - strgLogPrefix, - rf.DownloadURL, - err, - ) - - continue - } - - fls = append(fls, &agd.FilterList{ - URL: u, - ID: id, - RefreshIvl: refreshIvl, - }) - } - - return fls -} - -// filterIndexRespFilter is the struct for a filter from the JSON response from -// a filter index API. -type filterIndexRespFilter struct { - DownloadURL string `json:"downloadUrl"` - ID string `json:"filterId"` -} diff --git a/internal/filter/storage_test.go b/internal/filter/storage_test.go index dcb4199..a72a728 100644 --- a/internal/filter/storage_test.go +++ b/internal/filter/storage_test.go @@ -15,6 +15,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" + "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" @@ -159,8 +160,8 @@ func TestStorage_FilterFromContext_customAllow(t *testing.T) { ID: agd.FilterListIDSafeBrowsing, CachePath: tmpFile.Name(), ReplacementHost: safeBrowsingSafeHost, - Staleness: 1 * time.Hour, - CacheTTL: 10 * time.Second, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 100, }) require.NoError(t, err) @@ -176,9 +177,13 @@ func TestStorage_FilterFromContext_customAllow(t *testing.T) { Parental: &agd.ParentalProtectionSettings{ Enabled: true, }, - ID: "prof1234", - FilteringEnabled: true, - SafeBrowsingEnabled: true, + ID: "prof1234", + FilteringEnabled: true, + SafeBrowsing: &agd.SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNewlyRegisteredDomains: false, + }, CustomRules: []agd.FilterRuleText{ safeBrowsingAllowRule, }, @@ -252,8 +257,8 @@ func TestStorage_FilterFromContext_schedule(t *testing.T) { ID: agd.FilterListIDAdultBlocking, CachePath: tmpFile.Name(), ReplacementHost: safeBrowsingSafeHost, - Staleness: 1 * time.Hour, - CacheTTL: 10 * time.Second, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 100, }) require.NoError(t, err) @@ -754,8 +759,8 @@ func TestStorage_FilterFromContext_safeBrowsing(t *testing.T) { ID: agd.FilterListIDSafeBrowsing, CachePath: cachePath, ReplacementHost: safeBrowsingSafeHost, - Staleness: 1 * time.Hour, - CacheTTL: 10 * time.Second, + Staleness: filtertest.Staleness, + CacheTTL: filtertest.CacheTTL, CacheSize: 100, }) require.NoError(t, err) @@ -767,10 +772,12 @@ func TestStorage_FilterFromContext_safeBrowsing(t *testing.T) { require.NoError(t, err) g := &agd.FilteringGroup{ - ID: "default", - RuleListIDs: []agd.FilterListID{}, - ParentalEnabled: true, - SafeBrowsingEnabled: true, + ID: "default", + RuleListIDs: []agd.FilterListID{}, + ParentalEnabled: true, + SafeBrowsingEnabled: true, + BlockDangerousDomains: true, + BlockNewlyRegisteredDomains: true, } // Test diff --git a/internal/geoip/asntops.go b/internal/geoip/asntops.go index d69f7bf..43d4305 100644 --- a/internal/geoip/asntops.go +++ b/internal/geoip/asntops.go @@ -4,8 +4,9 @@ package geoip import "github.com/AdguardTeam/AdGuardDNS/internal/agd" -// allTopASNs contains all specially handled ASNs. -var allTopASNs = map[agd.ASN]struct{}{ +// DefaultTopASNs contains all specially handled ASNs. +var DefaultTopASNs = map[agd.ASN]struct{}{ + 2: {}, 577: {}, 701: {}, 812: {}, @@ -26,12 +27,14 @@ var allTopASNs = map[agd.ASN]struct{}{ 2519: {}, 2527: {}, 2586: {}, + 2609: {}, 2740: {}, 2856: {}, 2860: {}, 3209: {}, 3212: {}, 3215: {}, + 3216: {}, 3238: {}, 3243: {}, 3249: {}, @@ -52,7 +55,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 4134: {}, 4609: {}, 4638: {}, - 4648: {}, 4657: {}, 4713: {}, 4760: {}, @@ -64,6 +66,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 4775: {}, 4788: {}, 4804: {}, + 4808: {}, 4812: {}, 4817: {}, 4818: {}, @@ -83,7 +86,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 5610: {}, 5617: {}, 5639: {}, - 5645: {}, 5769: {}, 6057: {}, 6147: {}, @@ -91,7 +93,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 6306: {}, 6327: {}, 6400: {}, - 6535: {}, 6568: {}, 6639: {}, 6661: {}, @@ -131,23 +132,19 @@ var allTopASNs = map[agd.ASN]struct{}{ 8359: {}, 8374: {}, 8376: {}, - 8386: {}, 8400: {}, 8402: {}, 8412: {}, 8447: {}, 8452: {}, 8473: {}, + 8544: {}, 8551: {}, - 8560: {}, 8585: {}, - 8632: {}, - 8661: {}, 8680: {}, 8681: {}, 8697: {}, 8708: {}, - 8728: {}, 8764: {}, 8781: {}, 8818: {}, @@ -155,11 +152,10 @@ var allTopASNs = map[agd.ASN]struct{}{ 8881: {}, 8926: {}, 8953: {}, - 8978: {}, + 8966: {}, 9009: {}, 9038: {}, 9051: {}, - 9105: {}, 9121: {}, 9146: {}, 9158: {}, @@ -199,12 +195,13 @@ var allTopASNs = map[agd.ASN]struct{}{ 10269: {}, 10396: {}, 10620: {}, - 10796: {}, 11081: {}, 11139: {}, + 11269: {}, 11315: {}, 11427: {}, 11556: {}, + 11562: {}, 11594: {}, 11664: {}, 11816: {}, @@ -219,9 +216,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 12392: {}, 12400: {}, 12430: {}, - 12455: {}, 12479: {}, - 12576: {}, 12578: {}, 12709: {}, 12716: {}, @@ -240,9 +235,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 12975: {}, 12997: {}, 13036: {}, - 13046: {}, 13122: {}, - 13124: {}, 13194: {}, 13280: {}, 13285: {}, @@ -255,6 +248,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 14117: {}, 14434: {}, 14522: {}, + 14593: {}, 14638: {}, 14709: {}, 14754: {}, @@ -270,7 +264,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 15480: {}, 15502: {}, 15557: {}, - 15659: {}, 15704: {}, 15706: {}, 15735: {}, @@ -291,13 +284,13 @@ var allTopASNs = map[agd.ASN]struct{}{ 16135: {}, 16232: {}, 16276: {}, - 16322: {}, 16345: {}, - 16437: {}, + 16509: {}, 16637: {}, 16705: {}, 17072: {}, 17079: {}, + 17400: {}, 17421: {}, 17458: {}, 17470: {}, @@ -322,6 +315,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 18809: {}, 18881: {}, 19114: {}, + 19246: {}, 19429: {}, 19711: {}, 19863: {}, @@ -350,12 +344,13 @@ var allTopASNs = map[agd.ASN]struct{}{ 21575: {}, 21744: {}, 21826: {}, + 21859: {}, 21928: {}, 21996: {}, 22047: {}, 22069: {}, 22085: {}, - 22351: {}, + 22363: {}, 22724: {}, 22773: {}, 22927: {}, @@ -369,7 +364,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 23693: {}, 23752: {}, 23889: {}, - 23917: {}, 23955: {}, 23969: {}, 24158: {}, @@ -384,7 +378,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 24722: {}, 24757: {}, 24835: {}, - 24852: {}, 24921: {}, 24940: {}, 25019: {}, @@ -418,7 +411,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 27781: {}, 27800: {}, 27831: {}, - 27839: {}, 27882: {}, 27884: {}, 27895: {}, @@ -430,12 +422,14 @@ var allTopASNs = map[agd.ASN]struct{}{ 28005: {}, 28006: {}, 28036: {}, + 28094: {}, 28104: {}, 28118: {}, 28126: {}, 28403: {}, 28548: {}, 28573: {}, + 28683: {}, 28698: {}, 28787: {}, 28884: {}, @@ -448,7 +442,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 29244: {}, 29247: {}, 29256: {}, - 29310: {}, 29314: {}, 29355: {}, 29357: {}, @@ -458,37 +451,32 @@ var allTopASNs = map[agd.ASN]struct{}{ 29544: {}, 29555: {}, 29571: {}, - 29580: {}, 29614: {}, 29695: {}, 29975: {}, 30689: {}, 30722: {}, + 30844: {}, 30873: {}, 30969: {}, 30985: {}, 30986: {}, - 30987: {}, 30990: {}, 30992: {}, 30999: {}, 31012: {}, 31037: {}, 31042: {}, - 31122: {}, 31133: {}, 31163: {}, - 31204: {}, 31205: {}, 31213: {}, 31224: {}, 31252: {}, 31452: {}, - 31549: {}, 31615: {}, 31721: {}, 32020: {}, - 33363: {}, 33392: {}, 33567: {}, 33576: {}, @@ -513,15 +501,14 @@ var allTopASNs = map[agd.ASN]struct{}{ 35432: {}, 35444: {}, 35805: {}, - 35807: {}, 35819: {}, 35900: {}, 36290: {}, 36549: {}, + 36866: {}, 36873: {}, 36884: {}, 36890: {}, - 36902: {}, 36903: {}, 36907: {}, 36908: {}, @@ -538,7 +525,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 36974: {}, 36988: {}, 36992: {}, - 36994: {}, 36996: {}, 36998: {}, 36999: {}, @@ -572,7 +558,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 37284: {}, 37287: {}, 37294: {}, - 37303: {}, 37309: {}, 37323: {}, 37336: {}, @@ -582,16 +567,15 @@ var allTopASNs = map[agd.ASN]struct{}{ 37371: {}, 37376: {}, 37385: {}, + 37406: {}, 37410: {}, 37424: {}, 37440: {}, 37447: {}, - 37451: {}, 37453: {}, 37457: {}, 37460: {}, 37461: {}, - 37463: {}, 37473: {}, 37492: {}, 37508: {}, @@ -600,6 +584,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 37526: {}, 37529: {}, 37531: {}, + 37541: {}, 37550: {}, 37552: {}, 37559: {}, @@ -608,17 +593,18 @@ var allTopASNs = map[agd.ASN]struct{}{ 37577: {}, 37594: {}, 37611: {}, - 37612: {}, + 37614: {}, 37616: {}, 37645: {}, - 37649: {}, 37671: {}, + 37678: {}, 37693: {}, 37705: {}, 38008: {}, 38009: {}, 38077: {}, 38195: {}, + 38198: {}, 38201: {}, 38235: {}, 38442: {}, @@ -626,17 +612,15 @@ var allTopASNs = map[agd.ASN]struct{}{ 38565: {}, 38623: {}, 38742: {}, - 38800: {}, 38819: {}, - 38875: {}, 38901: {}, 38999: {}, 39010: {}, 39232: {}, + 39402: {}, 39603: {}, 39611: {}, 39642: {}, - 39737: {}, 39891: {}, 40945: {}, 41164: {}, @@ -644,6 +628,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 41329: {}, 41330: {}, 41557: {}, + 41697: {}, 41738: {}, 41750: {}, 41897: {}, @@ -653,21 +638,18 @@ var allTopASNs = map[agd.ASN]struct{}{ 42298: {}, 42313: {}, 42437: {}, + 42532: {}, 42560: {}, 42610: {}, 42772: {}, 42779: {}, - 42837: {}, - 42841: {}, 42863: {}, - 42960: {}, 42961: {}, 43019: {}, 43197: {}, + 43242: {}, 43447: {}, - 43513: {}, 43557: {}, - 43571: {}, 43612: {}, 43733: {}, 43766: {}, @@ -679,12 +661,14 @@ var allTopASNs = map[agd.ASN]struct{}{ 44143: {}, 44244: {}, 44395: {}, + 44477: {}, 44489: {}, 44558: {}, 44575: {}, + 44735: {}, 44869: {}, + 44925: {}, 45143: {}, - 45168: {}, 45177: {}, 45178: {}, 45245: {}, @@ -693,6 +677,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 45356: {}, 45498: {}, 45609: {}, + 45629: {}, 45650: {}, 45669: {}, 45727: {}, @@ -708,20 +693,21 @@ var allTopASNs = map[agd.ASN]struct{}{ 47331: {}, 47377: {}, 47394: {}, - 47524: {}, 47589: {}, 47883: {}, - 47956: {}, 47975: {}, 48092: {}, 48190: {}, 48206: {}, 48252: {}, 48503: {}, + 48675: {}, 48728: {}, 48832: {}, + 48847: {}, 48887: {}, 49273: {}, + 49628: {}, 49800: {}, 49902: {}, 49981: {}, @@ -729,20 +715,19 @@ var allTopASNs = map[agd.ASN]struct{}{ 50223: {}, 50251: {}, 50266: {}, + 50360: {}, 50616: {}, - 50810: {}, + 50710: {}, 50973: {}, 51207: {}, 51375: {}, 51407: {}, 51495: {}, 51765: {}, - 51852: {}, 51896: {}, 52228: {}, 52233: {}, 52253: {}, - 52257: {}, 52260: {}, 52262: {}, 52263: {}, @@ -758,31 +743,33 @@ var allTopASNs = map[agd.ASN]struct{}{ 55850: {}, 55943: {}, 55944: {}, - 56017: {}, 56055: {}, 56089: {}, 56167: {}, 56300: {}, - 56369: {}, - 56653: {}, 56665: {}, 56696: {}, 56902: {}, + 57218: {}, 57269: {}, 57293: {}, 57388: {}, 57513: {}, - 57704: {}, 58224: {}, 58460: {}, 58731: {}, 58952: {}, 59257: {}, + 59974: {}, 59989: {}, 60068: {}, 60258: {}, + 60471: {}, + 60781: {}, 61143: {}, + 61272: {}, 61461: {}, + 63473: {}, 63949: {}, 64466: {}, 131178: {}, @@ -796,7 +783,6 @@ var allTopASNs = map[agd.ASN]struct{}{ 132167: {}, 132199: {}, 132471: {}, - 132486: {}, 132618: {}, 133385: {}, 133481: {}, @@ -804,9 +790,10 @@ var allTopASNs = map[agd.ASN]struct{}{ 133606: {}, 133612: {}, 134783: {}, + 134840: {}, 135409: {}, 136255: {}, - 136950: {}, + 136557: {}, 137412: {}, 137824: {}, 138179: {}, @@ -815,27 +802,31 @@ var allTopASNs = map[agd.ASN]struct{}{ 139898: {}, 139922: {}, 140504: {}, - 196838: {}, 197207: {}, 197830: {}, 198279: {}, + 198288: {}, 198605: {}, 199140: {}, 199276: {}, 199731: {}, 200134: {}, + 201019: {}, 201167: {}, 201767: {}, + 201884: {}, 201986: {}, 202087: {}, 202254: {}, - 202422: {}, - 202448: {}, + 203020: {}, 203214: {}, 203953: {}, 203995: {}, + 204106: {}, 204170: {}, - 204279: {}, + 204317: {}, + 204342: {}, + 204649: {}, 205110: {}, 205368: {}, 205714: {}, @@ -843,20 +834,18 @@ var allTopASNs = map[agd.ASN]struct{}{ 206067: {}, 206206: {}, 206262: {}, - 207369: {}, - 207569: {}, 207651: {}, 207810: {}, - 209424: {}, - 210003: {}, + 207876: {}, + 209442: {}, + 209854: {}, 210315: {}, 210542: {}, 211144: {}, 212238: {}, - 212370: {}, 213155: {}, - 213371: {}, 262145: {}, + 262181: {}, 262186: {}, 262197: {}, 262202: {}, @@ -864,6 +853,7 @@ var allTopASNs = map[agd.ASN]struct{}{ 262239: {}, 262589: {}, 263238: {}, + 263703: {}, 263725: {}, 263783: {}, 263824: {}, @@ -872,19 +862,19 @@ var allTopASNs = map[agd.ASN]struct{}{ 264663: {}, 264668: {}, 264731: {}, - 266673: {}, - 269729: {}, 271773: {}, 327697: {}, + 327707: {}, 327712: {}, - 327725: {}, 327738: {}, 327756: {}, 327765: {}, 327769: {}, + 327776: {}, 327799: {}, 327802: {}, 327885: {}, + 327903: {}, 327931: {}, 327934: {}, 328061: {}, @@ -903,9 +893,11 @@ var allTopASNs = map[agd.ASN]struct{}{ 328469: {}, 328488: {}, 328539: {}, + 328605: {}, + 328708: {}, + 328727: {}, 328755: {}, 328943: {}, - 393275: {}, 394311: {}, 395561: {}, 396304: {}, @@ -914,13 +906,13 @@ var allTopASNs = map[agd.ASN]struct{}{ 399724: {}, } -// countryTopASNs is a mapping of a country to their top ASNs. -var countryTopASNs = map[agd.Country]agd.ASN{ +// DefaultCountryTopASNs is a mapping of a country to their top ASNs. +var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryAD: 6752, agd.CountryAE: 5384, agd.CountryAF: 132471, agd.CountryAG: 11594, - agd.CountryAI: 2740, + agd.CountryAI: 11139, agd.CountryAL: 21183, agd.CountryAM: 12297, agd.CountryAO: 37119, @@ -930,7 +922,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryAU: 1221, agd.CountryAW: 11816, agd.CountryAX: 3238, - agd.CountryAZ: 28787, + agd.CountryAZ: 34170, agd.CountryBA: 9146, agd.CountryBB: 14813, agd.CountryBD: 24389, @@ -940,17 +932,17 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryBH: 5416, agd.CountryBI: 327799, agd.CountryBJ: 37424, - agd.CountryBL: 3215, - agd.CountryBM: 32020, + agd.CountryBL: 14593, + agd.CountryBM: 3855, agd.CountryBN: 10094, agd.CountryBO: 6568, agd.CountryBQ: 27745, - agd.CountryBR: 28573, + agd.CountryBR: 26599, agd.CountryBS: 15146, agd.CountryBT: 18024, agd.CountryBW: 14988, - agd.CountryBY: 6697, - agd.CountryBZ: 212370, + agd.CountryBY: 25106, + agd.CountryBZ: 10269, agd.CountryCA: 812, agd.CountryCD: 37020, agd.CountryCF: 37460, @@ -958,15 +950,15 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryCH: 3303, agd.CountryCI: 29571, agd.CountryCK: 10131, - agd.CountryCL: 7418, + agd.CountryCL: 27651, agd.CountryCM: 30992, agd.CountryCN: 4134, - agd.CountryCO: 10620, + agd.CountryCO: 27831, agd.CountryCR: 11830, agd.CountryCU: 27725, agd.CountryCV: 37517, agd.CountryCW: 52233, - agd.CountryCY: 202448, + agd.CountryCY: 6866, agd.CountryCZ: 5610, agd.CountryDE: 3320, agd.CountryDJ: 30990, @@ -975,20 +967,20 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryDO: 6400, agd.CountryDZ: 36947, agd.CountryEC: 27947, - agd.CountryEE: 3249, + agd.CountryEE: 44477, agd.CountryEG: 8452, - agd.CountryEH: 6713, + agd.CountryEH: 36947, agd.CountryER: 24757, - agd.CountryES: 12479, + agd.CountryES: 3352, agd.CountryET: 24757, agd.CountryFI: 51765, agd.CountryFJ: 38442, - agd.CountryFK: 198605, + agd.CountryFK: 204649, agd.CountryFM: 139759, agd.CountryFO: 15389, agd.CountryFR: 3215, agd.CountryGA: 36924, - agd.CountryGB: 60068, + agd.CountryGB: 2856, agd.CountryGD: 46650, agd.CountryGE: 16010, agd.CountryGF: 3215, @@ -996,29 +988,29 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryGH: 30986, agd.CountryGI: 8301, agd.CountryGL: 8818, - agd.CountryGM: 25250, + agd.CountryGM: 37309, agd.CountryGN: 37461, agd.CountryGP: 3215, - agd.CountryGQ: 37173, + agd.CountryGQ: 37529, agd.CountryGR: 6799, agd.CountryGT: 14754, - agd.CountryGU: 3605, + agd.CountryGU: 9246, agd.CountryGW: 37559, agd.CountryGY: 19863, agd.CountryHK: 4760, - agd.CountryHN: 52262, + agd.CountryHN: 14754, agd.CountryHR: 5391, agd.CountryHT: 27653, agd.CountryHU: 5483, agd.CountryID: 7713, - agd.CountryIE: 6830, + agd.CountryIE: 16509, agd.CountryIL: 1680, agd.CountryIM: 13122, agd.CountryIN: 55836, agd.CountryIO: 17458, agd.CountryIQ: 203214, - agd.CountryIR: 58224, - agd.CountryIS: 43571, + agd.CountryIR: 44244, + agd.CountryIS: 44735, agd.CountryIT: 1267, agd.CountryJE: 8680, agd.CountryJM: 30689, @@ -1027,7 +1019,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryKE: 33771, agd.CountryKG: 50223, agd.CountryKH: 38623, - agd.CountryKI: 134783, + agd.CountryKI: 135409, agd.CountryKM: 36939, agd.CountryKN: 11139, agd.CountryKR: 4766, @@ -1039,10 +1031,10 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryLC: 15344, agd.CountryLI: 20634, agd.CountryLK: 18001, - agd.CountryLR: 37410, + agd.CountryLR: 37094, agd.CountryLS: 33567, agd.CountryLT: 8764, - agd.CountryLU: 6661, + agd.CountryLU: 53667, agd.CountryLV: 24921, agd.CountryLY: 21003, agd.CountryMA: 36903, @@ -1071,22 +1063,22 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryNA: 36996, agd.CountryNC: 18200, agd.CountryNE: 37531, - agd.CountryNF: 45168, agd.CountryNG: 29465, agd.CountryNI: 14754, agd.CountryNL: 1136, agd.CountryNO: 2119, agd.CountryNP: 17501, - agd.CountryNR: 140504, + agd.CountryNR: 198605, + agd.CountryNU: 198605, agd.CountryNZ: 9790, agd.CountryOM: 28885, - agd.CountryPA: 18809, + agd.CountryPA: 11556, agd.CountryPE: 12252, agd.CountryPF: 9471, agd.CountryPG: 139898, agd.CountryPH: 9299, agd.CountryPK: 45669, - agd.CountryPL: 5617, + agd.CountryPL: 43447, agd.CountryPM: 3695, agd.CountryPR: 14638, agd.CountryPS: 12975, @@ -1094,7 +1086,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryPW: 17893, agd.CountryPY: 23201, agd.CountryQA: 42298, - agd.CountryRE: 3215, + agd.CountryRE: 37002, agd.CountryRO: 8708, agd.CountryRS: 8400, agd.CountryRU: 8359, @@ -1105,7 +1097,9 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountrySD: 15706, agd.CountrySE: 60068, agd.CountrySG: 4773, - agd.CountrySI: 3212, + agd.CountrySH: 33763, + agd.CountrySI: 5603, + agd.CountrySJ: 29695, agd.CountrySK: 6855, agd.CountrySL: 37164, agd.CountrySM: 15433, @@ -1123,33 +1117,31 @@ var countryTopASNs = map[agd.Country]agd.ASN{ agd.CountryTG: 36924, agd.CountryTH: 131445, agd.CountryTJ: 43197, - agd.CountryTK: 4648, agd.CountryTL: 58731, agd.CountryTM: 51495, agd.CountryTN: 37705, agd.CountryTO: 38201, agd.CountryTR: 47331, agd.CountryTT: 27800, - agd.CountryTV: 23917, + agd.CountryTV: 198605, agd.CountryTW: 3462, agd.CountryTZ: 36908, agd.CountryUA: 15895, agd.CountryUG: 37075, - agd.CountryUS: 7922, + agd.CountryUS: 21928, agd.CountryUY: 6057, agd.CountryUZ: 8193, - agd.CountryVA: 8978, agd.CountryVC: 46408, agd.CountryVE: 8048, - agd.CountryVG: 14813, + agd.CountryVG: 396357, agd.CountryVI: 14434, agd.CountryVN: 7552, agd.CountryVU: 9249, agd.CountryWF: 45879, - agd.CountryWS: 38800, + agd.CountryWS: 17993, agd.CountryXK: 21246, agd.CountryYE: 30873, - agd.CountryYT: 3215, + agd.CountryYT: 49902, agd.CountryZA: 37457, agd.CountryZM: 37287, agd.CountryZW: 37204, diff --git a/internal/geoip/asntops_generate.go b/internal/geoip/asntops_generate.go index 9a32879..32d2c29 100644 --- a/internal/geoip/asntops_generate.go +++ b/internal/geoip/asntops_generate.go @@ -33,27 +33,27 @@ func main() { check(err) defer log.OnCloserError(out, log.ERROR) - countryTopASNs := map[agd.Country][]agd.ASN{} - err = json.NewDecoder(resp.Body).Decode(&countryTopASNs) + defaultCountryTopASNs := map[agd.Country][]agd.ASN{} + err = json.NewDecoder(resp.Body).Decode(&defaultCountryTopASNs) check(err) - allTopASNs := map[agd.ASN]struct{}{} - for _, asns := range countryTopASNs { + defaultTopASNs := map[agd.ASN]struct{}{} + for _, asns := range defaultCountryTopASNs { for _, asn := range asns { if asn != 0 { - allTopASNs[asn] = struct{}{} + defaultTopASNs[asn] = struct{}{} } } } type templateData struct { - AllTopASNs map[agd.ASN]struct{} - CountryTopASNs map[agd.Country][]agd.ASN + DefaultTopASNs map[agd.ASN]struct{} + DefaultCountryTopASNs map[agd.Country][]agd.ASN } tmplData := &templateData{ - AllTopASNs: allTopASNs, - CountryTopASNs: countryTopASNs, + DefaultTopASNs: defaultTopASNs, + DefaultCountryTopASNs: defaultCountryTopASNs, } tmpl, err := template.New("main").Parse(tmplStr) @@ -74,16 +74,16 @@ package geoip import "github.com/AdguardTeam/AdGuardDNS/internal/agd" -// allTopASNs contains all specially handled ASNs. -var allTopASNs = map[agd.ASN]struct{}{ -{{- range $asn, $_ := .AllTopASNs }} +// DefaultTopASNs contains all specially handled ASNs. +var DefaultTopASNs = map[agd.ASN]struct{}{ +{{- range $asn, $_ := .DefaultTopASNs }} {{ printf "%-7s {}," ( printf "%d:" $asn ) }} {{- end }} } -// countryTopASNs is a mapping of a country to their top ASNs. -var countryTopASNs = map[agd.Country]agd.ASN{ -{{- range $ctry, $ASNs := .CountryTopASNs }} +// DefaultCountryTopASNs is a mapping of a country to their top ASNs. +var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ +{{- range $ctry, $ASNs := .DefaultCountryTopASNs }} {{- if gt (len $ASNs) 0 }} agd.Country{{ $ctry }}: {{ index $ASNs 0 }}, {{- else }} diff --git a/internal/geoip/file.go b/internal/geoip/file.go index 5d29b81..7eb74c5 100644 --- a/internal/geoip/file.go +++ b/internal/geoip/file.go @@ -20,6 +20,14 @@ import ( // FileConfig is the file-based GeoIP configuration structure. type FileConfig struct { + // AllTopASNs contains all subnets from CountryTopASNs. While scanning the + // statistics data file this list is used as a dictionary to check if the + // current ASN included in CountryTopASNs. + AllTopASNs map[agd.ASN]struct{} + + // CountryTopASNs is a mapping of a country to their top ASNs. + CountryTopASNs map[agd.Country]agd.ASN + // ASNPath is the path to the GeoIP database of ASNs. ASNPath string @@ -37,6 +45,9 @@ type FileConfig struct { // File is a file implementation of [geoip.Interface]. type File struct { + allTopASNs map[agd.ASN]struct{} + countryTopASNs map[agd.Country]agd.ASN + // mu protects asn, country, country subnet maps, and caches against // simultaneous access during a refresh. mu *sync.RWMutex @@ -80,6 +91,9 @@ func NewFile(c *FileConfig) (f *File, err error) { ipCacheSize: c.IPCacheSize, hostCacheSize: c.HostCacheSize, + + allTopASNs: c.AllTopASNs, + countryTopASNs: c.CountryTopASNs, } // TODO(a.garipov): Consider adding software module ID into the contexts and @@ -138,8 +152,6 @@ func (f *File) SubnetByLocation( asn agd.ASN, fam netutil.AddrFamily, ) (n netip.Prefix, err error) { - // TODO(a.garipov): Thoroughly cover with tests. - var topASNSubnets asnSubnets var ctrySubnets countrySubnets @@ -160,7 +172,7 @@ func (f *File) SubnetByLocation( var ok bool if n, ok = topASNSubnets[asn]; ok { return n, nil - } else if asn, ok = countryTopASNs[c]; ok { + } else if asn, ok = f.countryTopASNs[c]; ok { // Technically, if there is an entry in countryTopASNs then that entry // also always exists in topASNSubnets, but let's be defensive about it. if n, ok = topASNSubnets[asn]; ok { @@ -350,7 +362,7 @@ func (f *File) Refresh(_ context.Context) (err error) { defer wg.Done() var ipv4, ipv6 asnSubnets - ipv4, ipv6, asnErr = resetTopASNSubnets(asn) + ipv4, ipv6, asnErr = f.resetTopASNSubnets(asn) if asnErr != nil { metrics.GeoIPUpdateStatus.WithLabelValues(f.asnPath).Set(0) diff --git a/internal/geoip/file_test.go b/internal/geoip/file_test.go index 1715546..ee8b234 100644 --- a/internal/geoip/file_test.go +++ b/internal/geoip/file_test.go @@ -13,10 +13,12 @@ import ( func TestFile_Data_cityDB(t *testing.T) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: cityPath, - HostCacheSize: 0, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: cityPath, + HostCacheSize: 0, + IPCacheSize: 1, + AllTopASNs: allTopASNs, + CountryTopASNs: countryTopASNs, } g, err := geoip.NewFile(conf) @@ -37,10 +39,12 @@ func TestFile_Data_cityDB(t *testing.T) { func TestFile_Data_countryDB(t *testing.T) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: countryPath, - HostCacheSize: 0, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: countryPath, + HostCacheSize: 0, + IPCacheSize: 1, + AllTopASNs: allTopASNs, + CountryTopASNs: countryTopASNs, } g, err := geoip.NewFile(conf) @@ -61,10 +65,12 @@ func TestFile_Data_countryDB(t *testing.T) { func TestFile_Data_hostCache(t *testing.T) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: cityPath, - HostCacheSize: 1, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: cityPath, + HostCacheSize: 1, + IPCacheSize: 1, + AllTopASNs: allTopASNs, + CountryTopASNs: countryTopASNs, } g, err := geoip.NewFile(conf) @@ -88,25 +94,63 @@ func TestFile_Data_hostCache(t *testing.T) { func TestFile_SubnetByLocation(t *testing.T) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: cityPath, - HostCacheSize: 0, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: cityPath, + HostCacheSize: 0, + IPCacheSize: 1, + AllTopASNs: allTopASNs, + CountryTopASNs: countryTopASNs, } - g, err := geoip.NewFile(conf) - require.NoError(t, err) + g, cErr := geoip.NewFile(conf) + require.NoError(t, cErr) - // TODO(a.garipov): Actually test ASN queries once we have the data. - gotIPv4Subnet, err := g.SubnetByLocation(testIPv4SubnetCtry, 0, netutil.AddrFamilyIPv4) - require.NoError(t, err) + testCases := []struct { + name string + country agd.Country + want netip.Prefix + asn agd.ASN + fam netutil.AddrFamily + }{{ + name: "by_asn", + country: testIPv4SubnetCtry, + asn: countryTopASNs[testIPv4SubnetCtry], + fam: netutil.AddrFamilyIPv4, + want: testIPv4CountrySubnet, + }, { + name: "from_top_countries_v4", + country: testIPv4SubnetCtry, + asn: 0, + fam: netutil.AddrFamilyIPv4, + want: testIPv4CountrySubnet, + }, { + name: "from_top_countries_v6", + country: testIPv6SubnetCtry, + asn: 0, + fam: netutil.AddrFamilyIPv6, + want: testIPv6CountrySubnet, + }, { + name: "from_countries_dict", + country: agd.CountryBT, + asn: 0, + fam: netutil.AddrFamilyIPv4, + want: netip.MustParsePrefix("67.43.156.0/24"), + }, { + name: "not_found", + country: agd.CountryFR, + asn: 0, + fam: netutil.AddrFamilyIPv4, + want: netutil.ZeroPrefix(netutil.AddrFamilyIPv4), + }} - assert.Equal(t, testIPv4CountrySubnet, gotIPv4Subnet) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctrySubnet, err := g.SubnetByLocation(tc.country, tc.asn, tc.fam) + require.NoError(t, err) - gotIPv6Subnet, err := g.SubnetByLocation(testIPv6SubnetCtry, 0, netutil.AddrFamilyIPv6) - require.NoError(t, err) - - assert.Equal(t, testIPv6CountrySubnet, gotIPv6Subnet) + assert.Equal(t, tc.want, ctrySubnet) + }) + } } var locSink *agd.Location @@ -115,10 +159,12 @@ var errSink error func BenchmarkFile_Data(b *testing.B) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: cityPath, - HostCacheSize: 0, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: cityPath, + HostCacheSize: 0, + IPCacheSize: 1, + AllTopASNs: geoip.DefaultTopASNs, + CountryTopASNs: geoip.DefaultCountryTopASNs, } g, err := geoip.NewFile(conf) @@ -166,10 +212,12 @@ var fileSink *geoip.File func BenchmarkNewFile(b *testing.B) { conf := &geoip.FileConfig{ - ASNPath: asnPath, - CountryPath: cityPath, - HostCacheSize: 0, - IPCacheSize: 1, + ASNPath: asnPath, + CountryPath: cityPath, + HostCacheSize: 0, + IPCacheSize: 1, + AllTopASNs: geoip.DefaultTopASNs, + CountryTopASNs: geoip.DefaultCountryTopASNs, } b.ReportAllocs() diff --git a/internal/geoip/filescanner.go b/internal/geoip/filescanner.go index 1c884fe..5824d60 100644 --- a/internal/geoip/filescanner.go +++ b/internal/geoip/filescanner.go @@ -45,7 +45,7 @@ const ( // bits set to zero. // // TODO(a.garipov): Consider merging with resetCountrySubnets. -func resetTopASNSubnets(r *maxminddb.Reader) (ipv4, ipv6 asnSubnets, err error) { +func (f *File) resetTopASNSubnets(r *maxminddb.Reader) (ipv4, ipv6 asnSubnets, err error) { ipv4, ipv6 = asnSubnets{}, asnSubnets{} nets := r.Networks(maxminddb.SkipAliasedNetworks) @@ -56,7 +56,7 @@ func resetTopASNSubnets(r *maxminddb.Reader) (ipv4, ipv6 asnSubnets, err error) if err != nil { // Don't wrap the error, because it's informative enough as is. return nil, nil, err - } else if _, ok := allTopASNs[asn]; !ok { + } else if _, ok := f.allTopASNs[asn]; !ok { continue } @@ -154,6 +154,10 @@ func applyTopASNSubnetHacks(subnets asnSubnets, fam netutil.AddrFamily) { // indeed not available in their network unless this network is used in // the ECS option. subnets[25159] = netip.MustParsePrefix("178.176.72.0/24") + // We need special handling for the Bangladesh ISP "Dot internet" as + // otherwise it receives bad IP addresses for Tiktok domains that aren't + // working for its customers. See AGDNS-1593 for details. + subnets[134732] = netip.MustParsePrefix("37.111.192.0/24") desiredLength = desiredIPv4SubnetLength case netutil.AddrFamilyIPv6: // TODO(a.garipov): Add more if we find them. diff --git a/internal/geoip/geoip_test.go b/internal/geoip/geoip_test.go index 2768867..88740b4 100644 --- a/internal/geoip/geoip_test.go +++ b/internal/geoip/geoip_test.go @@ -25,7 +25,21 @@ const ( testOtherHost = "other.example.com" ) -// Test data. See [ASN], [city], and [country] testing datum. +// Test ASN data. +var ( + countryTopASNs = map[agd.Country]agd.ASN{ + agd.CountryAU: 1221, + agd.CountryJP: 2516, + agd.CountryUS: 7922, + } + allTopASNs = map[agd.ASN]struct{}{ + countryTopASNs[agd.CountryAU]: {}, + countryTopASNs[agd.CountryJP]: {}, + countryTopASNs[agd.CountryUS]: {}, + } +) + +// Test queries data. See [ASN], [city], and [country] testing datum. // // [ASN]: https://github.com/maxmind/MaxMind-DB/blob/2bf1713b3b5adcb022cf4bb77eb0689beaadcfef/source-data/GeoLite2-ASN-Test.json // [city]: https://github.com/maxmind/MaxMind-DB/blob/2bf1713b3b5adcb022cf4bb77eb0689beaadcfef/source-data/GeoIP2-City-Test.json @@ -55,11 +69,5 @@ var testIPWithCountry = netip.MustParseAddr("2001:218::") // Subnets for CountrySubnet tests. var ( testIPv4CountrySubnet = netip.MustParsePrefix("76.128.0.0/24") - - // TODO(a.garipov): Either find a better subnet and country that don't - // trigger the ASN optimizations or just remove this one completely. - // - // testIPv6CountrySubnet = netip.MustParsePrefix("2001:218::/32") - testIPv6CountrySubnet = netip.MustParsePrefix("240f::/56") ) diff --git a/internal/metrics/dnsdb.go b/internal/metrics/dnsdb.go index 1544c1e..1d7ad39 100644 --- a/internal/metrics/dnsdb.go +++ b/internal/metrics/dnsdb.go @@ -6,21 +6,15 @@ import ( ) var ( - // DNSDBSize is a gauge with the total count of records in the local DNSDB. - DNSDBSize = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "db_size", - Namespace: namespace, - Subsystem: subsystemDNSDB, - Help: "Count of records in the local DNSDB.", - }) // DNSDBBufferSize is a gauge with the total count of records in the // in-memory temporary buffer. DNSDBBufferSize = promauto.NewGauge(prometheus.GaugeOpts{ Name: "buffer_size", Namespace: namespace, Subsystem: subsystemDNSDB, - Help: "Count of records in the temporary buffer.", + Help: "Count of records in the in-memory buffer.", }) + // DNSDBRotateTime is a gauge with the time when the DNSDB was rotated. DNSDBRotateTime = promauto.NewGauge(prometheus.GaugeOpts{ Name: "rotate_time", @@ -28,11 +22,13 @@ var ( Subsystem: subsystemDNSDB, Help: "Last time when the database was rotated.", }) - // DNSDBSaveDuration is a histogram with the time elapsed on saving DNSDB. + + // DNSDBSaveDuration is a histogram with the time elapsed on rotating the + // buffer for sending over HTTP. DNSDBSaveDuration = promauto.NewHistogram(prometheus.HistogramOpts{ Name: "save_duration", Namespace: namespace, Subsystem: subsystemDNSDB, - Help: "Time elapsed on saving buffer to the database.", + Help: "Time elapsed on rotating the buffer for sending over HTTP.", }) ) diff --git a/internal/metrics/filter.go b/internal/metrics/filter.go index be3071b..47f9c52 100644 --- a/internal/metrics/filter.go +++ b/internal/metrics/filter.go @@ -15,8 +15,8 @@ var ( Help: "The number of rules loaded by filters.", }, []string{"filter"}) - // FilterUpdatedTime is a gauge with the last time when the filter was - // last time updated. + // FilterUpdatedTime is a gauge with the last time when the filter was last + // time updated. FilterUpdatedTime = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "updated_time", Subsystem: subsystemFilter, @@ -53,8 +53,8 @@ var ( ) var ( - // hashPrefixFilterCacheSize is a gauge with the total count of records in the - // HashStorage cache. "filter" is either "yes" (the metric is for hostnames that support + // hashPrefixFilterCacheSize is a gauge with the total count of records in + // the HashStorage cache. hashPrefixFilterCacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "hash_prefix_cache_size", Subsystem: subsystemFilter, @@ -62,18 +62,25 @@ var ( Help: "The total number of items in the HashPrefixFilter cache.", }, []string{"filter"}) - // HashPrefixFilterSafeBrowsingCacheSize is the gauge with the total number of items in - // the cache for domain names for SafeBrowsing filter. + // HashPrefixFilterSafeBrowsingCacheSize is the gauge with the total number + // of items in the cache for domain names for safe browsing filter. HashPrefixFilterSafeBrowsingCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ "filter": "safe_browsing", }) - // HashPrefixFilterAdultBlockingCacheSize is the gauge with the total number of items in - // the cache for domain names for AdultBlocking filter. + // HashPrefixFilterAdultBlockingCacheSize is the gauge with the total number + // of items in the cache for domain names for adult blocking filter. HashPrefixFilterAdultBlockingCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ "filter": "adult_blocking", }) + // HashPrefixFilterNewRegDomainsCacheSize is the gauge with the total number + // of items in the cache for domain names for safe browsing newly registered + // domains filter. + HashPrefixFilterNewRegDomainsCacheSize = hashPrefixFilterCacheSize.With(prometheus.Labels{ + "filter": "newly_registered_domains", + }) + // hashPrefixFilterCacheLookups is a counter with the total number of host // cache lookups. "hit" is either "1" (item found) or "0". (item not found). hashPrefixFilterCacheLookups = promauto.NewCounterVec(prometheus.CounterOpts{ @@ -85,30 +92,44 @@ var ( }, []string{"hit", "filter"}) // HashPrefixFilterCacheSafeBrowsingHits is a counter with the total number - // of SafeBrowsing filter cache hits. + // of safe browsing filter cache hits. HashPrefixFilterCacheSafeBrowsingHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ "hit": "1", "filter": "safe_browsing", }) // HashPrefixFilterCacheSafeBrowsingMisses is a counter with the total number - // of SafeBrowsing filter cache misses. + // of safe browsing filter cache misses. HashPrefixFilterCacheSafeBrowsingMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ "hit": "0", "filter": "safe_browsing", }) // HashPrefixFilterCacheAdultBlockingHits is a counter with the total number - // of AdultBlocking filter cache hits. + // of adult blocking filter cache hits. HashPrefixFilterCacheAdultBlockingHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ "hit": "1", "filter": "adult_blocking", }) // HashPrefixFilterCacheAdultBlockingMisses is a counter with the total number - // of AdultBlocking filter cache misses. + // of adult blocking filter cache misses. HashPrefixFilterCacheAdultBlockingMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ "hit": "0", "filter": "adult_blocking", }) + + // HashPrefixFilterCacheNewRegDomainsHits is a counter with the total number + // of newly registered domains filter cache hits. + HashPrefixFilterCacheNewRegDomainsHits = hashPrefixFilterCacheLookups.With(prometheus.Labels{ + "hit": "1", + "filter": "newly_registered_domains", + }) + + // HashPrefixFilterCacheNewRegDomainsMisses is a counter with the total + // number of newly registered domains filter cache misses. + HashPrefixFilterCacheNewRegDomainsMisses = hashPrefixFilterCacheLookups.With(prometheus.Labels{ + "hit": "0", + "filter": "newly_registered_domains", + }) ) diff --git a/internal/metrics/research.go b/internal/metrics/research.go index 5c4b34e..9c83949 100644 --- a/internal/metrics/research.go +++ b/internal/metrics/research.go @@ -2,6 +2,8 @@ package metrics import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/common/model" @@ -45,12 +47,23 @@ var ResearchBlockedRequestsPerSubdivTotal = promauto.NewCounterVec(prometheus.Co `subdivision from anonymous users.`, }, []string{"filter", "country", "subdivision"}) -// ReportResearchMetrics reports metrics to prometheus that we may need to -// conduct researches. -func ReportResearchMetrics( +// ResearchResponseECH counts the number of DNS responses with a ECH config. +var ResearchResponseECH = promauto.NewCounter(prometheus.CounterOpts{ + Name: "response_ech", + Namespace: namespace, + Subsystem: subsystemResearch, + Help: `The number of DNS responses with a ECH config.`, +}) + +// ReportResearch reports metrics to prometheus that we may need to conduct +// researches. If researchLogs is true, this method may also write additional +// INFO-level logs. +func ReportResearch( ri *agd.RequestInfo, + origResp *dns.Msg, filterID agd.FilterListID, blocked bool, + researchLogs bool, ) { filteringEnabled := ri.FilteringGroup != nil && ri.FilteringGroup.RuleListsEnabled && @@ -85,6 +98,29 @@ func ReportResearchMetrics( } reportResearchRequest(ctry, subdiv) + reportResearchECH(ri, origResp, researchLogs) +} + +// reportResearchECH checks if the response has ECH config and if it does, +// reports to metrics and writes to log. +func reportResearchECH(ri *agd.RequestInfo, origResp *dns.Msg, researchLogs bool) { + if origResp == nil || ri.QType != dns.TypeHTTPS { + return + } + + for _, rr := range origResp.Answer { + if svcb, ok := rr.(*dns.HTTPS); ok { + for _, v := range svcb.Value { + if v.Key() == dns.SVCB_ECHCONFIG { + ResearchResponseECH.Inc() + + if researchLogs { + log.Info("research: ech-enabled: %s", ri.Host) + } + } + } + } + } } // reportResearchBlocked reports on a blocked request to the research metrics. diff --git a/internal/metrics/usercount.go b/internal/metrics/usercount.go index b06cf3f..aec6488 100644 --- a/internal/metrics/usercount.go +++ b/internal/metrics/usercount.go @@ -6,6 +6,7 @@ import ( "time" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/timeutil" "github.com/axiomhq/hyperloglog" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -29,25 +30,34 @@ var dnsSvcUsersDailyCount = promauto.NewGauge(prometheus.GaugeOpts{ Help: "The approximate number of DNS users for the last 24 hours.", }) -// dayMinutes contains the number of minutes in a day for convenience. -const dayMinutes = 24 * 60 +const ( + // minutesPerHour is the number of minutes in an hour. + minutesPerHour = int(time.Hour / time.Minute) + + // hoursPerDay is the number of hours in a day. + hoursPerDay = int(timeutil.Day / time.Hour) +) // userCounter is used to save estimated counts of active users per hour and per // day. type userCounter struct { - // currentMinuteCounterMu protects currentMinute and currentMinuteCounter. - currentMinuteCounterMu *sync.Mutex + // currentMu protects currentMinute and currentMinuteCounter. + currentMu *sync.Mutex // currentMinuteCounter is a counter for the current minute of a day. currentMinuteCounter *hyperloglog.Sketch - // dayMinuteCountersMu protects dayMinuteCounters. - dayMinuteCountersMu *sync.Mutex + // countersMu protects hourMinuteCounters and dayHourCounters. + countersMu *sync.Mutex - // dayMinuteCounters contains HyperLogLog counters for each minute of the - // day. The index of the slice is the minute of the day in the [0, 1440) + // hourMinuteCounters contains HyperLogLog counters for each minute of an + // hour. The index of the slice is the minute of the hour in the [0, 60) // interval. - dayMinuteCounters []*hyperloglog.Sketch + hourMinuteCounters *[minutesPerHour]*hyperloglog.Sketch + + // dayHourCounters contains HyperLogLog counters for each hour of a day. + // The index of the slice is the hour of the day in the [0, 24) interval. + dayHourCounters *[hoursPerDay]*hyperloglog.Sketch // currentMinute is the current minute of the day in the [0, 1440) interval. currentMinute int @@ -56,10 +66,11 @@ type userCounter struct { // newUserCounter initializes and returns a *userCounter. func newUserCounter() (c *userCounter) { return &userCounter{ - currentMinuteCounterMu: &sync.Mutex{}, - currentMinuteCounter: nil, - dayMinuteCountersMu: &sync.Mutex{}, - dayMinuteCounters: make([]*hyperloglog.Sketch, dayMinutes), + currentMu: &sync.Mutex{}, + currentMinuteCounter: nil, + countersMu: &sync.Mutex{}, + hourMinuteCounters: &[minutesPerHour]*hyperloglog.Sketch{}, + dayHourCounters: &[hoursPerDay]*hyperloglog.Sketch{}, // Use -1 to trigger the initialization of currentMinuteCounter // regardless of the actual current minute of the day. currentMinute: -1, @@ -68,33 +79,37 @@ func newUserCounter() (c *userCounter) { // record updates the current minute-of-the-day counter as well as sets the // values of the hourly and daily metric counters, if necessary. now is the -// time for which to record the IP address, typically the current time. If -// syncUpdate is true, record performs the metric counter updates syncrhonously. -// syncUpdate is currently only used in tests. +// time for which to record the IP address, typically the current time. +// +// If syncUpdate is true, record performs the metric counter updates +// synchronously. It's is currently only used in tests. +// +// It currently assumes that it will be called at least once per day. func (c *userCounter) record(now time.Time, ip netip.Addr, syncUpdate bool) { - minuteOfTheDay := now.Hour()*60 + now.Minute() + hour, minute, _ := now.Clock() + minuteOfDay := hour*minutesPerHour + minute // Assume that ip is the remote IP address, which has already been unmapped // by [netutil.NetAddrToAddrPort]. b := ip.As16() - c.currentMinuteCounterMu.Lock() - defer c.currentMinuteCounterMu.Unlock() + c.currentMu.Lock() + defer c.currentMu.Unlock() - if c.currentMinute != minuteOfTheDay { + if c.currentMinute != minuteOfDay { prevMinute := c.currentMinute prevMinuteCounter := c.currentMinuteCounter - c.currentMinute = minuteOfTheDay + c.currentMinute = minuteOfDay c.currentMinuteCounter = newHyperLogLog() // If this is the first iteration and prevMinute is -1, don't update the // counters, since there are none. if prevMinute != -1 { if syncUpdate { - c.updateCounters(prevMinute, prevMinuteCounter) + c.updateCounters(prevMinute, hour, prevMinuteCounter) } else { - go c.updateCounters(prevMinute, prevMinuteCounter) + go c.updateCounters(prevMinute, hour, prevMinuteCounter) } } } @@ -102,55 +117,70 @@ func (c *userCounter) record(now time.Time, ip netip.Addr, syncUpdate bool) { c.currentMinuteCounter.Insert(b[:]) } -// updateCounters adds prevCounter to c.dayMinuteCounters and then merges the -// daily counters and updates the metrics. -func (c *userCounter) updateCounters(prevMinute int, prevCounter *hyperloglog.Sketch) { +// updateCounters adds prevCounter to counters and then merges them and updates +// the metrics. It also clears all the stale hourly counters from the previous +// day. +func (c *userCounter) updateCounters( + prevMinute int, + currentHour int, + prevMinuteCounter *hyperloglog.Sketch, +) { defer log.OnPanic("metrics.userCounter.updateCounters") - c.dayMinuteCountersMu.Lock() - defer c.dayMinuteCountersMu.Unlock() + prevMinuteOfHour := prevMinute % minutesPerHour + hourOfPrevMinute := prevMinute / minutesPerHour + + c.countersMu.Lock() + defer c.countersMu.Unlock() // Insert the previous counter into the rolling counters collection. - c.dayMinuteCounters[prevMinute] = prevCounter + c.hourMinuteCounters[prevMinuteOfHour] = prevMinuteCounter + c.updateHours(currentHour, hourOfPrevMinute, prevMinuteCounter) // Calculate the estimated numbers of hourly and daily users. - hourly, daily := c.estimate(prevMinute) + hourly, daily := c.estimate() dnsSvcUsersCount.Set(float64(hourly)) dnsSvcUsersDailyCount.Set(float64(daily)) } -// estimate uses HyperLogLog counters to estimate the hourly and daily users -// count, starting with the minute of the day m. -func (c *userCounter) estimate(m int) (hourly, daily uint64) { +// updateHours adds the prevMinuteCounter to the hourly counter for prevHour +// hour, and clears all the counters between curHour and prevHour, since those +// may contain data for the previous day. +func (c *userCounter) updateHours(curHour, prevHour int, prevMinuteCounter *hyperloglog.Sketch) { + for h := curHour; h != prevHour; h = decMod(h, hoursPerDay) { + c.dayHourCounters[h] = nil + } + + if c.dayHourCounters[prevHour] == nil { + c.dayHourCounters[prevHour] = newHyperLogLog() + } + + mustMerge(c.dayHourCounters[prevHour], prevMinuteCounter) +} + +// estimate uses HyperLogLog counters to return the number of users for the last +// hour and the last day. It doesn't include the data for current minute. It +// must not be called concurrently with [userCounter.updateCounters]. +func (c *userCounter) estimate() (hourly, daily uint64) { hourlyCounter, dailyCounter := newHyperLogLog(), newHyperLogLog() - // Go through all minutes in a day while decreasing the current minute m. - // Decreasing m, as opposed to increasing it or using i as the minute, is - // required to make summing the hourly statistics within the same loop - // easier. - for i := 0; i < dayMinutes; i++ { - minCounter := c.dayMinuteCounters[m] - m = decrMod(m, dayMinutes) - - if minCounter == nil { - continue + for _, c := range c.hourMinuteCounters { + if c != nil { + mustMerge(hourlyCounter, c) } + } - // Use [mustMerge], since the only reason an error may be returned here - // is when the two sketches do not have the same precisions. - mustMerge(dailyCounter, minCounter) - - // Only include the first 60 minutes into the hourly statistics. - if i < 60 { - mustMerge(hourlyCounter, minCounter) + for _, c := range c.dayHourCounters { + if c != nil { + mustMerge(dailyCounter, c) } } return hourlyCounter.Estimate(), dailyCounter.Estimate() } -// mustMerge panics if a.Merge(b) returns an error. +// mustMerge panics if [hyperloglog.Sketch.Merge] returns an error. func mustMerge(a, b *hyperloglog.Sketch) { err := a.Merge(b) if err != nil { @@ -158,9 +188,33 @@ func mustMerge(a, b *hyperloglog.Sketch) { } } -// decrMod decreases n by one using modulus m. That is, for n = 0 and m = 100 -// it returns 99. -func decrMod(n, m int) (res int) { +// hyperloglogConfig is a serialized [hyperLogLog.Sketch] with prescision 18 and +// sparse mode enabled. +var hyperloglogConfig = [20]byte{ + // Version. + 0: 0x1, + // Prescision. + 1: 18, + // Sparse. + 3: 0x1, +} + +// newHyperLogLog creates a new instance of hyperloglog.Sketch with prescision +// 18 and sparse mode enabled. +func newHyperLogLog() (sk *hyperloglog.Sketch) { + sk = &hyperloglog.Sketch{} + err := sk.UnmarshalBinary(hyperloglogConfig[:]) + if err != nil { + // Generally shouldn't happen. + panic(err) + } + + return sk +} + +// decMod decreases n by one using modulus m. That is, for n = 0 and m = 100 it +// returns 99. n should be in the [0, m) interval. +func decMod(n, m int) (res int) { if n == 0 { return m - 1 } @@ -168,11 +222,6 @@ func decrMod(n, m int) (res int) { return n - 1 } -// newHyperLogLog creates a new instance of hyperloglog.Sketch. -func newHyperLogLog() (sk *hyperloglog.Sketch) { - return hyperloglog.New16() -} - // defaultUserCounter is the main user statistics counter. var defaultUserCounter = newUserCounter() diff --git a/internal/metrics/usercount_internal_test.go b/internal/metrics/usercount_internal_test.go index 641a378..4642269 100644 --- a/internal/metrics/usercount_internal_test.go +++ b/internal/metrics/usercount_internal_test.go @@ -2,49 +2,246 @@ package metrics import ( "math/rand" + "net" "net/netip" "strconv" "testing" "time" + "github.com/AdguardTeam/golibs/netutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestUserCounter(t *testing.T) { +// Use a constant seed to make the test reproducible. +const randSeed = 1234 + +// randIP is a test helper that returns a pseudorandomly generated IP address. +// fam must be either [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6]. +func randIP(t testing.TB, r *rand.Rand, fam netutil.AddrFamily) (ip netip.Addr) { + t.Helper() + + var buf []byte + switch fam { + case netutil.AddrFamilyIPv4: + buf = make([]byte, net.IPv4len) + case netutil.AddrFamilyIPv6: + buf = make([]byte, net.IPv6len) + default: + t.Fatalf("unexpected address family %q", fam) + } + + n, err := r.Read(buf) + require.NoError(t, err) + require.Equal(t, len(buf), n) + + var ok bool + ip, ok = netip.AddrFromSlice(buf) + require.True(t, ok) + + return ip +} + +func TestUserCounter_Estimate(t *testing.T) { + // TODO(e.burkov): Add tests for more than 48 hours gaps, when it will be + // supported. + testCases := []struct { + name string + nows []time.Time + wantDaily uint64 + wantHourly uint64 + }{{ + name: "empty", + nows: nil, + wantDaily: 0, + wantHourly: 0, + }, { + name: "each_minute", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 3, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 4, 0, 0, time.UTC), + }, + wantDaily: 4, + wantHourly: 4, + }, { + name: "each_hour", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 2, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 3, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 4, 0, 0, 0, time.UTC), + }, + wantDaily: 4, + wantHourly: 1, + }, { + name: "each_day", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 4, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 5, 0, 0, 0, 0, time.UTC), + }, + wantDaily: 0, + wantHourly: 0, + }, { + name: "few_per_minute", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 0, 1, 0, time.UTC), + time.Date(2023, 1, 1, 0, 0, 2, 0, time.UTC), + time.Date(2023, 1, 1, 0, 0, 3, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 1, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 2, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 3, 0, time.UTC), + time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC), + }, + wantDaily: 8, + wantHourly: 8, + }, { + name: "few_per_hour", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 3, 0, 0, time.UTC), + time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 1, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 1, 2, 0, 0, time.UTC), + time.Date(2023, 1, 1, 1, 3, 0, 0, time.UTC), + time.Date(2023, 1, 1, 2, 0, 0, 0, time.UTC), + }, + wantDaily: 8, + wantHourly: 4, + }, { + name: "few_hours_gap", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC), + time.Date(2023, 1, 1, 4, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 4, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 4, 2, 0, 0, time.UTC), + }, + wantDaily: 5, + wantHourly: 3, + }, { + name: "few_per_day", + nows: []time.Time{ + time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 1, 0, 2, 0, 0, time.UTC), + time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC), + time.Date(2023, 1, 2, 0, 1, 0, 0, time.UTC), + time.Date(2023, 1, 2, 0, 2, 0, 0, time.UTC), + }, + wantDaily: 5, + wantHourly: 3, + }, { + name: "day_and_hour_gap", + nows: []time.Time{ + time.Date(2023, 1, 1, 23, 0, 0, 0, time.UTC), + time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + }, + wantDaily: 1, + wantHourly: 1, + }, { + name: "day_and_minute_gap", + nows: []time.Time{ + time.Date(2023, 1, 1, 23, 59, 0, 0, time.UTC), + time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC), + }, + wantDaily: 1, + wantHourly: 1, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := rand.New(rand.NewSource(randSeed)) + c := newUserCounter() + + for _, now := range tc.nows { + c.record(now, randIP(t, r, netutil.AddrFamilyIPv6), true) + } + + hourly, daily := c.estimate() + assert.Equal(t, tc.wantHourly, hourly) + assert.Equal(t, tc.wantDaily, daily) + }) + } +} + +func TestUserCounter_simple(t *testing.T) { const ipsPerMinute = 2 - // Use a constant seed to make the test reproducible. - src := rand.NewSource(1234) + src := rand.NewSource(randSeed) r := rand.New(src) c := newUserCounter() now := time.Unix(0, 0).UTC() - for h := 0; h < 24; h++ { - t.Run(strconv.Itoa(h), func(t *testing.T) { - for m := 0; m < 60; m++ { + for d, h := now.Day(), now.Hour(); now.Day() == d; h = now.Hour() { + t.Run(strconv.Itoa(now.Hour()), func(t *testing.T) { + for ; now.Hour() == h; now = now.Add(1 * time.Minute) { for i := 0; i < ipsPerMinute; i++ { - c.record(now, randIP(r), true) + c.record(now, randIP(t, r, netutil.AddrFamilyIPv4), true) } - - now = now.Add(1 * time.Minute) } - hourly, _ := c.estimate(h*60 + 59) + hourly, _ := c.estimate() assert.InEpsilon(t, uint64(ipsPerMinute*60), hourly, 0.02) }) } - _, daily := c.estimate(23*60 + 59) + _, daily := c.estimate() assert.InEpsilon(t, uint64(ipsPerMinute*24*60), daily, 0.02) } -// randIP returns a pseudorandomly generated IP address. -func randIP(r *rand.Rand) (ip netip.Addr) { - return netip.AddrFrom4([4]byte{ - byte(r.Int31n(256)), - byte(r.Int31n(256)), - byte(r.Int31n(256)), - byte(r.Int31n(256)), +// uint64Sink is a sink for uint64 values returned from benchmarks. +var uint64Sink uint64 + +func BenchmarkUserCounter_Estimate(b *testing.B) { + const n = 100 + + zeroTime := time.Unix(0, 0).UTC() + + sparseCounter := newUserCounter() + for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) { + r := rand.New(rand.NewSource(randSeed)) + for i := 0; i < n; i++ { + sparseCounter.record(now, randIP(b, r, netutil.AddrFamilyIPv6), true) + } + } + + seqCounter := newUserCounter() + for d, now := zeroTime.Day(), zeroTime; d == now.Day(); now = now.Add(time.Minute) { + addr := netip.AddrFrom16([16]byte{}) + for i := 0; i < n; i++ { + addr = addr.Next() + seqCounter.record(now, addr, true) + } + } + + b.Run("sparse", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + uint64Sink, uint64Sink = sparseCounter.estimate() + } + }) + + b.Run("sequential", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + uint64Sink, uint64Sink = seqCounter.estimate() + } }) } diff --git a/internal/profiledb/internal/filecachejson/filecachejson.go b/internal/profiledb/internal/filecachejson/filecachejson.go index 3c3110e..c647a5a 100644 --- a/internal/profiledb/internal/filecachejson/filecachejson.go +++ b/internal/profiledb/internal/filecachejson/filecachejson.go @@ -12,7 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/google/renameio" + renameio "github.com/google/renameio/v2" ) // Storage is the file-cache storage that encodes data using JSON. @@ -59,6 +59,15 @@ func (s *Storage) Load() (c *internal.FileCache, err error) { return nil, nil } + if data.Version != internal.FileCacheVersion { + return nil, fmt.Errorf( + "%w: version %d is different from %d", + internal.CacheVersionError, + data.Version, + internal.FileCacheVersion, + ) + } + return &internal.FileCache{ SyncTime: data.SyncTime, Profiles: data.Profiles, diff --git a/internal/profiledb/internal/filecachepb/filecache.pb.go b/internal/profiledb/internal/filecachepb/filecache.pb.go index 2c72dfc..bb202ea 100644 --- a/internal/profiledb/internal/filecachepb/filecache.pb.go +++ b/internal/profiledb/internal/filecachepb/filecache.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.30.0 -// protoc v4.22.3 +// protoc v4.23.4 // source: filecache.proto package filecachepb @@ -113,12 +113,14 @@ type Profile struct { CustomRules []string `protobuf:"bytes,10,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"` FilteredResponseTtl *durationpb.Duration `protobuf:"bytes,11,opt,name=filtered_response_ttl,json=filteredResponseTtl,proto3" json:"filtered_response_ttl,omitempty"` FilteringEnabled bool `protobuf:"varint,12,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"` - SafeBrowsingEnabled bool `protobuf:"varint,13,opt,name=safe_browsing_enabled,json=safeBrowsingEnabled,proto3" json:"safe_browsing_enabled,omitempty"` - RuleListsEnabled bool `protobuf:"varint,14,opt,name=rule_lists_enabled,json=ruleListsEnabled,proto3" json:"rule_lists_enabled,omitempty"` - QueryLogEnabled bool `protobuf:"varint,15,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"` - Deleted bool `protobuf:"varint,16,opt,name=deleted,proto3" json:"deleted,omitempty"` - BlockPrivateRelay bool `protobuf:"varint,17,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"` - BlockFirefoxCanary bool `protobuf:"varint,18,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"` + // Deprecated: Marked as deprecated in filecache.proto. + SafeBrowsingEnabled bool `protobuf:"varint,13,opt,name=safe_browsing_enabled,json=safeBrowsingEnabled,proto3" json:"safe_browsing_enabled,omitempty"` + RuleListsEnabled bool `protobuf:"varint,14,opt,name=rule_lists_enabled,json=ruleListsEnabled,proto3" json:"rule_lists_enabled,omitempty"` + QueryLogEnabled bool `protobuf:"varint,15,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"` + Deleted bool `protobuf:"varint,16,opt,name=deleted,proto3" json:"deleted,omitempty"` + BlockPrivateRelay bool `protobuf:"varint,17,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"` + BlockFirefoxCanary bool `protobuf:"varint,18,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"` + SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,19,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"` } func (x *Profile) Reset() { @@ -244,6 +246,7 @@ func (x *Profile) GetFilteringEnabled() bool { return false } +// Deprecated: Marked as deprecated in filecache.proto. func (x *Profile) GetSafeBrowsingEnabled() bool { if x != nil { return x.SafeBrowsingEnabled @@ -286,6 +289,13 @@ func (x *Profile) GetBlockFirefoxCanary() bool { return false } +func (x *Profile) GetSafeBrowsing() *SafeBrowsingSettings { + if x != nil { + return x.SafeBrowsing + } + return nil +} + type isProfile_BlockingMode interface { isProfile_BlockingMode() } @@ -401,6 +411,69 @@ func (x *ParentalProtectionSettings) GetYoutubeSafeSearch() bool { return false } +type SafeBrowsingSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + BlockDangerousDomains bool `protobuf:"varint,2,opt,name=block_dangerous_domains,json=blockDangerousDomains,proto3" json:"block_dangerous_domains,omitempty"` + BlockNewlyRegisteredDomains bool `protobuf:"varint,3,opt,name=block_newly_registered_domains,json=blockNewlyRegisteredDomains,proto3" json:"block_newly_registered_domains,omitempty"` +} + +func (x *SafeBrowsingSettings) Reset() { + *x = SafeBrowsingSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_filecache_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SafeBrowsingSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SafeBrowsingSettings) ProtoMessage() {} + +func (x *SafeBrowsingSettings) ProtoReflect() protoreflect.Message { + mi := &file_filecache_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SafeBrowsingSettings.ProtoReflect.Descriptor instead. +func (*SafeBrowsingSettings) Descriptor() ([]byte, []int) { + return file_filecache_proto_rawDescGZIP(), []int{3} +} + +func (x *SafeBrowsingSettings) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *SafeBrowsingSettings) GetBlockDangerousDomains() bool { + if x != nil { + return x.BlockDangerousDomains + } + return false +} + +func (x *SafeBrowsingSettings) GetBlockNewlyRegisteredDomains() bool { + if x != nil { + return x.BlockNewlyRegisteredDomains + } + return false +} + type ParentalProtectionSchedule struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -419,7 +492,7 @@ type ParentalProtectionSchedule struct { func (x *ParentalProtectionSchedule) Reset() { *x = ParentalProtectionSchedule{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[3] + mi := &file_filecache_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -432,7 +505,7 @@ func (x *ParentalProtectionSchedule) String() string { func (*ParentalProtectionSchedule) ProtoMessage() {} func (x *ParentalProtectionSchedule) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[3] + mi := &file_filecache_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -445,7 +518,7 @@ func (x *ParentalProtectionSchedule) ProtoReflect() protoreflect.Message { // Deprecated: Use ParentalProtectionSchedule.ProtoReflect.Descriptor instead. func (*ParentalProtectionSchedule) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{3} + return file_filecache_proto_rawDescGZIP(), []int{4} } func (x *ParentalProtectionSchedule) GetTimeZone() string { @@ -516,7 +589,7 @@ type DayRange struct { func (x *DayRange) Reset() { *x = DayRange{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[4] + mi := &file_filecache_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -529,7 +602,7 @@ func (x *DayRange) String() string { func (*DayRange) ProtoMessage() {} func (x *DayRange) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[4] + mi := &file_filecache_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -542,7 +615,7 @@ func (x *DayRange) ProtoReflect() protoreflect.Message { // Deprecated: Use DayRange.ProtoReflect.Descriptor instead. func (*DayRange) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{4} + return file_filecache_proto_rawDescGZIP(), []int{5} } func (x *DayRange) GetStart() uint32 { @@ -571,7 +644,7 @@ type BlockingModeCustomIP struct { func (x *BlockingModeCustomIP) Reset() { *x = BlockingModeCustomIP{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[5] + mi := &file_filecache_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -584,7 +657,7 @@ func (x *BlockingModeCustomIP) String() string { func (*BlockingModeCustomIP) ProtoMessage() {} func (x *BlockingModeCustomIP) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[5] + mi := &file_filecache_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -597,7 +670,7 @@ func (x *BlockingModeCustomIP) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockingModeCustomIP.ProtoReflect.Descriptor instead. func (*BlockingModeCustomIP) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{5} + return file_filecache_proto_rawDescGZIP(), []int{6} } func (x *BlockingModeCustomIP) GetIpv4() []byte { @@ -623,7 +696,7 @@ type BlockingModeNXDOMAIN struct { func (x *BlockingModeNXDOMAIN) Reset() { *x = BlockingModeNXDOMAIN{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[6] + mi := &file_filecache_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -636,7 +709,7 @@ func (x *BlockingModeNXDOMAIN) String() string { func (*BlockingModeNXDOMAIN) ProtoMessage() {} func (x *BlockingModeNXDOMAIN) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[6] + mi := &file_filecache_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -649,7 +722,7 @@ func (x *BlockingModeNXDOMAIN) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockingModeNXDOMAIN.ProtoReflect.Descriptor instead. func (*BlockingModeNXDOMAIN) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{6} + return file_filecache_proto_rawDescGZIP(), []int{7} } type BlockingModeNullIP struct { @@ -661,7 +734,7 @@ type BlockingModeNullIP struct { func (x *BlockingModeNullIP) Reset() { *x = BlockingModeNullIP{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[7] + mi := &file_filecache_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -674,7 +747,7 @@ func (x *BlockingModeNullIP) String() string { func (*BlockingModeNullIP) ProtoMessage() {} func (x *BlockingModeNullIP) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[7] + mi := &file_filecache_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -687,7 +760,7 @@ func (x *BlockingModeNullIP) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockingModeNullIP.ProtoReflect.Descriptor instead. func (*BlockingModeNullIP) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{7} + return file_filecache_proto_rawDescGZIP(), []int{8} } type BlockingModeREFUSED struct { @@ -699,7 +772,7 @@ type BlockingModeREFUSED struct { func (x *BlockingModeREFUSED) Reset() { *x = BlockingModeREFUSED{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[8] + mi := &file_filecache_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -712,7 +785,7 @@ func (x *BlockingModeREFUSED) String() string { func (*BlockingModeREFUSED) ProtoMessage() {} func (x *BlockingModeREFUSED) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[8] + mi := &file_filecache_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -725,7 +798,7 @@ func (x *BlockingModeREFUSED) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockingModeREFUSED.ProtoReflect.Descriptor instead. func (*BlockingModeREFUSED) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{8} + return file_filecache_proto_rawDescGZIP(), []int{9} } type Device struct { @@ -743,7 +816,7 @@ type Device struct { func (x *Device) Reset() { *x = Device{} if protoimpl.UnsafeEnabled { - mi := &file_filecache_proto_msgTypes[9] + mi := &file_filecache_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +829,7 @@ func (x *Device) String() string { func (*Device) ProtoMessage() {} func (x *Device) ProtoReflect() protoreflect.Message { - mi := &file_filecache_proto_msgTypes[9] + mi := &file_filecache_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -769,7 +842,7 @@ func (x *Device) ProtoReflect() protoreflect.Message { // Deprecated: Use Device.ProtoReflect.Descriptor instead. func (*Device) Descriptor() ([]byte, []int) { - return file_filecache_proto_rawDescGZIP(), []int{9} + return file_filecache_proto_rawDescGZIP(), []int{10} } func (x *Device) GetDeviceId() string { @@ -827,7 +900,7 @@ var file_filecache_proto_rawDesc = []byte{ 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x82, 0x08, 0x0a, 0x07, + 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xcc, 0x08, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, @@ -874,88 +947,104 @@ var file_filecache_proto_rawDesc = []byte{ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x74, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x62, 0x72, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x15, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x73, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, - 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x75, 0x6c, - 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x73, - 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, - 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x72, - 0x65, 0x6c, 0x61, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x30, 0x0a, - 0x14, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6f, 0x78, 0x5f, 0x63, - 0x61, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x46, 0x69, 0x72, 0x65, 0x66, 0x6f, 0x78, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x42, - 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x22, 0xa5, 0x02, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, - 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x41, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x50, 0x61, - 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, - 0x6c, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x61, 0x64, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, - 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, - 0x75, 0x62, 0x65, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, - 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x22, 0xca, 0x02, 0x0a, 0x1a, 0x50, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, - 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x25, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, - 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x6d, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x03, 0x74, - 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, - 0x75, 0x65, 0x12, 0x25, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x77, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x03, 0x74, 0x68, 0x75, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, - 0x12, 0x25, 0x0a, 0x03, 0x66, 0x72, 0x69, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, - 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x25, - 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, + 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x73, 0x61, 0x66, 0x65, 0x42, 0x72, + 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2c, 0x0a, + 0x12, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x75, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x4c, 0x6f, 0x67, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, + 0x79, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x66, 0x69, 0x72, 0x65, 0x66, + 0x6f, 0x78, 0x5f, 0x63, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x46, 0x69, 0x72, 0x65, 0x66, 0x6f, 0x78, 0x43, 0x61, 0x6e, + 0x61, 0x72, 0x79, 0x12, 0x44, 0x0a, 0x0d, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x62, 0x72, 0x6f, 0x77, + 0x73, 0x69, 0x6e, 0x67, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, + 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0c, 0x73, 0x61, 0x66, + 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa5, 0x02, 0x0a, 0x1a, 0x50, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, + 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, + 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x29, 0x0a, 0x10, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x75, 0x6c, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x75, + 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x61, + 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x5f, 0x73, 0x61, + 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x22, 0xad, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, + 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, + 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e, + 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x43, 0x0a, + 0x1e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x65, 0x77, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x65, 0x77, 0x6c, + 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x1a, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x50, + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x25, + 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x03, 0x73, 0x75, 0x6e, 0x22, 0x32, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x49, - 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, 0x4d, 0x41, 0x49, - 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, - 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, 0x22, 0xb5, - 0x01, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, - 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x49, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x66, 0x69, 0x6c, 0x65, - 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x03, 0x6d, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, + 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x03, + 0x77, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, + 0x77, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x03, 0x74, 0x68, 0x75, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x25, 0x0a, 0x03, 0x66, 0x72, + 0x69, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x66, 0x72, + 0x69, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x25, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x64, + 0x62, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75, 0x6e, 0x22, + 0x32, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, + 0x65, 0x6e, 0x64, 0x22, 0x3e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, + 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, + 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, + 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, + 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, + 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, + 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, + 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, 0x22, 0xb5, 0x01, 0x0a, 0x06, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x70, 0x12, 0x1f, 0x0a, + 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, + 0x49, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x66, 0x69, 0x6c, 0x65, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, + 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -970,45 +1059,47 @@ func file_filecache_proto_rawDescGZIP() []byte { return file_filecache_proto_rawDescData } -var file_filecache_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_filecache_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_filecache_proto_goTypes = []interface{}{ (*FileCache)(nil), // 0: profiledb.FileCache (*Profile)(nil), // 1: profiledb.Profile (*ParentalProtectionSettings)(nil), // 2: profiledb.ParentalProtectionSettings - (*ParentalProtectionSchedule)(nil), // 3: profiledb.ParentalProtectionSchedule - (*DayRange)(nil), // 4: profiledb.DayRange - (*BlockingModeCustomIP)(nil), // 5: profiledb.BlockingModeCustomIP - (*BlockingModeNXDOMAIN)(nil), // 6: profiledb.BlockingModeNXDOMAIN - (*BlockingModeNullIP)(nil), // 7: profiledb.BlockingModeNullIP - (*BlockingModeREFUSED)(nil), // 8: profiledb.BlockingModeREFUSED - (*Device)(nil), // 9: profiledb.Device - (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 11: google.protobuf.Duration + (*SafeBrowsingSettings)(nil), // 3: profiledb.SafeBrowsingSettings + (*ParentalProtectionSchedule)(nil), // 4: profiledb.ParentalProtectionSchedule + (*DayRange)(nil), // 5: profiledb.DayRange + (*BlockingModeCustomIP)(nil), // 6: profiledb.BlockingModeCustomIP + (*BlockingModeNXDOMAIN)(nil), // 7: profiledb.BlockingModeNXDOMAIN + (*BlockingModeNullIP)(nil), // 8: profiledb.BlockingModeNullIP + (*BlockingModeREFUSED)(nil), // 9: profiledb.BlockingModeREFUSED + (*Device)(nil), // 10: profiledb.Device + (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 12: google.protobuf.Duration } var file_filecache_proto_depIdxs = []int32{ - 10, // 0: profiledb.FileCache.sync_time:type_name -> google.protobuf.Timestamp + 11, // 0: profiledb.FileCache.sync_time:type_name -> google.protobuf.Timestamp 1, // 1: profiledb.FileCache.profiles:type_name -> profiledb.Profile - 9, // 2: profiledb.FileCache.devices:type_name -> profiledb.Device + 10, // 2: profiledb.FileCache.devices:type_name -> profiledb.Device 2, // 3: profiledb.Profile.parental:type_name -> profiledb.ParentalProtectionSettings - 5, // 4: profiledb.Profile.blocking_mode_custom_ip:type_name -> profiledb.BlockingModeCustomIP - 6, // 5: profiledb.Profile.blocking_mode_nxdomain:type_name -> profiledb.BlockingModeNXDOMAIN - 7, // 6: profiledb.Profile.blocking_mode_null_ip:type_name -> profiledb.BlockingModeNullIP - 8, // 7: profiledb.Profile.blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED - 10, // 8: profiledb.Profile.update_time:type_name -> google.protobuf.Timestamp - 11, // 9: profiledb.Profile.filtered_response_ttl:type_name -> google.protobuf.Duration - 3, // 10: profiledb.ParentalProtectionSettings.schedule:type_name -> profiledb.ParentalProtectionSchedule - 4, // 11: profiledb.ParentalProtectionSchedule.mon:type_name -> profiledb.DayRange - 4, // 12: profiledb.ParentalProtectionSchedule.tue:type_name -> profiledb.DayRange - 4, // 13: profiledb.ParentalProtectionSchedule.wed:type_name -> profiledb.DayRange - 4, // 14: profiledb.ParentalProtectionSchedule.thu:type_name -> profiledb.DayRange - 4, // 15: profiledb.ParentalProtectionSchedule.fri:type_name -> profiledb.DayRange - 4, // 16: profiledb.ParentalProtectionSchedule.sat:type_name -> profiledb.DayRange - 4, // 17: profiledb.ParentalProtectionSchedule.sun:type_name -> profiledb.DayRange - 18, // [18:18] is the sub-list for method output_type - 18, // [18:18] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 6, // 4: profiledb.Profile.blocking_mode_custom_ip:type_name -> profiledb.BlockingModeCustomIP + 7, // 5: profiledb.Profile.blocking_mode_nxdomain:type_name -> profiledb.BlockingModeNXDOMAIN + 8, // 6: profiledb.Profile.blocking_mode_null_ip:type_name -> profiledb.BlockingModeNullIP + 9, // 7: profiledb.Profile.blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED + 11, // 8: profiledb.Profile.update_time:type_name -> google.protobuf.Timestamp + 12, // 9: profiledb.Profile.filtered_response_ttl:type_name -> google.protobuf.Duration + 3, // 10: profiledb.Profile.safe_browsing:type_name -> profiledb.SafeBrowsingSettings + 4, // 11: profiledb.ParentalProtectionSettings.schedule:type_name -> profiledb.ParentalProtectionSchedule + 5, // 12: profiledb.ParentalProtectionSchedule.mon:type_name -> profiledb.DayRange + 5, // 13: profiledb.ParentalProtectionSchedule.tue:type_name -> profiledb.DayRange + 5, // 14: profiledb.ParentalProtectionSchedule.wed:type_name -> profiledb.DayRange + 5, // 15: profiledb.ParentalProtectionSchedule.thu:type_name -> profiledb.DayRange + 5, // 16: profiledb.ParentalProtectionSchedule.fri:type_name -> profiledb.DayRange + 5, // 17: profiledb.ParentalProtectionSchedule.sat:type_name -> profiledb.DayRange + 5, // 18: profiledb.ParentalProtectionSchedule.sun:type_name -> profiledb.DayRange + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_filecache_proto_init() } @@ -1054,7 +1145,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParentalProtectionSchedule); i { + switch v := v.(*SafeBrowsingSettings); i { case 0: return &v.state case 1: @@ -1066,7 +1157,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DayRange); i { + switch v := v.(*ParentalProtectionSchedule); i { case 0: return &v.state case 1: @@ -1078,7 +1169,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlockingModeCustomIP); i { + switch v := v.(*DayRange); i { case 0: return &v.state case 1: @@ -1090,7 +1181,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlockingModeNXDOMAIN); i { + switch v := v.(*BlockingModeCustomIP); i { case 0: return &v.state case 1: @@ -1102,7 +1193,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlockingModeNullIP); i { + switch v := v.(*BlockingModeNXDOMAIN); i { case 0: return &v.state case 1: @@ -1114,7 +1205,7 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlockingModeREFUSED); i { + switch v := v.(*BlockingModeNullIP); i { case 0: return &v.state case 1: @@ -1126,6 +1217,18 @@ func file_filecache_proto_init() { } } file_filecache_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockingModeREFUSED); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_filecache_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Device); i { case 0: return &v.state @@ -1150,7 +1253,7 @@ func file_filecache_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_filecache_proto_rawDesc, NumEnums: 0, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/profiledb/internal/filecachepb/filecache.proto b/internal/profiledb/internal/filecachepb/filecache.proto index 6fd8f68..06679e0 100644 --- a/internal/profiledb/internal/filecachepb/filecache.proto +++ b/internal/profiledb/internal/filecachepb/filecache.proto @@ -29,12 +29,13 @@ message Profile { repeated string custom_rules = 10; google.protobuf.Duration filtered_response_ttl = 11; bool filtering_enabled = 12; - bool safe_browsing_enabled = 13; + bool safe_browsing_enabled = 13 [deprecated = true]; bool rule_lists_enabled = 14; bool query_log_enabled = 15; bool deleted = 16; bool block_private_relay = 17; bool block_firefox_canary = 18; + SafeBrowsingSettings safe_browsing = 19; } message ParentalProtectionSettings { @@ -46,6 +47,12 @@ message ParentalProtectionSettings { bool youtube_safe_search = 6; } +message SafeBrowsingSettings { + bool enabled = 1; + bool block_dangerous_domains = 2; + bool block_newly_registered_domains = 3; +} + message ParentalProtectionSchedule { string time_zone = 1; DayRange mon = 2; diff --git a/internal/profiledb/internal/filecachepb/filecachepb.go b/internal/profiledb/internal/filecachepb/filecachepb.go index 71a8438..aaafcd2 100644 --- a/internal/profiledb/internal/filecachepb/filecachepb.go +++ b/internal/profiledb/internal/filecachepb/filecachepb.go @@ -85,7 +85,7 @@ func (x *Profile) toInternal() (prof *agd.Profile, err error) { CustomRules: unsafelyConvertStrSlice[string, agd.FilterRuleText](x.CustomRules), FilteredResponseTTL: x.FilteredResponseTtl.AsDuration(), FilteringEnabled: x.FilteringEnabled, - SafeBrowsingEnabled: x.SafeBrowsingEnabled, + SafeBrowsing: x.SafeBrowsing.toInternal(), RuleListsEnabled: x.RuleListsEnabled, QueryLogEnabled: x.QueryLogEnabled, Deleted: x.Deleted, @@ -240,6 +240,20 @@ func byteSlicesToIPs(data [][]byte) (ips []netip.Addr, err error) { return ips, nil } +// toInternal converts a protobuf safe browsing settings structure to an +// internal one. +func (x *SafeBrowsingSettings) toInternal() (s *agd.SafeBrowsingSettings) { + if x == nil { + return nil + } + + return &agd.SafeBrowsingSettings{ + Enabled: x.Enabled, + BlockDangerousDomains: x.BlockDangerousDomains, + BlockNewlyRegisteredDomains: x.BlockNewlyRegisteredDomains, + } +} + // profilesToProtobuf converts a slice of profiles to protobuf structures. func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) { pbProfiles = make([]*Profile, 0, len(profiles)) @@ -254,7 +268,7 @@ func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) { CustomRules: unsafelyConvertStrSlice[agd.FilterRuleText, string](p.CustomRules), FilteredResponseTtl: durationpb.New(p.FilteredResponseTTL), FilteringEnabled: p.FilteringEnabled, - SafeBrowsingEnabled: p.SafeBrowsingEnabled, + SafeBrowsing: safeBrowsingToProtobuf(p.SafeBrowsing), RuleListsEnabled: p.RuleListsEnabled, QueryLogEnabled: p.QueryLogEnabled, Deleted: p.Deleted, @@ -386,3 +400,16 @@ func ipsToByteSlices(ips []netip.Addr) (data [][]byte) { return data } + +// safeBrowsingToProtobuf converts safe browsing settings to protobuf structure. +func safeBrowsingToProtobuf(s *agd.SafeBrowsingSettings) (sbSetts *SafeBrowsingSettings) { + if s == nil { + return nil + } + + return &SafeBrowsingSettings{ + Enabled: s.Enabled, + BlockDangerousDomains: s.BlockDangerousDomains, + BlockNewlyRegisteredDomains: s.BlockNewlyRegisteredDomains, + } +} diff --git a/internal/profiledb/internal/filecachepb/storage.go b/internal/profiledb/internal/filecachepb/storage.go index 4e2eb96..3d60e0c 100644 --- a/internal/profiledb/internal/filecachepb/storage.go +++ b/internal/profiledb/internal/filecachepb/storage.go @@ -7,7 +7,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/google/renameio" + renameio "github.com/google/renameio/v2" "google.golang.org/protobuf/proto" ) @@ -49,6 +49,17 @@ func (s *Storage) Load() (c *internal.FileCache, err error) { return nil, fmt.Errorf("decoding protobuf: %w", err) } + if fc.Version != internal.FileCacheVersion { + // Do not decode protobuf file contents in case it probably has + // unexpected structure. + return nil, fmt.Errorf( + "%w: version %d is different from %d", + internal.CacheVersionError, + fc.Version, + internal.FileCacheVersion, + ) + } + return toInternal(fc) } diff --git a/internal/profiledb/internal/internal.go b/internal/profiledb/internal/internal.go index 2d2705a..fc8c0b5 100644 --- a/internal/profiledb/internal/internal.go +++ b/internal/profiledb/internal/internal.go @@ -6,12 +6,17 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/golibs/errors" ) // FileCacheVersion is the version of cached data structure. It must be // manually incremented on every change in [agd.Device], [agd.Profile], and any // file-cache structures. -const FileCacheVersion = 6 +const FileCacheVersion = 7 + +// CacheVersionError is returned from [FileCacheStorage.Load] method if the +// stored cache version doesn't match current [FileCacheVersion]. +const CacheVersionError errors.Error = "unsuitable cache version" // FileCache contains the data that is cached on the filesystem. type FileCache struct { diff --git a/internal/profiledb/internal/profiledbtest/profiledbtest.go b/internal/profiledb/internal/profiledbtest/profiledbtest.go index 9d0d28c..cc9cb45 100644 --- a/internal/profiledb/internal/profiledbtest/profiledbtest.go +++ b/internal/profiledb/internal/profiledbtest/profiledbtest.go @@ -70,10 +70,14 @@ func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) { }, FilteredResponseTTL: 10 * time.Second, FilteringEnabled: true, - SafeBrowsingEnabled: true, - RuleListsEnabled: true, - QueryLogEnabled: true, - BlockPrivateRelay: true, - BlockFirefoxCanary: true, + SafeBrowsing: &agd.SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNewlyRegisteredDomains: false, + }, + RuleListsEnabled: true, + QueryLogEnabled: true, + BlockPrivateRelay: true, + BlockFirefoxCanary: true, }, dev } diff --git a/internal/profiledb/profiledb.go b/internal/profiledb/profiledb.go index ffa4a95..43b5b38 100644 --- a/internal/profiledb/profiledb.go +++ b/internal/profiledb/profiledb.go @@ -237,6 +237,12 @@ func (db *Default) loadFileCache() (err error) { c, err := db.cache.Load() if err != nil { + if errors.Is(err, internal.CacheVersionError) { + log.Info("%s: %s", logPrefix, err) + + return nil + } + // Don't wrap the error, because it's informative enough as is. return err } else if c == nil { @@ -255,16 +261,7 @@ func (db *Default) loadFileCache() (err error) { time.Since(start), ) - if c.Version != internal.FileCacheVersion { - log.Info( - "%s: version %d is different from %d", - logPrefix, - c.Version, - internal.FileCacheVersion, - ) - - return nil - } else if profNum == 0 || devNum == 0 { + if profNum == 0 || devNum == 0 { log.Info("%s: empty", logPrefix) return nil diff --git a/internal/profiledb/testdata/profiles.json b/internal/profiledb/testdata/profiles.json index d6b7350..1aca6a8 100644 --- a/internal/profiledb/testdata/profiles.json +++ b/internal/profiledb/testdata/profiles.json @@ -54,7 +54,11 @@ ], "FilteredResponseTTL": 10000000000, "FilteringEnabled": true, - "SafeBrowsingEnabled": true, + "SafeBrowsing": { + "Enabled": true, + "BlockDangerousDomains": true, + "BlockNewlyRegisteredDomains": false + }, "RuleListsEnabled": true, "QueryLogEnabled": true, "Deleted": false, @@ -73,5 +77,5 @@ "FilteringEnabled": true } ], - "version": 6 + "version": 7 } diff --git a/internal/querylog/entry.go b/internal/querylog/entry.go index 794c959..36dcfd9 100644 --- a/internal/querylog/entry.go +++ b/internal/querylog/entry.go @@ -22,9 +22,6 @@ type Entry struct { // Time is the time of receiving the request in milliseconds. Time time.Time - // RequestID is the ID of the request. - RequestID agd.RequestID - // ProfileID is the detected profile ID, if any. ProfileID agd.ProfileID @@ -41,6 +38,9 @@ type Entry struct { // DomainFQDN is the fully-qualified name of the requested resource. DomainFQDN string + // RequestID is the ID of the request. + RequestID agd.RequestID + // ClientASN is the detected autonomous system number of the client's IP // address, if any. ClientASN agd.ASN @@ -134,7 +134,7 @@ type jsonlEntry struct { // RequestID is the ID of the request. // // The short name "u" stands for "unique". - RequestID agd.RequestID `json:"u"` + RequestID string `json:"u"` // ProfileID is the detected profile ID, if any. // diff --git a/internal/querylog/fs.go b/internal/querylog/fs.go index 1e4449d..0393a36 100644 --- a/internal/querylog/fs.go +++ b/internal/querylog/fs.go @@ -78,7 +78,7 @@ func (l *FileSystem) Write(_ context.Context, e *Entry) (err error) { c, id, r := resultData(e.RequestResult, e.ResponseResult) *entBuf.ent = jsonlEntry{ - RequestID: e.RequestID, + RequestID: e.RequestID.String(), ProfileID: e.ProfileID, DeviceID: e.DeviceID, ClientCountry: e.ClientCountry, diff --git a/internal/querylog/fs_test.go b/internal/querylog/fs_test.go index 02a9e6a..28c0621 100644 --- a/internal/querylog/fs_test.go +++ b/internal/querylog/fs_test.go @@ -34,10 +34,14 @@ func TestFileSystem_Write(t *testing.T) { b, err := io.ReadAll(f) require.NoError(t, err) - rep := strings.NewReplacer(" ", "", "\n", "") + rep := strings.NewReplacer( + " ", "", + "\n", "", + "REQID", testRequestID.String(), + ) want := rep.Replace(` { - "u":"req1234", + "u":"REQID", "b":"prof1234", "i":"dev1234", "c":"RU", @@ -69,10 +73,14 @@ func TestFileSystem_Write(t *testing.T) { b, err = io.ReadAll(f) require.NoError(t, err) - rep = strings.NewReplacer(" ", "", "\n", "") + rep = strings.NewReplacer( + " ", "", + "\n", "", + "REQID", testRequestID.String(), + ) want = rep.Replace(` { - "u":"req1234", + "u":"REQID", "b":"prof1234", "i":"dev1234", "c":"RU", diff --git a/internal/querylog/querylog_test.go b/internal/querylog/querylog_test.go index 4586af7..f6ec01e 100644 --- a/internal/querylog/querylog_test.go +++ b/internal/querylog/querylog_test.go @@ -15,7 +15,8 @@ func TestMain(m *testing.M) { testutil.DiscardLogOutput(m) } -// Helpers +// testRequestID is the common request ID for tests. +var testRequestID = agd.NewRequestID() // testEntry returns an entry for tests. func testEntry() (e *querylog.Entry) { @@ -26,7 +27,7 @@ func testEntry() (e *querylog.Entry) { }, ResponseResult: nil, Time: time.Unix(123, 0), - RequestID: "req1234", + RequestID: testRequestID, ProfileID: "prof1234", DeviceID: "dev1234", ClientCountry: agd.CountryRU, diff --git a/internal/tools/go.mod b/internal/tools/go.mod index ea99e18..4eeff43 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -4,31 +4,32 @@ go 1.20 require ( github.com/fzipp/gocyclo v0.6.0 - github.com/golangci/misspell v0.4.0 - github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 + github.com/golangci/misspell v0.4.1 + github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 github.com/kisielk/errcheck v1.6.3 github.com/kyoh86/looppointer v0.2.1 github.com/securego/gosec/v2 v2.16.0 - golang.org/x/tools v0.9.3 - golang.org/x/vuln v0.1.0 - google.golang.org/protobuf v1.30.0 + github.com/uudashr/gocognit v1.0.7 + golang.org/x/tools v0.11.1 + golang.org/x/vuln v1.0.0 + google.golang.org/protobuf v1.31.0 honnef.co/go/tools v0.4.3 mvdan.cc/gofumpt v0.5.0 - mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 + mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 ) require ( - github.com/BurntSushi/toml v1.3.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gookit/color v1.5.3 // indirect + github.com/gookit/color v1.5.4 // indirect github.com/kyoh86/nolint v0.0.1 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect - golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index 9c17051..deff817 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -1,6 +1,5 @@ -github.com/BurntSushi/toml v1.3.1 h1:rHnDkSK+/g6DlREUK73PkmIs60pqrnuduK+JmP++JmU= -github.com/BurntSushi/toml v1.3.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= @@ -9,8 +8,8 @@ github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlya github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= -github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= +github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= +github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -19,10 +18,10 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= 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/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= -github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -41,7 +40,9 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/securego/gosec/v2 v2.16.0 h1:Pi0JKoasQQ3NnoRao/ww/N/XdynIB9NRYYZT5CyOs5U= github.com/securego/gosec/v2 v2.16.0/go.mod h1:xvLcVZqUfo4aAQu56TNv7/Ltz6emAOQAEsrZrt7uGlI= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/uudashr/gocognit v1.0.7 h1:e9aFXgKgUJrQ5+bs61zBigmj7bFJ/5cC6HmMahVzuDo= +github.com/uudashr/gocognit v1.0.7/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -53,25 +54,26 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 h1:pnP8r+W8Fm7XJ8CWtXi4S9oJmPBTrkfYN/dNbaPj6Y4= -golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b h1:3dfup1Bt5y1sKG6rbyAX4qNymwAtJcqx+Aqm1DPP/Qg= +golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -81,8 +83,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -94,17 +97,18 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -golang.org/x/vuln v0.1.0 h1:9GRdj6wAIkDrsMevuolY+SXERPjQPp2P1ysYA0jpZe0= -golang.org/x/vuln v0.1.0/go.mod h1:/YuzZYjGbwB8y19CisAppfyw3uTZnuCz3r+qgx/QRzU= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g= +golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -113,5 +117,5 @@ honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= -mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI= -mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8= +mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 h1:F4Q7pXcrU9UiU1fq0ZWqSOxKjNAteRuDr7JDk7uVLRQ= +mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868/go.mod h1:6ZaiQyI7Tiq0HQ56g6N8TlkSd80/LyagZeaw8mb7jYE= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index deaa4f1..6a1a029 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -1,5 +1,4 @@ //go:build tools -// +build tools package tools @@ -10,6 +9,7 @@ import ( _ "github.com/kisielk/errcheck" _ "github.com/kyoh86/looppointer" _ "github.com/securego/gosec/v2/cmd/gosec" + _ "github.com/uudashr/gocognit/cmd/gocognit" _ "golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness" _ "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow" _ "golang.org/x/vuln/cmd/govulncheck" diff --git a/internal/websvc/linkip.go b/internal/websvc/linkip.go index 2270aa9..66f34a1 100644 --- a/internal/websvc/linkip.go +++ b/internal/websvc/linkip.go @@ -36,22 +36,14 @@ func linkedIPHandler( ) (h http.Handler) { logPrefix := fmt.Sprintf("websvc: linked ip proxy %s", name) - // Use a custom Director to make sure we send the correct Host header and - // don't send anything besides the path. - // - // TODO(a.garipov): Use the Go 1.20 [httputil.ReverseProxy.Rewrite] API? - director := func(r *http.Request) { - r.URL.Scheme = apiURL.Scheme - r.Host, r.URL.Host = apiURL.Host, apiURL.Host - - hdr := r.Header - - // Set the X-Forwarded-For header to a nil value to make sure that - // the proxy doesn't add it automatically. - hdr[httphdr.XForwardedFor] = nil + // Use a Rewrite func to make sure we send the correct Host header and don't + // send anything besides the path. + rewrite := func(r *httputil.ProxyRequest) { + r.SetURL(apiURL) + r.Out.Host = apiURL.Host // Make sure that all requests are marked with our user agent. - hdr.Set(httphdr.UserAgent, agdhttp.UserAgent()) + r.Out.Header.Set(httphdr.UserAgent, agdhttp.UserAgent()) } // Use largely the same transport as http.DefaultTransport, but with a @@ -94,7 +86,7 @@ func linkedIPHandler( return &linkedIPProxy{ httpProxy: &httputil.ReverseProxy{ - Director: director, + Rewrite: rewrite, Transport: transport, ErrorLog: log.StdLog(logPrefix, log.DEBUG), ModifyResponse: modifyResponse, @@ -147,7 +139,7 @@ func (prx *linkedIPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Set the request ID. reqID := agd.NewRequestID() r = r.WithContext(agd.WithRequestID(r.Context(), reqID)) - hdr.Set(httphdr.XRequestID, string(reqID)) + hdr.Set(httphdr.XRequestID, reqID.String()) log.Debug("%s: proxying %s %s: req %s", prx.logPrefix, m, p, reqID) diff --git a/internal/websvc/linkip_internal_test.go b/internal/websvc/linkip_internal_test.go index e325474..f43291c 100644 --- a/internal/websvc/linkip_internal_test.go +++ b/internal/websvc/linkip_internal_test.go @@ -19,12 +19,29 @@ import ( ) func TestLinkedIPProxy_ServeHTTP(t *testing.T) { - var numReq atomic.Uint64 + var ( + apiURL *url.URL + numReq atomic.Uint64 + ) + upstream := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { pt := testutil.PanicT{} - rid := r.Header.Get(httphdr.XRequestID) - require.NotEmpty(pt, rid) + hdr := r.Header + + require.NotEmpty(pt, hdr.Get(httphdr.XRequestID)) + require.Equal(pt, agdhttp.UserAgent(), hdr.Get(httphdr.UserAgent)) + require.NotEmpty(pt, hdr.Get(httphdr.CFConnectingIP)) + + require.Empty(pt, hdr.Get(httphdr.Forwarded)) + require.Empty(pt, hdr.Get(httphdr.XForwardedHost)) + require.Empty(pt, hdr.Get(httphdr.XForwardedProto)) + require.Empty(pt, hdr.Get(httphdr.XForwardedFor)) + + require.Empty(pt, hdr.Get(httphdr.TrueClientIP)) + require.Empty(pt, hdr.Get(httphdr.XRealIP)) + + require.Equal(pt, apiURL.Host, r.Host) numReq.Add(1) }) @@ -44,50 +61,55 @@ func TestLinkedIPProxy_ServeHTTP(t *testing.T) { 2*time.Second, ) - expectedUserAgent := agdhttp.UserAgent() - testCases := []struct { - name string - method string - path string - diff uint64 - wantCode int + name string + method string + path string + wantAccessControlHdrVal string + diff uint64 + wantCode int }{{ - name: "linkip", - method: http.MethodGet, - path: "/linkip/dev1234/0123456789/status", - diff: +1, - wantCode: http.StatusOK, + name: "linkip", + method: http.MethodGet, + path: "/linkip/dev1234/0123456789/status", + diff: +1, + wantCode: http.StatusOK, + wantAccessControlHdrVal: agdhttp.HdrValWildcard, }, { - name: "ddns", - method: http.MethodPost, - path: "/ddns/dev1234/0123456789/example.com", - diff: +1, - wantCode: http.StatusOK, + name: "ddns", + method: http.MethodPost, + path: "/ddns/dev1234/0123456789/example.com", + diff: +1, + wantCode: http.StatusOK, + wantAccessControlHdrVal: agdhttp.HdrValWildcard, }, { - name: "other", - method: http.MethodGet, - path: "/some/other/path", - diff: 0, - wantCode: http.StatusNotFound, + name: "other", + method: http.MethodGet, + path: "/some/other/path", + diff: 0, + wantCode: http.StatusNotFound, + wantAccessControlHdrVal: "", }, { - name: "robots_txt", - method: http.MethodGet, - path: "/robots.txt", - diff: 0, - wantCode: http.StatusOK, + name: "robots_txt", + method: http.MethodGet, + path: "/robots.txt", + diff: 0, + wantCode: http.StatusOK, + wantAccessControlHdrVal: "", }, { - name: "linkip_bad_path", - method: http.MethodGet, - path: "/linkip/dev1234/0123456789/status/more/stuff", - diff: 0, - wantCode: http.StatusNotFound, + name: "linkip_bad_path", + method: http.MethodGet, + path: "/linkip/dev1234/0123456789/status/more/stuff", + diff: 0, + wantCode: http.StatusNotFound, + wantAccessControlHdrVal: "", }, { - name: "linkip_bad_method", - method: http.MethodDelete, - path: "/linkip/dev1234/0123456789/status", - diff: 0, - wantCode: http.StatusNotFound, + name: "linkip_bad_method", + method: http.MethodDelete, + path: "/linkip/dev1234/0123456789/status", + diff: 0, + wantCode: http.StatusNotFound, + wantAccessControlHdrVal: "", }} for _, tc := range testCases { @@ -97,14 +119,26 @@ func TestLinkedIPProxy_ServeHTTP(t *testing.T) { Host: "www.example.com", Path: tc.path, }).String(), strings.NewReader("")) + + // Set some test headers. + r.Header.Set(httphdr.Forwarded, "1.1.1.1") + r.Header.Set(httphdr.XForwardedHost, "forward.example.org") + r.Header.Set(httphdr.XForwardedProto, "https") + r.Header.Set(httphdr.XForwardedFor, "1.1.1.1") + + r.Header.Set(httphdr.TrueClientIP, "1.1.1.1") + r.Header.Set(httphdr.XRealIP, "1.1.1.1") + rw := httptest.NewRecorder() prev := numReq.Load() h.ServeHTTP(rw, r) assert.Equal(t, prev+tc.diff, numReq.Load(), "req was not expected") - assert.Equal(t, tc.wantCode, rw.Code) - assert.Equal(t, expectedUserAgent, rw.Header().Get(httphdr.Server)) + + hdr := rw.Header() + assert.Equal(t, agdhttp.UserAgent(), hdr.Get(httphdr.Server)) + assert.Equal(t, tc.wantAccessControlHdrVal, hdr.Get(httphdr.AccessControlAllowOrigin)) }) } } diff --git a/internal/websvc/websvc.go b/internal/websvc/websvc.go index 7a1f4e1..583648c 100644 --- a/internal/websvc/websvc.go +++ b/internal/websvc/websvc.go @@ -26,10 +26,6 @@ type Config struct { // LinkedIP is the optional linked IP web server. LinkedIP *LinkedIPServer - // LinkedIPBackendURL is the URL to which linked IP API requests are - // proxied. - LinkedIPBackendURL *url.URL - // RootRedirectURL is the URL to which root HTTP requests are redirected. // If not set, these requests are responded with a 404 page. RootRedirectURL *url.URL @@ -62,6 +58,9 @@ type Config struct { // LinkedIPServer is the linked IP server configuration. type LinkedIPServer struct { + // TargetURL is the URL to which linked IP API requests are proxied. + TargetURL *url.URL + // Bind are the addresses on which to serve the linked IP API. Bind []*BindData } @@ -128,10 +127,10 @@ func New(c *Config) (svc *Service) { svc.rootRedirectURL = c.RootRedirectURL.String() } - if l := c.LinkedIP; l != nil { + if l := c.LinkedIP; l != nil && l.TargetURL != nil { for _, b := range l.Bind { addr := b.Address.String() - h := linkedIPHandler(c.LinkedIPBackendURL, c.ErrColl, addr, c.Timeout) + h := linkedIPHandler(l.TargetURL, c.ErrColl, addr, c.Timeout) errLog := log.StdLog(fmt.Sprintf("websvc: linked ip: %s", addr), log.DEBUG) svc.linkedIP = append(svc.linkedIP, &http.Server{ Addr: addr, diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh index 59da921..4df7220 100644 --- a/scripts/make/go-build.sh +++ b/scripts/make/go-build.sh @@ -102,4 +102,10 @@ then "$go" env fi -"$go" build --ldflags="$ldflags" "$race_flags" --trimpath "$o_flags" "$v_flags" "$x_flags" +"$go" build\ + --ldflags="$ldflags"\ + "$race_flags"\ + --trimpath\ + "$o_flags"\ + "$v_flags"\ + "$x_flags" diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 9c5b2b4..b2c0a7d 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a significant change is made to this script. # -# AdGuard-Project-Version: 3 +# AdGuard-Project-Version: 5 verbose="${VERBOSE:-0}" readonly verbose @@ -35,7 +35,7 @@ set -f -u go_version="$( "${GO:-go}" version )" readonly go_version -go_min_version='go1.20.5' +go_min_version='go1.20.7' go_version_msg=" warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}). if you have the version installed, please set the GO environment variable. @@ -80,13 +80,17 @@ esac # # * Package golang.org/x/net/context has been moved into stdlib. # -# NOTE: For AdGuard DNS, there are the following exceptions: +# Currently, the only standard exception are files generated from protobuf +# schemas, which use package reflect. If your project needs more exceptions, +# add and document them. # -# * internal/profiledb/internal/filecachepb/filecache.pb.go: a file generated -# by the protobuf compiler. +# NOTE: For AdGuard DNS, there are the following exceptions: # # * internal/profiledb/internal/filecachepb/unsafe.go: a “safe” unsafe helper # to prevent excessive allocations. +# +# TODO(a.garipov): Add deprecated packages golang.org/x/exp/maps and +# golang.org/x/exp/slices once all projects switch to Go 1.21. blocklist_imports() { git grep\ -e '[[:space:]]"errors"$'\ @@ -98,7 +102,7 @@ blocklist_imports() { -e '[[:space:]]"golang.org/x/net/context"$'\ -n\ -- '*.go'\ - ':!internal/profiledb/internal/filecachepb/filecache.pb.go'\ + ':!*.pb.go'\ ':!internal/profiledb/internal/filecachepb/unsafe.go'\ | sed -e 's/^\([^[:space:]]\+\)\(.*\)$/\1 blocked import:\2/'\ || exit 0 @@ -110,6 +114,7 @@ method_const() { git grep -F\ -e '"DELETE"'\ -e '"GET"'\ + -e '"PATCH"'\ -e '"POST"'\ -e '"PUT"'\ -n\ @@ -145,6 +150,8 @@ underscores() { fi } +# TODO(a.garipov): Add an analyzer to look for `fallthrough`, `goto`, and `new`? + # Checks @@ -170,6 +177,46 @@ run_linter govulncheck ./... "$dnssrvmod" # NOTE: For AdGuard DNS, ignore the generated protobuf file. run_linter gocyclo --ignore '\.pb\.go$' --over 10 . +# TODO(a.garipov): Enable for all. +run_linter gocognit --over 10\ + ./internal/agd/\ + ./internal/agdhttp/\ + ./internal/agdio/\ + ./internal/agdnet/\ + ./internal/agdtest/\ + ./internal/agdtime/\ + ./internal/billstat/\ + ./internal/bindtodevice/\ + ./internal/connlimiter/\ + ./internal/consul/\ + ./internal/debugsvc/\ + ./internal/dnscheck/\ + ./internal/dnsdb/\ + ./internal/dnsserver/cache/\ + ./internal/dnsserver/dnsservertest/\ + ./internal/dnsserver/forward/\ + ./internal/dnsserver/netext/\ + ./internal/dnsserver/pool/\ + ./internal/dnsserver/prometheus/\ + ./internal/dnsserver/querylog/\ + ./internal/dnsserver/ratelimit/\ + ./internal/errcoll/\ + ./internal/filter/internal/custom/\ + ./internal/filter/internal/filtertest/\ + ./internal/filter/internal/resultcache/\ + ./internal/filter/internal/rulelist/\ + ./internal/filter/internal/safesearch/\ + ./internal/filter/internal/serviceblock/\ + ./internal/geoip/\ + ./internal/optlog/\ + ./internal/profiledb/internal/filecachejson/\ + ./internal/profiledb/internal/profiledbtest/\ + ./internal/querylog/\ + ./internal/rulestat/\ + ./internal/tools/\ + ./internal/websvc/\ + ; + run_linter ineffassign ./... "$dnssrvmod" run_linter unparam ./... "$dnssrvmod" diff --git a/scripts/make/go-test.sh b/scripts/make/go-test.sh index 5b87aa4..1c5443f 100644 --- a/scripts/make/go-test.sh +++ b/scripts/make/go-test.sh @@ -49,9 +49,9 @@ readonly go count_flags shuffle_flags timeout_flags # TODO(a.garipov): Remove the dnsserver stuff once it is separated. "$go" test\ "$count_flags"\ - "$shuffle_flags"\ "$race_flags"\ + "$shuffle_flags"\ "$timeout_flags"\ - "$x_flags"\ "$v_flags"\ + "$x_flags"\ ./... github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/... diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh index 8ec6135..c9f2c92 100644 --- a/scripts/make/go-tools.sh +++ b/scripts/make/go-tools.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a significant change is made to this script. # -# AdGuard-Project-Version: 2 +# AdGuard-Project-Version: 3 verbose="${VERBOSE:-0}" readonly verbose @@ -36,6 +36,7 @@ readonly go rm -f\ bin/errcheck\ bin/fieldalignment\ + bin/gocognit\ bin/gocyclo\ bin/gofumpt\ bin/gosec\ @@ -68,6 +69,7 @@ env\ github.com/kisielk/errcheck\ github.com/kyoh86/looppointer/cmd/looppointer\ github.com/securego/gosec/v2/cmd/gosec\ + github.com/uudashr/gocognit/cmd/gocognit\ golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment\ golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness\ golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow\ diff --git a/scripts/make/txt-lint.sh b/scripts/make/txt-lint.sh index 6edc8bf..35d0394 100644 --- a/scripts/make/txt-lint.sh +++ b/scripts/make/txt-lint.sh @@ -3,7 +3,7 @@ # This comment is used to simplify checking local copies of the script. Bump # this number every time a remarkable change is made to this script. # -# AdGuard-Project-Version: 2 +# AdGuard-Project-Version: 4 verbose="${VERBOSE:-0}" readonly verbose @@ -27,6 +27,8 @@ set -f -u # Source the common helpers, including not_found. . ./scripts/make/helper.sh +# Simple analyzers + # trailing_newlines is a simple check that makes sure that all plain-text files # have a trailing newlines to make sure that all tools work correctly with them. trailing_newlines() { @@ -45,8 +47,23 @@ trailing_newlines() { done } +# trailing_whitespace is a simple check that makes sure that there are no +# trailing whitespace in plain-text files. +trailing_whitespace() { + # NOTE: Adjust for your project. + git ls-files\ + ':!*.mmdb'\ + | while read -r f + do + grep -e '[[:space:]]$' -n -- "$f"\ + | sed -e "s:^:${f}\::" -e 's/ \+$/>>>&<<