diff --git a/CHANGELOG.md b/CHANGELOG.md index c60d030..29b5bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,49 @@ The format is **not** based on [Keep a Changelog][kec], since the project -## AGDNS-1537 / Build 580 +## AGDNS-1607 / Build 617 + +* New configuration `access` has been added, it has an a list of AdBlock rules + to block requests, and a lists of client subnets to block access from. + Example configuration: + + ```yaml + access: + blocked_question_domains: + - 'test.org' + - '||example.org^$dnstype=AAAA' + blocked_client_subnets: + - '1.1.1.1' + - '2.2.2.0/8' + ``` + + + +## AGDNS-1619 / Build 611 + +* Added a new metric `bill_stat_upload_duration` that counts the duration of + billing statistics upload. +* The environment variable `BILLSTAT_URL`, which describes the endpoint for + backend billing statistics uploader API, now supports GRPC endpoints. + + + +## AGDNS-1600 / Build 582 + + * The environment variable `PROFILES_CACHE_PATH` no longer supports JSON + files. Use protobuf with `.pb` extension instead. The default value has + been changed to `./profilecache.pb`. + + + +## AGDNS-1539 / Build 581 + + * The environment variable `PROFILES_URL`, which describes the endpoint for + profiles sync API, now supports GRPC endpoints. + + + +## AGDNS-1579 / Build 580 * The optional property `bind_interfaces` of `server_groups.*.servers` objects has been changed, property `subnet` is now an array and has been diff --git a/Makefile b/Makefile index decf5b3..66973fe 100644 --- a/Makefile +++ b/Makefile @@ -51,17 +51,11 @@ test: go-test go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh +go-gen: ; $(ENV) "$(SHELL)" ./scripts/make/go-gen.sh go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh -go-gen: - cd ./internal/agd/ && "$(GO.MACRO)" run ./country_generate.go - cd ./internal/geoip/ && "$(GO.MACRO)" run ./asntops_generate.go - - cd ./internal/profiledb/internal/filecachepb/ &&\ - protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto - go-check: go-tools go-lint go-test # A quick check to make sure that all operating systems relevant to the diff --git a/config.dist.yaml b/config.dist.yaml index 6806607..72763f3 100644 --- a/config.dist.yaml +++ b/config.dist.yaml @@ -52,6 +52,15 @@ ratelimit: stop: 1000 resume: 800 +# Access settings. +access: + # Domains to block. + blocked_question_domains: + - 'test.org' + # Client subnets to block. + blocked_client_subnets: + - '1.2.3.0/8' + # DNS cache configuration. cache: # The type of cache to use. Can be 'simple' (a simple LRU cache) or 'ecs' diff --git a/doc/configuration.md b/doc/configuration.md index 90d8cc9..8ec88db 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -32,6 +32,7 @@ configuration file with comments. * [Servers](#server_groups-*-servers-*) * [Connectivity check](#connectivity-check) * [Network settings](#network) + * [Access settings](#access) * [Additional metrics information](#additional_metrics_info) [dist]: ../config.dist.yml @@ -1093,6 +1094,22 @@ The `network` object has the following properties: +## Access settings + +The `access` object has the following properties: + +* `blocked_question_domains`: + The list of domains or AdBlock rules to block requests. + + **Examples:** `test.org`, `||example.org^$dnstype=AAAA`. + +* `blocked_client_subnets`: + The list of IP addresses or CIDR-es to block. + + **Example:** `127.0.0.1`. + + + ## Additional metrics information The `additional_metrics_info` object is a map of strings with extra information diff --git a/doc/development.md b/doc/development.md index 42990f0..6b52219 100644 --- a/doc/development.md +++ b/doc/development.md @@ -241,27 +241,33 @@ Examples below are for the configuration with the following changes: You may also need to remove `probe_ipv6` if your network does not support IPv6. +If you're using an OS different from Linux, you also need to make these changes: + + * Remove the `interface_listeners` section. + * Remove `bind_interfaces` from the `default_dns` server configuration and + replace it with `bind_addresses`. + ```sh env \ - 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' \ + ADULT_BLOCKING_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/adult_blocking.txt' \ + BILLSTAT_URL='https://httpbin.agrd.workers.dev/post' \ + BLOCKED_SERVICE_INDEX_URL='https://adguardteam.github.io/HostlistsRegistry/assets/services.json' \ + CONSUL_ALLOWLIST_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/consul_allowlist.json' \ CONFIG_PATH='./config.yaml' \ 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' \ + NEW_REG_DOMAINS_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/nrd.txt' \ + PROFILES_CACHE_PATH='./test/profilecache.pb' \ + PROFILES_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/profiles' \ + SAFE_BROWSING_URL='https://raw.githubusercontent.com/ameshkov/stuff/master/DNS/safe_browsing.txt' \ GENERAL_SAFE_SEARCH_URL='https://adguardteam.github.io/HostlistsRegistry/assets/engines_safe_search.txt' \ GEOIP_ASN_PATH='./test/GeoLite2-ASN-Test.mmdb' \ GEOIP_COUNTRY_PATH='./test/GeoIP2-City-Test.mmdb' \ QUERYLOG_PATH='./test/cache/querylog.jsonl' \ - LINKED_IP_TARGET_URL='PUT LINKED IP TARGET URL HERE' \ + LINKED_IP_TARGET_URL='https://httpbin.agrd.workers.dev/anything' \ LISTEN_ADDR='127.0.0.1' \ LISTEN_PORT='8081' \ - RULESTAT_URL='https://testchrome.adtidy.org/api/1.0/rulestats.html' \ + RULESTAT_URL='https://httpbin.agrd.workers.dev/post' \ SENTRY_DSN='https://1:1@localhost/1' \ VERBOSE='1' \ YOUTUBE_SAFE_SEARCH_URL='https://adguardteam.github.io/HostlistsRegistry/assets/youtube_safe_search.txt' \ diff --git a/doc/environment.md b/doc/environment.md index 02e52f7..421c48c 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -49,9 +49,10 @@ The URL of source list of rules for adult blocking filter. ## `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] +The base backend URL for backend billing statistics uploader API. Supports HTTP +and GRPC protocols. In case of HTTP the backend endpoint must reply with a 200 +status code on success. See the [external HTTP API requirements +section][ext-billstat]. **Default:** No default value, the variable is **required.** @@ -227,13 +228,10 @@ The path to the profile cache file: < /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`. +**Default:** `./profilecache.pb`. [conf-backend-full_refresh_interval]: configuration.md#backend-full_refresh_interval @@ -241,9 +239,10 @@ The profile cache is read on start and is later updated on every ## `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]. +The base backend URL for profiles API. Supports HTTP (`http://` and `https://`) +and GRPC (`grpc://` and `grpcs://`) URLs. In case of HTTP the backend endpoint +must reply with a 200 status code on success. See the [external API +requirements section][ext-profiles]. **Default:** No default value, the variable is **required.** diff --git a/doc/externalhttp.md b/doc/externalhttp.md index cb3cf01..d14837f 100644 --- a/doc/externalhttp.md +++ b/doc/externalhttp.md @@ -29,7 +29,9 @@ document should set the `Server` header in their replies. ## Backend Billing Statistics This is the service to which the [`BILLSTAT_URL`][env-billstat_url] environment -variable points. This service must provide one endpoint: +variable points. Supports `http(s):` and `grpc(s)` URLs. In case of GRPC +protocol, the service must correspond to `./internal/backendpb/backend.proto`. +In case of HTTP protocol this service must provide one endpoint: `POST /dns_api/v1/devices_activity`, it must respond with a `200 OK` response code and accept a JSON document in the following format: @@ -55,7 +57,9 @@ code and accept a JSON document in the following format: ## Backend Profiles Service This is the service to which the [`PROFILES_URL`][env-profiles_url] environment -variable points. This service must provide one endpoint: +variable points. Supports `http(s):` and `grpc(s)` URLs. In case of GRPC +protocol, the service must correspond to `./internal/backendpb/backend.proto`. +In case of HTTP protocol this service must provide one endpoint: `GET /dns_api/v1/settings`, it must respond with a `200 OK` response code and accept a JSON document in the following format: diff --git a/go.mod b/go.mod index 4a111e6..7e13da2 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.20 require ( github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0 - github.com/AdguardTeam/golibs v0.13.6 - github.com/AdguardTeam/urlfilter v0.16.2 + github.com/AdguardTeam/golibs v0.15.0 + github.com/AdguardTeam/urlfilter v0.17.0 github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc github.com/bluele/gcache v0.0.2 @@ -19,12 +19,13 @@ require ( 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/quic-go/quic-go v0.38.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/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 golang.org/x/time v0.3.0 + google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -40,19 +41,19 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/onsi/ginkgo/v2 v2.10.0 // indirect + github.com/onsi/ginkgo/v2 v2.11.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.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.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 + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b90aa1f..dfda8ac 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,7 @@ -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.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.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/AdguardTeam/golibs v0.15.0 h1:yOv/fdVkJIOWKr0NlUXAE9RA0DK9GKiBbiGzq47vY7o= +github.com/AdguardTeam/golibs v0.15.0/go.mod h1:66ZLs8P7nk/3IfKroQ1rqtieLk+5eXYXMBKXlVL7KeI= +github.com/AdguardTeam/urlfilter v0.17.0 h1:tUzhtR9wMx704GIP3cibsDQJrixlMHfwoQbYJfPdFow= +github.com/AdguardTeam/urlfilter v0.17.0/go.mod h1:bbuZjPUzm/Ip+nz5qPPbwIP+9rZyQbQad8Lt/0fCulU= github.com/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= @@ -27,7 +22,6 @@ github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -38,8 +32,7 @@ github.com/getsentry/sentry-go v0.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtV 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.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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 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= @@ -50,25 +43,20 @@ 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-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/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.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= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/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.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.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/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU= @@ -77,9 +65,9 @@ github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= @@ -90,47 +78,40 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi 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= -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/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= +github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= github.com/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/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 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.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/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.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/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-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.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -138,44 +119,37 @@ 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= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/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.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.work.sum b/go.work.sum index 1b6b6e5..4fd0ee0 100644 --- a/go.work.sum +++ b/go.work.sum @@ -119,7 +119,6 @@ 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= @@ -150,6 +149,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW 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= @@ -200,14 +200,11 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE= @@ -234,6 +231,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk= @@ -283,12 +281,9 @@ github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgS github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI= @@ -296,13 +291,18 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a 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/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/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.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= +github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/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= @@ -313,6 +313,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: 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/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.38.0/go.mod h1:MPCuRq7KBK2hNcfKj/1iD1BGuN3eAYMeNxp3T42LRUg= github.com/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= @@ -324,6 +326,8 @@ github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiy github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48 h1:vabduItPAIz9px5iryD5peyx7O3Ya8TBThapgXim98o= @@ -388,6 +392,7 @@ github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1 github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ= github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= @@ -429,10 +434,13 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -441,6 +449,9 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/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= @@ -456,6 +467,10 @@ golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su 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/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/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= @@ -481,9 +496,14 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc 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/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= @@ -491,15 +511,27 @@ 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/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= @@ -519,11 +551,15 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= @@ -536,6 +572,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/access/access.go b/internal/access/access.go new file mode 100644 index 0000000..c68829a --- /dev/null +++ b/internal/access/access.go @@ -0,0 +1,108 @@ +// Package access contains structures for access control management. +package access + +import ( + "fmt" + "net/netip" + "strings" + + "github.com/AdguardTeam/golibs/stringutil" + "github.com/AdguardTeam/urlfilter" + "github.com/AdguardTeam/urlfilter/filterlist" +) + +// unit is a convenient alias for struct{} +type unit = struct{} + +// Interface is the access manager interface. +type Interface interface { + // IsBlockedHost returns true if host should be blocked. + IsBlockedHost(host string, qt uint16) (blocked bool) + + // IsBlockedIP returns the status of the IP address blocking as well as the + // rule that blocked it. + IsBlockedIP(ip netip.Addr) (blocked bool, rule string) +} + +// type check +var _ Interface = (*Manager)(nil) + +// Manager controls IP and client blocking that takes place before all +// other processing. An Manager is safe for concurrent use. +type Manager struct { + blockedIPs map[netip.Addr]unit + blockedHostsEng *urlfilter.DNSEngine + blockedNets []netip.Prefix +} + +// New create an Manager. The parameters assumed to be valid. +func New(blockedDomains, blockedSubnets []string) (am *Manager, err error) { + am = &Manager{ + blockedIPs: map[netip.Addr]unit{}, + } + + processAccessList(blockedSubnets, am.blockedIPs, &am.blockedNets) + + b := &strings.Builder{} + for _, h := range blockedDomains { + stringutil.WriteToBuilder(b, strings.ToLower(h), "\n") + } + + lists := []filterlist.RuleList{ + &filterlist.StringRuleList{ + ID: 0, + RulesText: b.String(), + IgnoreCosmetic: true, + }, + } + + rulesStrg, err := filterlist.NewRuleStorage(lists) + if err != nil { + return nil, fmt.Errorf("adding blocked hosts: %w", err) + } + + am.blockedHostsEng = urlfilter.NewDNSEngine(rulesStrg) + + return am, nil +} + +// processAccessList is a helper for processing a list of strings, each of them +// assumed be a valid IP address or a valid CIDR. +func processAccessList(strs []string, ips map[netip.Addr]unit, nets *[]netip.Prefix) { + for _, s := range strs { + var err error + var ip netip.Addr + var ipnet netip.Prefix + if ip, err = netip.ParseAddr(s); err == nil { + ips[ip] = unit{} + } else if ipnet, err = netip.ParsePrefix(s); err == nil { + *nets = append(*nets, ipnet) + } + } +} + +// IsBlockedHost returns true if host should be blocked. +func (am *Manager) IsBlockedHost(host string, qt uint16) (blocked bool) { + _, blocked = am.blockedHostsEng.MatchRequest(&urlfilter.DNSRequest{ + Hostname: host, + DNSType: qt, + }) + + return blocked +} + +// IsBlockedIP returns the status of the IP address blocking as well as the rule +// that blocked it. +func (am *Manager) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) { + if _, ok := am.blockedIPs[ip]; ok { + return true, ip.String() + } + + for _, ipnet := range am.blockedNets { + if ipnet.Contains(ip) { + return true, ipnet.String() + } + } + + return false, "" +} diff --git a/internal/access/access_test.go b/internal/access/access_test.go new file mode 100644 index 0000000..fe4ccc6 --- /dev/null +++ b/internal/access/access_test.go @@ -0,0 +1,107 @@ +package access_test + +import ( + "net/netip" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/access" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAccessManager_IsBlockedHost(t *testing.T) { + am, err := access.New([]string{ + "block.test", + "UPPERCASE.test", + "||block_aaaa.test^$dnstype=AAAA", + }, []string{}) + require.NoError(t, err) + + testCases := []struct { + want assert.BoolAssertionFunc + name string + host string + qt uint16 + }{{ + want: assert.False, + name: "pass", + host: "pass.test", + qt: dns.TypeA, + }, { + want: assert.True, + name: "blocked_domain_A", + host: "block.test", + qt: dns.TypeA, + }, { + want: assert.True, + name: "blocked_domain_HTTPS", + host: "block.test", + qt: dns.TypeHTTPS, + }, { + want: assert.True, + name: "uppercase_domain", + host: "uppercase.test", + qt: dns.TypeHTTPS, + }, { + want: assert.False, + name: "pass_qt", + host: "block_aaaa.test", + qt: dns.TypeA, + }, { + want: assert.True, + name: "block_qt", + host: "block_aaaa.test", + qt: dns.TypeAAAA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + blocked := am.IsBlockedHost(tc.host, tc.qt) + tc.want(t, blocked) + }) + } +} + +func TestAccessManager_IsBlockedIP(t *testing.T) { + am, err := access.New([]string{}, []string{ + "1.1.1.1", + "2.2.2.0/8", + }) + require.NoError(t, err) + + testCases := []struct { + want assert.BoolAssertionFunc + ip netip.Addr + wantRule string + name string + }{{ + want: assert.False, + wantRule: "", + name: "pass", + ip: netip.MustParseAddr("1.1.1.0"), + }, { + want: assert.True, + wantRule: "1.1.1.1", + name: "block_ip", + ip: netip.MustParseAddr("1.1.1.1"), + }, { + want: assert.False, + wantRule: "", + name: "pass_subnet", + ip: netip.MustParseAddr("1.2.2.2"), + }, { + want: assert.True, + wantRule: "2.2.2.0/8", + name: "block_subnet", + ip: netip.MustParseAddr("2.2.2.2"), + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + blocked, rule := am.IsBlockedIP(tc.ip) + tc.want(t, blocked) + assert.Equal(t, tc.wantRule, rule) + }) + } +} diff --git a/internal/agd/country_generate.go b/internal/agd/country_generate.go index 553b8c1..a958bb4 100644 --- a/internal/agd/country_generate.go +++ b/internal/agd/country_generate.go @@ -6,6 +6,7 @@ import ( "encoding/csv" "net/http" "os" + "strings" "text/template" "time" @@ -40,10 +41,10 @@ func main() { // Skip the first row, as it is a header. rows = rows[1:] - // Sort by the code to make the output more predictable and easier to look - // through. - slices.SortFunc(rows, func(a, b []string) (less bool) { - return a[1] < b[1] + slices.SortFunc(rows, func(a, b []string) (res int) { + // Sort by the code to make the output more predictable and easier to + // look through. + return strings.Compare(a[1], b[1]) }) tmpl, err := template.New("main").Parse(tmplStr) diff --git a/internal/agdnet/agdnet.go b/internal/agdnet/agdnet.go index 49a339f..6430ae0 100644 --- a/internal/agdnet/agdnet.go +++ b/internal/agdnet/agdnet.go @@ -74,3 +74,10 @@ func ParseSubnets(strs ...string) (subnets []netip.Prefix, err error) { return subnets, nil } + +// NormalizeDomain returns lowercased version of the host without the final dot. +// +// TODO(a.garipov): Move to golibs. +func NormalizeDomain(fqdn string) (host string) { + return strings.ToLower(strings.TrimSuffix(fqdn, ".")) +} diff --git a/internal/agdnet/prefixaddr.go b/internal/agdnet/prefixaddr.go new file mode 100644 index 0000000..a7c0085 --- /dev/null +++ b/internal/agdnet/prefixaddr.go @@ -0,0 +1,19 @@ +package agdnet + +import ( + "fmt" + "net/netip" +) + +// FormatPrefixAddr returns either a simple IP:port address or one with the +// prefix length appended after a slash, depending on whether or not subnet is a +// single-address subnet. This is done to make using the IP:port part easier to +// split off using functions like [strings.Cut]. +func FormatPrefixAddr(subnet netip.Prefix, port uint16) (s string) { + addrPort := netip.AddrPortFrom(subnet.Addr(), port) + if subnet.IsSingleIP() { + return addrPort.String() + } + + return fmt.Sprintf("%s/%d", addrPort, subnet.Bits()) +} diff --git a/internal/agdnet/prefixaddr_example_test.go b/internal/agdnet/prefixaddr_example_test.go new file mode 100644 index 0000000..818667a --- /dev/null +++ b/internal/agdnet/prefixaddr_example_test.go @@ -0,0 +1,17 @@ +package agdnet_test + +import ( + "fmt" + "net/netip" + + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" +) + +func ExampeFormatPrefixAddr() { + fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.4/32"), 5678)) + fmt.Println(agdnet.FormatPrefixAddr(netip.MustParsePrefix("1.2.3.0/24"), 5678)) + + // Output: + // 1.2.3.4:5678 + // 1.2.3.0:5678/24 +} diff --git a/internal/agdnet/resolver.go b/internal/agdnet/resolver.go index 1c632c8..26fd161 100644 --- a/internal/agdnet/resolver.go +++ b/internal/agdnet/resolver.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "net" + "net/netip" "sync" "time" @@ -19,7 +20,14 @@ import ( // // See go doc net.Resolver. type Resolver interface { - LookupIP(ctx context.Context, fam netutil.AddrFamily, host string) (ips []net.IP, err error) + // LookupNetIP returns a slice of host's IP addresses of family specified by + // fam, which must be either [netutil.AddrFamilyIPv4] or + // [netutil.AddrFamilyIPv6]. + LookupNetIP( + ctx context.Context, + fam netutil.AddrFamily, + host string, + ) (ips []netip.Addr, err error) } // DefaultResolver uses [net.DefaultResolver] to resolve addresses. @@ -28,17 +36,17 @@ type DefaultResolver struct{} // type check var _ Resolver = DefaultResolver{} -// LookupIP implements the [Resolver] interface for DefaultResolver. -func (DefaultResolver) LookupIP( +// LookupNetIP implements the [Resolver] interface for DefaultResolver. +func (DefaultResolver) LookupNetIP( ctx context.Context, fam netutil.AddrFamily, host string, -) (ips []net.IP, err error) { +) (ips []netip.Addr, err error) { switch fam { case netutil.AddrFamilyIPv4: - return net.DefaultResolver.LookupIP(ctx, "ip4", host) + return net.DefaultResolver.LookupNetIP(ctx, "ip4", host) case netutil.AddrFamilyIPv6: - return net.DefaultResolver.LookupIP(ctx, "ip6", host) + return net.DefaultResolver.LookupNetIP(ctx, "ip6", host) default: return nil, net.UnknownNetworkError(fam.String()) } @@ -50,7 +58,7 @@ type resolveCache map[string]*resolveCacheItem // resolveCacheItem is an item of [resolveCache]. type resolveCacheItem struct { refrTime time.Time - ips []net.IP + ips []netip.Addr } // CachingResolver caches resolved results for hosts for a certain time, @@ -83,13 +91,13 @@ func NewCachingResolver(resolver Resolver, ttl time.Duration) (c *CachingResolve // type check var _ Resolver = (*CachingResolver)(nil) -// LookupIP implements the [Resolver] interface for *CachingResolver. host +// LookupNetIP implements the [Resolver] interface for *CachingResolver. host // should be normalized. Slice ips and its elements must not be mutated. -func (c *CachingResolver) LookupIP( +func (c *CachingResolver) LookupNetIP( ctx context.Context, fam netutil.AddrFamily, host string, -) (ips []net.IP, err error) { +) (ips []netip.Addr, err error) { c.mu.Lock() defer c.mu.Unlock() @@ -119,20 +127,20 @@ func (c *CachingResolver) resolve( fam netutil.AddrFamily, host string, ) (item *resolveCacheItem, err error) { - var ips []net.IP + var ips []netip.Addr refrTime := time.Now() // Don't resolve IP addresses. ip := ipFromHost(host, fam) - if ip != nil { - ips = []net.IP{ip} + if ip != (netip.Addr{}) { + ips = []netip.Addr{ip} // Set the refresh time to the maximum date that time.Duration allows to // prevent this item from refreshing. refrTime = time.Unix(0, math.MaxInt64) } else { - ips, err = c.resolver.LookupIP(ctx, fam, host) + ips, err = c.resolver.LookupNetIP(ctx, fam, host) if err != nil { if !isExpectedLookupError(fam, err) { return nil, fmt.Errorf("resolving %s addr for %q: %w", fam, host, err) @@ -159,22 +167,25 @@ 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 +// ipFromHost parses host as if it'd be an IP address of specified fam. It +// returns an empty netip. +func ipFromHost(host string, fam netutil.AddrFamily) (ip netip.Addr) { + var famFunc func(netip.Addr) (ok bool) + switch fam { + case netutil.AddrFamilyIPv4: + famFunc = netip.Addr.Is4 + case netutil.AddrFamilyIPv6: + famFunc = netip.Addr.Is6 + default: + return netip.Addr{} } - ip4 := ip.To4() - if fam == netutil.AddrFamilyIPv4 && ip4 != nil { - return ip4 - } else if fam == netutil.AddrFamilyIPv6 && ip4 == nil { - return ip + ip, err := netip.ParseAddr(host) + if err != nil || !famFunc(ip) { + return netip.Addr{} } - return nil + return ip } // isExpectedLookupError returns true if the error is an expected lookup error. diff --git a/internal/agdnet/resolver_test.go b/internal/agdnet/resolver_test.go index d23102f..cb4caf2 100644 --- a/internal/agdnet/resolver_test.go +++ b/internal/agdnet/resolver_test.go @@ -2,7 +2,7 @@ package agdnet_test import ( "context" - "net" + "net/netip" "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" @@ -17,14 +17,14 @@ func TestCachingResolver_Resolve(t *testing.T) { const testHost = "addr.example" var numLookups uint64 - wantIPv4 := []net.IP{{1, 2, 3, 4}} - wantIPv6 := []net.IP{net.ParseIP("1234::5678")} + wantIPv4 := []netip.Addr{netip.MustParseAddr("1.2.3.4")} + wantIPv6 := []netip.Addr{netip.MustParseAddr("1234::5678")} r := &agdtest.Resolver{ - OnLookupIP: func( - _ context.Context, + OnLookupNetIP: func( + ctx context.Context, fam netutil.AddrFamily, - _ string, - ) (ips []net.IP, err error) { + host string, + ) (ips []netip.Addr, err error) { numLookups++ if fam == netutil.AddrFamilyIPv4 { @@ -40,7 +40,7 @@ func TestCachingResolver_Resolve(t *testing.T) { testCases := []struct { name string host string - wantIPs []net.IP + wantIPs []netip.Addr wantNum uint64 fam netutil.AddrFamily }{{ @@ -78,7 +78,7 @@ func TestCachingResolver_Resolve(t *testing.T) { ctx := context.Background() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got, err := cached.LookupIP(ctx, tc.fam, tc.host) + got, err := cached.LookupNetIP(ctx, tc.fam, tc.host) require.NoError(t, err) assert.Equal(t, tc.wantNum, numLookups) diff --git a/internal/agdprotobuf/pbutil.go b/internal/agdprotobuf/pbutil.go new file mode 100644 index 0000000..ba0386a --- /dev/null +++ b/internal/agdprotobuf/pbutil.go @@ -0,0 +1,27 @@ +// Package agdprotobuf contains protobuf utils. +package agdprotobuf + +import ( + "fmt" + "net/netip" +) + +// ByteSlicesToIPs converts a slice of byte slices into a slice of netip.Addrs. +func ByteSlicesToIPs(data [][]byte) (ips []netip.Addr, err error) { + if data == nil { + return nil, nil + } + + ips = make([]netip.Addr, 0, len(data)) + for i, ipData := range data { + var ip netip.Addr + err = ip.UnmarshalBinary(ipData) + if err != nil { + return nil, fmt.Errorf("ip at index %d: %w", i, err) + } + + ips = append(ips, ip) + } + + return ips, nil +} diff --git a/internal/agdsync/agdsync.go b/internal/agdsync/agdsync.go new file mode 100644 index 0000000..938b3d3 --- /dev/null +++ b/internal/agdsync/agdsync.go @@ -0,0 +1,37 @@ +// Package agdsync contains extensions and utilities for package sync from the +// standard library. +// +// TODO(a.garipov): Move to module golibs. +package agdsync + +import "sync" + +// TypedPool is the strongly typed version of [sync.Pool] that manages pointers +// to T. +type TypedPool[T any] struct { + pool *sync.Pool +} + +// NewTypedPool returns a new strongly typed pool. newFunc must not be nil. +func NewTypedPool[T any](newFunc func() (v *T)) (p *TypedPool[T]) { + return &TypedPool[T]{ + pool: &sync.Pool{ + New: func() (v any) { return newFunc() }, + }, + } +} + +// Get selects an arbitrary item from the pool, removes it from the pool, and +// returns it to the caller. +// +// See [sync.Pool.Get]. +func (p *TypedPool[T]) Get() (v *T) { + return p.pool.Get().(*T) +} + +// Put adds v to the pool. +// +// See [sync.Pool.Put]. +func (p *TypedPool[T]) Put(v *T) { + p.pool.Put(v) +} diff --git a/internal/agdtest/interface.go b/internal/agdtest/interface.go index bc1eb56..df69850 100644 --- a/internal/agdtest/interface.go +++ b/internal/agdtest/interface.go @@ -6,6 +6,7 @@ import ( "net/netip" "time" + "github.com/AdguardTeam/AdGuardDNS/internal/access" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" @@ -56,6 +57,27 @@ func (r *Refresher) Refresh(ctx context.Context) (err error) { return r.OnRefresh(ctx) } +// Package access + +// type check +var _ access.Interface = (*AccessManager)(nil) + +// AccessManager is a [access.Interface] for tests. +type AccessManager struct { + OnIsBlockedHost func(host string, qt uint16) (blocked bool) + OnIsBlockedIP func(ip netip.Addr) (blocked bool, rule string) +} + +// IsBlockedHost implements the [access.Interface] interface for *AccessManager. +func (a *AccessManager) IsBlockedHost(host string, qt uint16) (blocked bool) { + return a.OnIsBlockedHost(host, qt) +} + +// IsBlockedIP implements the [access.Interface] interface for *AccessManager. +func (a *AccessManager) IsBlockedIP(ip netip.Addr) (blocked bool, rule string) { + return a.OnIsBlockedIP(ip) +} + // Package agdnet // type check @@ -63,20 +85,20 @@ var _ agdnet.Resolver = (*Resolver)(nil) // Resolver is an agd.Resolver for tests. type Resolver struct { - OnLookupIP func( + OnLookupNetIP func( ctx context.Context, fam netutil.AddrFamily, host string, - ) (ips []net.IP, err error) + ) (ips []netip.Addr, err error) } -// LookupIP implements the agd.Resolver interface for *Resolver. -func (r *Resolver) LookupIP( +// LookupNetIP implements the [agd.Resolver] interface for *Resolver. +func (r *Resolver) LookupNetIP( ctx context.Context, fam netutil.AddrFamily, host string, -) (ips []net.IP, err error) { - return r.OnLookupIP(ctx, fam, host) +) (ips []netip.Addr, err error) { + return r.OnLookupNetIP(ctx, fam, host) } // Package billstat diff --git a/internal/backendpb/backend.pb.go b/internal/backendpb/backend.pb.go new file mode 100644 index 0000000..33aaf0a --- /dev/null +++ b/internal/backendpb/backend.pb.go @@ -0,0 +1,1464 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.23.4 +// source: backend.proto + +package backendpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DNSProfilesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SyncTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=sync_time,json=syncTime,proto3" json:"sync_time,omitempty"` +} + +func (x *DNSProfilesRequest) Reset() { + *x = DNSProfilesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DNSProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DNSProfilesRequest) ProtoMessage() {} + +func (x *DNSProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[0] + 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 DNSProfilesRequest.ProtoReflect.Descriptor instead. +func (*DNSProfilesRequest) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{0} +} + +func (x *DNSProfilesRequest) GetSyncTime() *timestamppb.Timestamp { + if x != nil { + return x.SyncTime + } + return nil +} + +type DNSProfile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"` + FilteringEnabled bool `protobuf:"varint,2,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"` + QueryLogEnabled bool `protobuf:"varint,3,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"` + Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"` + SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,5,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"` + Parental *ParentalSettings `protobuf:"bytes,6,opt,name=parental,proto3" json:"parental,omitempty"` + RuleLists *RuleListsSettings `protobuf:"bytes,7,opt,name=rule_lists,json=ruleLists,proto3" json:"rule_lists,omitempty"` + Devices []*DeviceSettings `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"` + CustomRules []string `protobuf:"bytes,9,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"` + FilteredResponseTtl *durationpb.Duration `protobuf:"bytes,10,opt,name=filtered_response_ttl,json=filteredResponseTtl,proto3" json:"filtered_response_ttl,omitempty"` + BlockPrivateRelay bool `protobuf:"varint,11,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"` + BlockFirefoxCanary bool `protobuf:"varint,12,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"` + // Types that are assignable to BlockingMode: + // + // *DNSProfile_BlockingModeCustomIp + // *DNSProfile_BlockingModeNxdomain + // *DNSProfile_BlockingModeNullIp + // *DNSProfile_BlockingModeRefused + BlockingMode isDNSProfile_BlockingMode `protobuf_oneof:"blocking_mode"` +} + +func (x *DNSProfile) Reset() { + *x = DNSProfile{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DNSProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DNSProfile) ProtoMessage() {} + +func (x *DNSProfile) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[1] + 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 DNSProfile.ProtoReflect.Descriptor instead. +func (*DNSProfile) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{1} +} + +func (x *DNSProfile) GetDnsId() string { + if x != nil { + return x.DnsId + } + return "" +} + +func (x *DNSProfile) GetFilteringEnabled() bool { + if x != nil { + return x.FilteringEnabled + } + return false +} + +func (x *DNSProfile) GetQueryLogEnabled() bool { + if x != nil { + return x.QueryLogEnabled + } + return false +} + +func (x *DNSProfile) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +func (x *DNSProfile) GetSafeBrowsing() *SafeBrowsingSettings { + if x != nil { + return x.SafeBrowsing + } + return nil +} + +func (x *DNSProfile) GetParental() *ParentalSettings { + if x != nil { + return x.Parental + } + return nil +} + +func (x *DNSProfile) GetRuleLists() *RuleListsSettings { + if x != nil { + return x.RuleLists + } + return nil +} + +func (x *DNSProfile) GetDevices() []*DeviceSettings { + if x != nil { + return x.Devices + } + return nil +} + +func (x *DNSProfile) GetCustomRules() []string { + if x != nil { + return x.CustomRules + } + return nil +} + +func (x *DNSProfile) GetFilteredResponseTtl() *durationpb.Duration { + if x != nil { + return x.FilteredResponseTtl + } + return nil +} + +func (x *DNSProfile) GetBlockPrivateRelay() bool { + if x != nil { + return x.BlockPrivateRelay + } + return false +} + +func (x *DNSProfile) GetBlockFirefoxCanary() bool { + if x != nil { + return x.BlockFirefoxCanary + } + return false +} + +func (m *DNSProfile) GetBlockingMode() isDNSProfile_BlockingMode { + if m != nil { + return m.BlockingMode + } + return nil +} + +func (x *DNSProfile) GetBlockingModeCustomIp() *BlockingModeCustomIP { + if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeCustomIp); ok { + return x.BlockingModeCustomIp + } + return nil +} + +func (x *DNSProfile) GetBlockingModeNxdomain() *BlockingModeNXDOMAIN { + if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeNxdomain); ok { + return x.BlockingModeNxdomain + } + return nil +} + +func (x *DNSProfile) GetBlockingModeNullIp() *BlockingModeNullIP { + if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeNullIp); ok { + return x.BlockingModeNullIp + } + return nil +} + +func (x *DNSProfile) GetBlockingModeRefused() *BlockingModeREFUSED { + if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeRefused); ok { + return x.BlockingModeRefused + } + return nil +} + +type isDNSProfile_BlockingMode interface { + isDNSProfile_BlockingMode() +} + +type DNSProfile_BlockingModeCustomIp struct { + BlockingModeCustomIp *BlockingModeCustomIP `protobuf:"bytes,13,opt,name=blocking_mode_custom_ip,json=blockingModeCustomIp,proto3,oneof"` +} + +type DNSProfile_BlockingModeNxdomain struct { + BlockingModeNxdomain *BlockingModeNXDOMAIN `protobuf:"bytes,14,opt,name=blocking_mode_nxdomain,json=blockingModeNxdomain,proto3,oneof"` +} + +type DNSProfile_BlockingModeNullIp struct { + BlockingModeNullIp *BlockingModeNullIP `protobuf:"bytes,15,opt,name=blocking_mode_null_ip,json=blockingModeNullIp,proto3,oneof"` +} + +type DNSProfile_BlockingModeRefused struct { + BlockingModeRefused *BlockingModeREFUSED `protobuf:"bytes,16,opt,name=blocking_mode_refused,json=blockingModeRefused,proto3,oneof"` +} + +func (*DNSProfile_BlockingModeCustomIp) isDNSProfile_BlockingMode() {} + +func (*DNSProfile_BlockingModeNxdomain) isDNSProfile_BlockingMode() {} + +func (*DNSProfile_BlockingModeNullIp) isDNSProfile_BlockingMode() {} + +func (*DNSProfile_BlockingModeRefused) isDNSProfile_BlockingMode() {} + +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"` + BlockNrd bool `protobuf:"varint,3,opt,name=block_nrd,json=blockNrd,proto3" json:"block_nrd,omitempty"` +} + +func (x *SafeBrowsingSettings) Reset() { + *x = SafeBrowsingSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[2] + 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_backend_proto_msgTypes[2] + 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_backend_proto_rawDescGZIP(), []int{2} +} + +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) GetBlockNrd() bool { + if x != nil { + return x.BlockNrd + } + return false +} + +type DeviceSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + FilteringEnabled bool `protobuf:"varint,3,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"` + LinkedIp []byte `protobuf:"bytes,4,opt,name=linked_ip,json=linkedIp,proto3" json:"linked_ip,omitempty"` + DedicatedIps [][]byte `protobuf:"bytes,5,rep,name=dedicated_ips,json=dedicatedIps,proto3" json:"dedicated_ips,omitempty"` +} + +func (x *DeviceSettings) Reset() { + *x = DeviceSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceSettings) ProtoMessage() {} + +func (x *DeviceSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_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 DeviceSettings.ProtoReflect.Descriptor instead. +func (*DeviceSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{3} +} + +func (x *DeviceSettings) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DeviceSettings) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeviceSettings) GetFilteringEnabled() bool { + if x != nil { + return x.FilteringEnabled + } + return false +} + +func (x *DeviceSettings) GetLinkedIp() []byte { + if x != nil { + return x.LinkedIp + } + return nil +} + +func (x *DeviceSettings) GetDedicatedIps() [][]byte { + if x != nil { + return x.DedicatedIps + } + return nil +} + +type ParentalSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + BlockAdult bool `protobuf:"varint,2,opt,name=block_adult,json=blockAdult,proto3" json:"block_adult,omitempty"` + GeneralSafeSearch bool `protobuf:"varint,3,opt,name=general_safe_search,json=generalSafeSearch,proto3" json:"general_safe_search,omitempty"` + YoutubeSafeSearch bool `protobuf:"varint,4,opt,name=youtube_safe_search,json=youtubeSafeSearch,proto3" json:"youtube_safe_search,omitempty"` + BlockedServices []string `protobuf:"bytes,5,rep,name=blocked_services,json=blockedServices,proto3" json:"blocked_services,omitempty"` + Schedule *ScheduleSettings `protobuf:"bytes,6,opt,name=schedule,proto3" json:"schedule,omitempty"` +} + +func (x *ParentalSettings) Reset() { + *x = ParentalSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ParentalSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParentalSettings) ProtoMessage() {} + +func (x *ParentalSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[4] + 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 ParentalSettings.ProtoReflect.Descriptor instead. +func (*ParentalSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{4} +} + +func (x *ParentalSettings) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *ParentalSettings) GetBlockAdult() bool { + if x != nil { + return x.BlockAdult + } + return false +} + +func (x *ParentalSettings) GetGeneralSafeSearch() bool { + if x != nil { + return x.GeneralSafeSearch + } + return false +} + +func (x *ParentalSettings) GetYoutubeSafeSearch() bool { + if x != nil { + return x.YoutubeSafeSearch + } + return false +} + +func (x *ParentalSettings) GetBlockedServices() []string { + if x != nil { + return x.BlockedServices + } + return nil +} + +func (x *ParentalSettings) GetSchedule() *ScheduleSettings { + if x != nil { + return x.Schedule + } + return nil +} + +type ScheduleSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tmz string `protobuf:"bytes,1,opt,name=tmz,proto3" json:"tmz,omitempty"` + WeeklyRange *WeeklyRange `protobuf:"bytes,2,opt,name=weeklyRange,proto3" json:"weeklyRange,omitempty"` +} + +func (x *ScheduleSettings) Reset() { + *x = ScheduleSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScheduleSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScheduleSettings) ProtoMessage() {} + +func (x *ScheduleSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[5] + 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 ScheduleSettings.ProtoReflect.Descriptor instead. +func (*ScheduleSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{5} +} + +func (x *ScheduleSettings) GetTmz() string { + if x != nil { + return x.Tmz + } + return "" +} + +func (x *ScheduleSettings) GetWeeklyRange() *WeeklyRange { + if x != nil { + return x.WeeklyRange + } + return nil +} + +type WeeklyRange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mon *DayRange `protobuf:"bytes,1,opt,name=mon,proto3" json:"mon,omitempty"` + Tue *DayRange `protobuf:"bytes,2,opt,name=tue,proto3" json:"tue,omitempty"` + Wed *DayRange `protobuf:"bytes,3,opt,name=wed,proto3" json:"wed,omitempty"` + Thu *DayRange `protobuf:"bytes,4,opt,name=thu,proto3" json:"thu,omitempty"` + Fri *DayRange `protobuf:"bytes,5,opt,name=fri,proto3" json:"fri,omitempty"` + Sat *DayRange `protobuf:"bytes,6,opt,name=sat,proto3" json:"sat,omitempty"` + Sun *DayRange `protobuf:"bytes,7,opt,name=sun,proto3" json:"sun,omitempty"` +} + +func (x *WeeklyRange) Reset() { + *x = WeeklyRange{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WeeklyRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WeeklyRange) ProtoMessage() {} + +func (x *WeeklyRange) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[6] + 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 WeeklyRange.ProtoReflect.Descriptor instead. +func (*WeeklyRange) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{6} +} + +func (x *WeeklyRange) GetMon() *DayRange { + if x != nil { + return x.Mon + } + return nil +} + +func (x *WeeklyRange) GetTue() *DayRange { + if x != nil { + return x.Tue + } + return nil +} + +func (x *WeeklyRange) GetWed() *DayRange { + if x != nil { + return x.Wed + } + return nil +} + +func (x *WeeklyRange) GetThu() *DayRange { + if x != nil { + return x.Thu + } + return nil +} + +func (x *WeeklyRange) GetFri() *DayRange { + if x != nil { + return x.Fri + } + return nil +} + +func (x *WeeklyRange) GetSat() *DayRange { + if x != nil { + return x.Sat + } + return nil +} + +func (x *WeeklyRange) GetSun() *DayRange { + if x != nil { + return x.Sun + } + return nil +} + +type DayRange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Start *durationpb.Duration `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` + End *durationpb.Duration `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` +} + +func (x *DayRange) Reset() { + *x = DayRange{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DayRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DayRange) ProtoMessage() {} + +func (x *DayRange) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[7] + 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 DayRange.ProtoReflect.Descriptor instead. +func (*DayRange) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{7} +} + +func (x *DayRange) GetStart() *durationpb.Duration { + if x != nil { + return x.Start + } + return nil +} + +func (x *DayRange) GetEnd() *durationpb.Duration { + if x != nil { + return x.End + } + return nil +} + +type RuleListsSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Ids []string `protobuf:"bytes,2,rep,name=ids,proto3" json:"ids,omitempty"` +} + +func (x *RuleListsSettings) Reset() { + *x = RuleListsSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuleListsSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuleListsSettings) ProtoMessage() {} + +func (x *RuleListsSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[8] + 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 RuleListsSettings.ProtoReflect.Descriptor instead. +func (*RuleListsSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{8} +} + +func (x *RuleListsSettings) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *RuleListsSettings) GetIds() []string { + if x != nil { + return x.Ids + } + return nil +} + +type BlockingModeCustomIP struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ipv4 []byte `protobuf:"bytes,1,opt,name=ipv4,proto3" json:"ipv4,omitempty"` + Ipv6 []byte `protobuf:"bytes,2,opt,name=ipv6,proto3" json:"ipv6,omitempty"` +} + +func (x *BlockingModeCustomIP) Reset() { + *x = BlockingModeCustomIP{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockingModeCustomIP) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockingModeCustomIP) ProtoMessage() {} + +func (x *BlockingModeCustomIP) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[9] + 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 BlockingModeCustomIP.ProtoReflect.Descriptor instead. +func (*BlockingModeCustomIP) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{9} +} + +func (x *BlockingModeCustomIP) GetIpv4() []byte { + if x != nil { + return x.Ipv4 + } + return nil +} + +func (x *BlockingModeCustomIP) GetIpv6() []byte { + if x != nil { + return x.Ipv6 + } + return nil +} + +type BlockingModeNXDOMAIN struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *BlockingModeNXDOMAIN) Reset() { + *x = BlockingModeNXDOMAIN{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockingModeNXDOMAIN) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockingModeNXDOMAIN) ProtoMessage() {} + +func (x *BlockingModeNXDOMAIN) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[10] + 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 BlockingModeNXDOMAIN.ProtoReflect.Descriptor instead. +func (*BlockingModeNXDOMAIN) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{10} +} + +type BlockingModeNullIP struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *BlockingModeNullIP) Reset() { + *x = BlockingModeNullIP{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockingModeNullIP) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockingModeNullIP) ProtoMessage() {} + +func (x *BlockingModeNullIP) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockingModeNullIP.ProtoReflect.Descriptor instead. +func (*BlockingModeNullIP) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{11} +} + +type BlockingModeREFUSED struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *BlockingModeREFUSED) Reset() { + *x = BlockingModeREFUSED{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockingModeREFUSED) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockingModeREFUSED) ProtoMessage() {} + +func (x *BlockingModeREFUSED) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockingModeREFUSED.ProtoReflect.Descriptor instead. +func (*BlockingModeREFUSED) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{12} +} + +type DeviceBillingStat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LastActivityTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=last_activity_time,json=lastActivityTime,proto3" json:"last_activity_time,omitempty"` + DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + ClientCountry string `protobuf:"bytes,3,opt,name=client_country,json=clientCountry,proto3" json:"client_country,omitempty"` + // Protocol type. Possible values see here: https://bit.adguard.com/projects/DNS/repos/dns-server/browse#ql-properties + Proto uint32 `protobuf:"varint,4,opt,name=proto,proto3" json:"proto,omitempty"` + Asn uint32 `protobuf:"varint,5,opt,name=asn,proto3" json:"asn,omitempty"` + Queries uint32 `protobuf:"varint,6,opt,name=queries,proto3" json:"queries,omitempty"` +} + +func (x *DeviceBillingStat) Reset() { + *x = DeviceBillingStat{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceBillingStat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceBillingStat) ProtoMessage() {} + +func (x *DeviceBillingStat) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[13] + 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 DeviceBillingStat.ProtoReflect.Descriptor instead. +func (*DeviceBillingStat) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{13} +} + +func (x *DeviceBillingStat) GetLastActivityTime() *timestamppb.Timestamp { + if x != nil { + return x.LastActivityTime + } + return nil +} + +func (x *DeviceBillingStat) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *DeviceBillingStat) GetClientCountry() string { + if x != nil { + return x.ClientCountry + } + return "" +} + +func (x *DeviceBillingStat) GetProto() uint32 { + if x != nil { + return x.Proto + } + return 0 +} + +func (x *DeviceBillingStat) GetAsn() uint32 { + if x != nil { + return x.Asn + } + return 0 +} + +func (x *DeviceBillingStat) GetQueries() uint32 { + if x != nil { + return x.Queries + } + return 0 +} + +var File_backend_proto protoreflect.FileDescriptor + +var file_backend_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4d, 0x0a, + 0x12, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xf9, 0x06, 0x0a, + 0x0a, 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x64, + 0x6e, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x6e, 0x73, + 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5f, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 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, 0x03, 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, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0d, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x62, 0x72, + 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 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, 0x12, 0x2d, 0x0a, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x61, 0x6c, + 0x12, 0x31, 0x0a, 0x0a, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x73, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x09, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x75, 0x6c, 0x65, + 0x73, 0x12, 0x4d, 0x0a, 0x15, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x74, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x74, 0x6c, + 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0b, 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, 0x0c, 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, 0x4e, 0x0a, 0x17, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x69, 0x70, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, + 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x49, 0x50, 0x48, 0x00, 0x52, 0x14, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x49, 0x70, 0x12, 0x4d, 0x0a, 0x16, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x78, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, + 0x65, 0x4e, 0x58, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e, 0x48, 0x00, 0x52, 0x14, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x78, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x12, 0x48, 0x0a, 0x15, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x5f, 0x69, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, + 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x48, 0x00, 0x52, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, + 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x70, 0x12, 0x4a, 0x0a, 0x15, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x5f, 0x72, 0x65, 0x66, + 0x75, 0x73, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, 0x44, + 0x48, 0x00, 0x52, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, + 0x52, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x61, 0x66, + 0x65, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x5f, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x6f, 0x75, 0x73, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x72, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x72, 0x64, + 0x22, 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x5f, 0x69, + 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, + 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, + 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x49, 0x70, 0x73, 0x22, 0x87, 0x02, 0x0a, 0x10, 0x50, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x61, 0x6c, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x61, + 0x64, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x41, 0x64, 0x75, 0x6c, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x6c, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x53, 0x61, 0x66, 0x65, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, + 0x65, 0x5f, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x11, 0x79, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x53, 0x61, 0x66, 0x65, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, + 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x12, 0x2d, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x22, 0x54, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6d, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x74, 0x6d, 0x7a, 0x12, 0x2e, 0x0a, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x57, 0x65, + 0x65, 0x6b, 0x6c, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x77, 0x65, 0x65, 0x6b, 0x6c, + 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x0b, 0x57, 0x65, 0x65, 0x6b, 0x6c, + 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x6d, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, + 0x6d, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x03, 0x74, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x75, 0x65, + 0x12, 0x1b, 0x0a, 0x03, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, + 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x77, 0x65, 0x64, 0x12, 0x1b, 0x0a, + 0x03, 0x74, 0x68, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x74, 0x68, 0x75, 0x12, 0x1b, 0x0a, 0x03, 0x66, 0x72, + 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x03, 0x66, 0x72, 0x69, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x03, 0x73, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x03, 0x73, 0x75, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x09, 0x2e, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x03, 0x73, 0x75, + 0x6e, 0x22, 0x68, 0x0a, 0x08, 0x44, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2f, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2b, + 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0x3f, 0x0a, 0x11, 0x52, + 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x3e, 0x0a, 0x14, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x22, 0x16, 0x0a, 0x14, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x58, 0x44, 0x4f, + 0x4d, 0x41, 0x49, 0x4e, 0x22, 0x14, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, + 0x4d, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6c, 0x6c, 0x49, 0x50, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x45, 0x46, 0x55, 0x53, 0x45, + 0x44, 0x22, 0xe3, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x69, 0x6c, 0x6c, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x10, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, + 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x61, + 0x73, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x61, 0x73, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x32, 0x8a, 0x01, 0x0a, 0x0a, 0x44, 0x4e, 0x53, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x67, 0x65, 0x74, 0x44, 0x4e, 0x53, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x13, 0x2e, 0x44, 0x4e, 0x53, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, + 0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, + 0x73, 0x61, 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x42, 0x69, 0x6c, 0x6c, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, + 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x28, 0x01, 0x42, 0x4a, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x67, 0x75, + 0x61, 0x72, 0x64, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x64, 0x6e, 0x73, 0x2e, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x10, 0x44, 0x4e, 0x53, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x0b, 0x2e, + 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x44, 0x4e, 0x53, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backend_proto_rawDescOnce sync.Once + file_backend_proto_rawDescData = file_backend_proto_rawDesc +) + +func file_backend_proto_rawDescGZIP() []byte { + file_backend_proto_rawDescOnce.Do(func() { + file_backend_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_proto_rawDescData) + }) + return file_backend_proto_rawDescData +} + +var file_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_backend_proto_goTypes = []interface{}{ + (*DNSProfilesRequest)(nil), // 0: DNSProfilesRequest + (*DNSProfile)(nil), // 1: DNSProfile + (*SafeBrowsingSettings)(nil), // 2: SafeBrowsingSettings + (*DeviceSettings)(nil), // 3: DeviceSettings + (*ParentalSettings)(nil), // 4: ParentalSettings + (*ScheduleSettings)(nil), // 5: ScheduleSettings + (*WeeklyRange)(nil), // 6: WeeklyRange + (*DayRange)(nil), // 7: DayRange + (*RuleListsSettings)(nil), // 8: RuleListsSettings + (*BlockingModeCustomIP)(nil), // 9: BlockingModeCustomIP + (*BlockingModeNXDOMAIN)(nil), // 10: BlockingModeNXDOMAIN + (*BlockingModeNullIP)(nil), // 11: BlockingModeNullIP + (*BlockingModeREFUSED)(nil), // 12: BlockingModeREFUSED + (*DeviceBillingStat)(nil), // 13: DeviceBillingStat + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 15: google.protobuf.Duration + (*emptypb.Empty)(nil), // 16: google.protobuf.Empty +} +var file_backend_proto_depIdxs = []int32{ + 14, // 0: DNSProfilesRequest.sync_time:type_name -> google.protobuf.Timestamp + 2, // 1: DNSProfile.safe_browsing:type_name -> SafeBrowsingSettings + 4, // 2: DNSProfile.parental:type_name -> ParentalSettings + 8, // 3: DNSProfile.rule_lists:type_name -> RuleListsSettings + 3, // 4: DNSProfile.devices:type_name -> DeviceSettings + 15, // 5: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration + 9, // 6: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP + 10, // 7: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN + 11, // 8: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP + 12, // 9: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED + 5, // 10: ParentalSettings.schedule:type_name -> ScheduleSettings + 6, // 11: ScheduleSettings.weeklyRange:type_name -> WeeklyRange + 7, // 12: WeeklyRange.mon:type_name -> DayRange + 7, // 13: WeeklyRange.tue:type_name -> DayRange + 7, // 14: WeeklyRange.wed:type_name -> DayRange + 7, // 15: WeeklyRange.thu:type_name -> DayRange + 7, // 16: WeeklyRange.fri:type_name -> DayRange + 7, // 17: WeeklyRange.sat:type_name -> DayRange + 7, // 18: WeeklyRange.sun:type_name -> DayRange + 15, // 19: DayRange.start:type_name -> google.protobuf.Duration + 15, // 20: DayRange.end:type_name -> google.protobuf.Duration + 14, // 21: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp + 0, // 22: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest + 13, // 23: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat + 1, // 24: DNSService.getDNSProfiles:output_type -> DNSProfile + 16, // 25: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty + 24, // [24:26] is the sub-list for method output_type + 22, // [22:24] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name +} + +func init() { file_backend_proto_init() } +func file_backend_proto_init() { + if File_backend_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backend_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DNSProfilesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DNSProfile); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SafeBrowsingSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ParentalSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScheduleSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WeeklyRange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DayRange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuleListsSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockingModeCustomIP); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockingModeNXDOMAIN); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockingModeNullIP); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[12].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_backend_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceBillingStat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_backend_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*DNSProfile_BlockingModeCustomIp)(nil), + (*DNSProfile_BlockingModeNxdomain)(nil), + (*DNSProfile_BlockingModeNullIp)(nil), + (*DNSProfile_BlockingModeRefused)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backend_proto_rawDesc, + NumEnums: 0, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_backend_proto_goTypes, + DependencyIndexes: file_backend_proto_depIdxs, + MessageInfos: file_backend_proto_msgTypes, + }.Build() + File_backend_proto = out.File + file_backend_proto_rawDesc = nil + file_backend_proto_goTypes = nil + file_backend_proto_depIdxs = nil +} diff --git a/internal/backendpb/backend.proto b/internal/backendpb/backend.proto new file mode 100644 index 0000000..9194342 --- /dev/null +++ b/internal/backendpb/backend.proto @@ -0,0 +1,124 @@ +syntax = "proto3"; + +option go_package = "./backendpb"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/empty.proto"; + +option java_multiple_files = true; +option java_package = "com.adguard.backend.dns.generated"; +option java_outer_classname = "DNSProfilesProto"; +option objc_class_prefix = "DNS"; + +service DNSService { + + /* + Gets DNS profiles. + + Field "sync_time" in DNSProfilesRequest - pass to return the latest updates after this time moment. + + The trailers headers will include a "sync_time", given in milliseconds, + that should be used for subsequent incremental DNS profile synchronization requests. + */ + rpc getDNSProfiles(DNSProfilesRequest) returns (stream DNSProfile); + + /* + Stores devices activity. + */ + rpc saveDevicesBillingStat(stream DeviceBillingStat) returns (google.protobuf.Empty); +} + +message DNSProfilesRequest { + google.protobuf.Timestamp sync_time = 1; +} + +message DNSProfile { + string dns_id = 1; + bool filtering_enabled = 2; + bool query_log_enabled = 3; + bool deleted = 4; + SafeBrowsingSettings safe_browsing = 5; + ParentalSettings parental = 6; + RuleListsSettings rule_lists = 7; + repeated DeviceSettings devices = 8; + repeated string custom_rules = 9; + google.protobuf.Duration filtered_response_ttl = 10; + bool block_private_relay = 11; + bool block_firefox_canary = 12; + oneof blocking_mode { + BlockingModeCustomIP blocking_mode_custom_ip = 13; + BlockingModeNXDOMAIN blocking_mode_nxdomain = 14; + BlockingModeNullIP blocking_mode_null_ip = 15; + BlockingModeREFUSED blocking_mode_refused = 16; + } +} + +message SafeBrowsingSettings { + bool enabled = 1; + bool block_dangerous_domains = 2; + bool block_nrd = 3; +} + +message DeviceSettings { + string id = 1; + string name = 2; + bool filtering_enabled = 3; + bytes linked_ip = 4; + repeated bytes dedicated_ips = 5; +} + +message ParentalSettings { + bool enabled = 1; + bool block_adult = 2; + bool general_safe_search = 3; + bool youtube_safe_search = 4; + repeated string blocked_services = 5; + ScheduleSettings schedule = 6; +} + +message ScheduleSettings { + string tmz = 1; + WeeklyRange weeklyRange = 2; +} + +message WeeklyRange { + DayRange mon = 1; + DayRange tue = 2; + DayRange wed = 3; + DayRange thu = 4; + DayRange fri = 5; + DayRange sat = 6; + DayRange sun = 7; +} + +message DayRange { + google.protobuf.Duration start = 1; + google.protobuf.Duration end = 2; +} + +message RuleListsSettings { + bool enabled = 1; + repeated string ids = 2; +} + +message BlockingModeCustomIP { + bytes ipv4 = 1; + bytes ipv6 = 2; +} + +message BlockingModeNXDOMAIN {} + +message BlockingModeNullIP {} + +message BlockingModeREFUSED {} + +message DeviceBillingStat { + google.protobuf.Timestamp last_activity_time = 1; + string device_id = 2; + string client_country = 3; + // Protocol type. Possible values see here: https://bit.adguard.com/projects/DNS/repos/dns-server/browse#ql-properties + uint32 proto = 4; + uint32 asn = 5; + uint32 queries = 6; +} diff --git a/internal/backendpb/backend_grpc.pb.go b/internal/backendpb/backend_grpc.pb.go new file mode 100644 index 0000000..88853ae --- /dev/null +++ b/internal/backendpb/backend_grpc.pb.go @@ -0,0 +1,222 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 +// source: backend.proto + +package backendpb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + DNSService_GetDNSProfiles_FullMethodName = "/DNSService/getDNSProfiles" + DNSService_SaveDevicesBillingStat_FullMethodName = "/DNSService/saveDevicesBillingStat" +) + +// DNSServiceClient is the client API for DNSService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DNSServiceClient interface { + // Gets DNS profiles. + // + // Field "sync_time" in DNSProfilesRequest - pass to return the latest updates after this time moment. + // + // The trailers headers will include a "sync_time", given in milliseconds, + // that should be used for subsequent incremental DNS profile synchronization requests. + GetDNSProfiles(ctx context.Context, in *DNSProfilesRequest, opts ...grpc.CallOption) (DNSService_GetDNSProfilesClient, error) + // Stores devices activity. + SaveDevicesBillingStat(ctx context.Context, opts ...grpc.CallOption) (DNSService_SaveDevicesBillingStatClient, error) +} + +type dNSServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDNSServiceClient(cc grpc.ClientConnInterface) DNSServiceClient { + return &dNSServiceClient{cc} +} + +func (c *dNSServiceClient) GetDNSProfiles(ctx context.Context, in *DNSProfilesRequest, opts ...grpc.CallOption) (DNSService_GetDNSProfilesClient, error) { + stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[0], DNSService_GetDNSProfiles_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &dNSServiceGetDNSProfilesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type DNSService_GetDNSProfilesClient interface { + Recv() (*DNSProfile, error) + grpc.ClientStream +} + +type dNSServiceGetDNSProfilesClient struct { + grpc.ClientStream +} + +func (x *dNSServiceGetDNSProfilesClient) Recv() (*DNSProfile, error) { + m := new(DNSProfile) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *dNSServiceClient) SaveDevicesBillingStat(ctx context.Context, opts ...grpc.CallOption) (DNSService_SaveDevicesBillingStatClient, error) { + stream, err := c.cc.NewStream(ctx, &DNSService_ServiceDesc.Streams[1], DNSService_SaveDevicesBillingStat_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &dNSServiceSaveDevicesBillingStatClient{stream} + return x, nil +} + +type DNSService_SaveDevicesBillingStatClient interface { + Send(*DeviceBillingStat) error + CloseAndRecv() (*emptypb.Empty, error) + grpc.ClientStream +} + +type dNSServiceSaveDevicesBillingStatClient struct { + grpc.ClientStream +} + +func (x *dNSServiceSaveDevicesBillingStatClient) Send(m *DeviceBillingStat) error { + return x.ClientStream.SendMsg(m) +} + +func (x *dNSServiceSaveDevicesBillingStatClient) CloseAndRecv() (*emptypb.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(emptypb.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// DNSServiceServer is the server API for DNSService service. +// All implementations must embed UnimplementedDNSServiceServer +// for forward compatibility +type DNSServiceServer interface { + // Gets DNS profiles. + // + // Field "sync_time" in DNSProfilesRequest - pass to return the latest updates after this time moment. + // + // The trailers headers will include a "sync_time", given in milliseconds, + // that should be used for subsequent incremental DNS profile synchronization requests. + GetDNSProfiles(*DNSProfilesRequest, DNSService_GetDNSProfilesServer) error + // Stores devices activity. + SaveDevicesBillingStat(DNSService_SaveDevicesBillingStatServer) error + mustEmbedUnimplementedDNSServiceServer() +} + +// UnimplementedDNSServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDNSServiceServer struct { +} + +func (UnimplementedDNSServiceServer) GetDNSProfiles(*DNSProfilesRequest, DNSService_GetDNSProfilesServer) error { + return status.Errorf(codes.Unimplemented, "method GetDNSProfiles not implemented") +} +func (UnimplementedDNSServiceServer) SaveDevicesBillingStat(DNSService_SaveDevicesBillingStatServer) error { + return status.Errorf(codes.Unimplemented, "method SaveDevicesBillingStat not implemented") +} +func (UnimplementedDNSServiceServer) mustEmbedUnimplementedDNSServiceServer() {} + +// UnsafeDNSServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DNSServiceServer will +// result in compilation errors. +type UnsafeDNSServiceServer interface { + mustEmbedUnimplementedDNSServiceServer() +} + +func RegisterDNSServiceServer(s grpc.ServiceRegistrar, srv DNSServiceServer) { + s.RegisterService(&DNSService_ServiceDesc, srv) +} + +func _DNSService_GetDNSProfiles_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(DNSProfilesRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(DNSServiceServer).GetDNSProfiles(m, &dNSServiceGetDNSProfilesServer{stream}) +} + +type DNSService_GetDNSProfilesServer interface { + Send(*DNSProfile) error + grpc.ServerStream +} + +type dNSServiceGetDNSProfilesServer struct { + grpc.ServerStream +} + +func (x *dNSServiceGetDNSProfilesServer) Send(m *DNSProfile) error { + return x.ServerStream.SendMsg(m) +} + +func _DNSService_SaveDevicesBillingStat_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(DNSServiceServer).SaveDevicesBillingStat(&dNSServiceSaveDevicesBillingStatServer{stream}) +} + +type DNSService_SaveDevicesBillingStatServer interface { + SendAndClose(*emptypb.Empty) error + Recv() (*DeviceBillingStat, error) + grpc.ServerStream +} + +type dNSServiceSaveDevicesBillingStatServer struct { + grpc.ServerStream +} + +func (x *dNSServiceSaveDevicesBillingStatServer) SendAndClose(m *emptypb.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *dNSServiceSaveDevicesBillingStatServer) Recv() (*DeviceBillingStat, error) { + m := new(DeviceBillingStat) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// DNSService_ServiceDesc is the grpc.ServiceDesc for DNSService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DNSService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "DNSService", + HandlerType: (*DNSServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "getDNSProfiles", + Handler: _DNSService_GetDNSProfiles_Handler, + ServerStreams: true, + }, + { + StreamName: "saveDevicesBillingStat", + Handler: _DNSService_SaveDevicesBillingStat_Handler, + ClientStreams: true, + }, + }, + Metadata: "backend.proto", +} diff --git a/internal/backendpb/backendpb.go b/internal/backendpb/backendpb.go new file mode 100644 index 0000000..3bda61f --- /dev/null +++ b/internal/backendpb/backendpb.go @@ -0,0 +1,43 @@ +// Package backendpb contains the protobuf structures for the backend API. +package backendpb + +import ( + "context" + "fmt" + "net/url" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +// newClient returns new properly initialized DNSServiceClient. +func newClient(apiURL *url.URL) (client DNSServiceClient, err error) { + var creds credentials.TransportCredentials + switch s := apiURL.Scheme; s { + case "grpc": + creds = insecure.NewCredentials() + case "grpcs": + // Use a nil [tls.Config] to get the default TLS configuration. + creds = credentials.NewTLS(nil) + default: + return nil, fmt.Errorf("bad grpc url scheme %q", s) + } + + conn, err := grpc.Dial(apiURL.Host, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("dialing: %w", err) + } + + // Immediately make a connection attempt, since the constructor is often + // called right before the initial refresh. + conn.Connect() + + return NewDNSServiceClient(conn), nil +} + +// reportf is a helper method for reporting non-critical errors. +func reportf(ctx context.Context, errColl agd.ErrorCollector, format string, args ...any) { + agd.Collectf(ctx, errColl, "backendpb: "+format, args...) +} diff --git a/internal/backendpb/backendpb_test.go b/internal/backendpb/backendpb_test.go new file mode 100644 index 0000000..43b8a60 --- /dev/null +++ b/internal/backendpb/backendpb_test.go @@ -0,0 +1,38 @@ +package backendpb_test + +import "github.com/AdguardTeam/AdGuardDNS/internal/backendpb" + +// testDNSServiceServer is the [backendpb.DNSServiceServer] for tests. +// +// TODO(d.kolyshev): Use this to remove as much as possible from the internal +// test. +type testDNSServiceServer struct { + backendpb.UnimplementedDNSServiceServer + OnGetDNSProfiles func( + req *backendpb.DNSProfilesRequest, + srv backendpb.DNSService_GetDNSProfilesServer, + ) (err error) + OnSaveDevicesBillingStat func( + srv backendpb.DNSService_SaveDevicesBillingStatServer, + ) (err error) +} + +// type check +var _ backendpb.DNSServiceServer = (*testDNSServiceServer)(nil) + +// GetDNSProfiles implements the [backendpb.DNSServiceServer] interface for +// *testDNSServiceServer +func (s *testDNSServiceServer) GetDNSProfiles( + req *backendpb.DNSProfilesRequest, + srv backendpb.DNSService_GetDNSProfilesServer, +) (err error) { + return s.OnGetDNSProfiles(req, srv) +} + +// SaveDevicesBillingStat implements the [backendpb.DNSServiceServer] interface +// for *testDNSServiceServer +func (s *testDNSServiceServer) SaveDevicesBillingStat( + srv backendpb.DNSService_SaveDevicesBillingStatServer, +) (err error) { + return s.OnSaveDevicesBillingStat(srv) +} diff --git a/internal/backendpb/billstat.go b/internal/backendpb/billstat.go new file mode 100644 index 0000000..afe7060 --- /dev/null +++ b/internal/backendpb/billstat.go @@ -0,0 +1,101 @@ +package backendpb + +import ( + "context" + "fmt" + "io" + "net/url" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/billstat" + "github.com/AdguardTeam/golibs/errors" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// BillStatConfig is the configuration structure for the business logic backend +// billing statistics uploader. +type BillStatConfig struct { + // ErrColl is the error collector that is used to collect critical and + // non-critical errors. + ErrColl agd.ErrorCollector + + // Endpoint is the backend API URL. The scheme should be either "grpc" or + // "grpcs". + Endpoint *url.URL +} + +// NewBillStat creates a new billing statistics uploader. c must not be nil. +func NewBillStat(c *BillStatConfig) (b *BillStat, err error) { + b = &BillStat{ + errColl: c.ErrColl, + } + + b.client, err = newClient(c.Endpoint) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, err + } + + return b, nil +} + +// BillStat is the implementation of the [billstat.Uploader] interface that +// uploads the billing statistics to the business logic backend. It is safe for +// concurrent use. +// +// TODO(a.garipov): Consider uniting with [ProfileStorage] into a single +// backendpb.Client. +type BillStat struct { + errColl agd.ErrorCollector + + // client is the current GRPC client. + client DNSServiceClient +} + +// type check +var _ billstat.Uploader = (*BillStat)(nil) + +// Upload implements the [billstat.Uploader] interface for *BillStat. +func (b *BillStat) Upload(ctx context.Context, records billstat.Records) (err error) { + if len(records) == 0 { + return nil + } + + stream, err := b.client.SaveDevicesBillingStat(ctx) + if err != nil { + return fmt.Errorf("opening stream: %w", err) + } + + for deviceID, record := range records { + if record == nil { + reportf(ctx, b.errColl, "device %q: null record", deviceID) + + continue + } + + sendErr := stream.Send(recordToProtobuf(record, deviceID)) + if sendErr != nil { + return fmt.Errorf("uploading device %q record: %w", deviceID, sendErr) + } + } + + _, err = stream.CloseAndRecv() + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("finishing stream: %w", err) + } + + return nil +} + +// recordToProtobuf converts a billstat record structure into the protobuf +// structure. +func recordToProtobuf(r *billstat.Record, devID agd.DeviceID) (s *DeviceBillingStat) { + return &DeviceBillingStat{ + LastActivityTime: timestamppb.New(r.Time), + DeviceId: string(devID), + ClientCountry: string(r.Country), + Proto: uint32(r.Proto), + Asn: uint32(r.ASN), + Queries: uint32(r.Queries), + } +} diff --git a/internal/backendpb/billstat_test.go b/internal/backendpb/billstat_test.go new file mode 100644 index 0000000..a4576da --- /dev/null +++ b/internal/backendpb/billstat_test.go @@ -0,0 +1,104 @@ +package backendpb_test + +import ( + "context" + "io" + "net" + "net/url" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/backendpb" + "github.com/AdguardTeam/AdGuardDNS/internal/billstat" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestBillStat_Upload(t *testing.T) { + const ( + wantDeviceID = "test" + invalidDeviceID = "invalid" + ) + + wantRecord := &billstat.Record{ + Time: time.Time{}, + Country: agd.CountryCY, + ASN: 1221, + Queries: 1122, + Proto: agd.ProtoDNS, + } + + records := billstat.Records{ + wantDeviceID: wantRecord, + invalidDeviceID: nil, + } + + srv := &testDNSServiceServer{ + OnSaveDevicesBillingStat: func( + srv backendpb.DNSService_SaveDevicesBillingStatServer, + ) (err error) { + pt := &testutil.PanicT{} + + for { + data, recvErr := srv.Recv() + if recvErr != nil && errors.Is(recvErr, io.EOF) { + return srv.SendAndClose(&emptypb.Empty{}) + } + + require.NoError(t, recvErr) + + assert.Equal(pt, wantDeviceID, data.DeviceId) + assert.Equal(pt, uint32(wantRecord.ASN), data.Asn) + assert.Equal(pt, string(wantRecord.Country), data.ClientCountry) + assert.Equal(pt, timestamppb.New(wantRecord.Time), data.LastActivityTime) + assert.Equal(pt, uint32(wantRecord.Proto), data.Proto) + assert.Equal(pt, uint32(wantRecord.Queries), data.Queries) + } + }, + } + + l, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + + grpcSrv := grpc.NewServer( + grpc.ConnectionTimeout(1*time.Second), + grpc.Creds(insecure.NewCredentials()), + ) + backendpb.RegisterDNSServiceServer(grpcSrv, srv) + + go func() { + pt := &testutil.PanicT{} + + srvErr := grpcSrv.Serve(l) + require.NoError(pt, srvErr) + }() + t.Cleanup(grpcSrv.GracefulStop) + + errColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, err error) { + testutil.AssertErrorMsg(t, `backendpb: device "invalid": null record`, err) + }, + } + + b, err := backendpb.NewBillStat(&backendpb.BillStatConfig{ + ErrColl: errColl, + Endpoint: &url.URL{ + Scheme: "grpc", + Host: l.Addr().String(), + }, + }) + require.NoError(t, err) + + ctx := context.Background() + + err = b.Upload(ctx, records) + require.NoError(t, err) +} diff --git a/internal/backendpb/profiledb.go b/internal/backendpb/profiledb.go new file mode 100644 index 0000000..ea157d8 --- /dev/null +++ b/internal/backendpb/profiledb.go @@ -0,0 +1,457 @@ +package backendpb + +import ( + "context" + "fmt" + "io" + "net/netip" + "net/url" + "strconv" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/errors" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// ProfileStorageConfig is the configuration for the business logic backend +// profile storage. +type ProfileStorageConfig struct { + // ErrColl is the error collector that is used to collect critical and + // non-critical errors. + ErrColl agd.ErrorCollector + + // Endpoint is the backend API URL. The scheme should be either "grpc" or + // "grpcs". + Endpoint *url.URL +} + +// ProfileStorage is the implementation of the [profiledb.Storage] interface +// that retrieves the profile and device information from the business logic +// backend. It is safe for concurrent use. +type ProfileStorage struct { + errColl agd.ErrorCollector + + // client is the current GRPC client. + client DNSServiceClient +} + +// NewProfileStorage returns a new [ProfileStorage] that retrieves information +// from the business logic backend. +func NewProfileStorage(c *ProfileStorageConfig) (s *ProfileStorage, err error) { + client, err := newClient(c.Endpoint) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, err + } + + return &ProfileStorage{ + client: client, + errColl: c.ErrColl, + }, nil +} + +// type check +var _ profiledb.Storage = (*ProfileStorage)(nil) + +// Profiles implements the [profiledb.Storage] interface for *ProfileStorage. +func (s *ProfileStorage) Profiles( + ctx context.Context, + req *profiledb.StorageRequest, +) (resp *profiledb.StorageResponse, err error) { + stream, err := s.client.GetDNSProfiles(ctx, toProtobuf(req)) + if err != nil { + return nil, fmt.Errorf("loading profiles: %w", err) + } + defer func() { err = errors.WithDeferred(err, stream.CloseSend()) }() + + resp = &profiledb.StorageResponse{ + Profiles: []*agd.Profile{}, + Devices: []*agd.Device{}, + } + + stats := &profilesCallStats{ + isFullSync: req.SyncTime == time.Time{}, + } + + for { + stats.startRecv() + profile, profErr := stream.Recv() + if profErr != nil { + if errors.Is(profErr, io.EOF) { + break + } + + return nil, fmt.Errorf("receiving profile: %w", profErr) + } + stats.endRecv() + + stats.startDec() + prof, devices, profErr := profile.toInternal(ctx, time.Now(), s.errColl) + if profErr != nil { + reportf(ctx, s.errColl, "loading profile: %w", profErr) + + continue + } + stats.endDec() + + resp.Profiles = append(resp.Profiles, prof) + resp.Devices = append(resp.Devices, devices...) + } + + stats.report() + + trailer := stream.Trailer() + resp.SyncTime, err = syncTimeFromTrailer(trailer) + if err != nil { + return nil, fmt.Errorf("retrieving sync_time: %w", err) + } + + return resp, nil +} + +// toInternal converts the protobuf-encoded data into a profile structure. +func (x *DNSProfile) toInternal( + ctx context.Context, + updTime time.Time, + errColl agd.ErrorCollector, +) (profile *agd.Profile, devices []*agd.Device, err error) { + if x == nil { + return nil, nil, fmt.Errorf("profile is nil") + } + + parental, err := x.Parental.toInternal(ctx, errColl) + if err != nil { + return nil, nil, fmt.Errorf("parental: %w", err) + } + + m, err := blockingModeToInternal(x.BlockingMode) + if err != nil { + return nil, nil, fmt.Errorf("blocking mode: %w", err) + } + + devices, deviceIds := devicesToInternal(ctx, x.Devices, errColl) + listsEnabled, listIDs := x.RuleLists.toInternal(ctx, errColl) + + profID, err := agd.NewProfileID(x.DnsId) + if err != nil { + return nil, nil, fmt.Errorf("id: %w", err) + } + + var fltRespTTL time.Duration + if respTTL := x.FilteredResponseTtl; respTTL != nil { + fltRespTTL = respTTL.AsDuration() + } + + return &agd.Profile{ + Parental: parental, + BlockingMode: m, + ID: profID, + UpdateTime: updTime, + DeviceIDs: deviceIds, + RuleListIDs: listIDs, + CustomRules: rulesToInternal(ctx, x.CustomRules, errColl), + FilteredResponseTTL: fltRespTTL, + FilteringEnabled: x.FilteringEnabled, + SafeBrowsing: x.SafeBrowsing.toInternal(), + RuleListsEnabled: listsEnabled, + QueryLogEnabled: x.QueryLogEnabled, + Deleted: x.Deleted, + BlockPrivateRelay: x.BlockPrivateRelay, + BlockFirefoxCanary: x.BlockFirefoxCanary, + }, devices, nil +} + +// toInternal converts a protobuf parental-settings structure to an internal +// one. If x is nil, toInternal returns nil. +func (x *ParentalSettings) toInternal( + ctx context.Context, + errColl agd.ErrorCollector, +) (s *agd.ParentalProtectionSettings, err error) { + if x == nil { + return nil, nil + } + + schedule, err := x.Schedule.toInternal() + if err != nil { + return nil, fmt.Errorf("schedule: %w", err) + } + + return &agd.ParentalProtectionSettings{ + Schedule: schedule, + BlockedServices: blockedSvcsToInternal(ctx, errColl, x.BlockedServices), + Enabled: x.Enabled, + BlockAdult: x.BlockAdult, + GeneralSafeSearch: x.GeneralSafeSearch, + YoutubeSafeSearch: x.YoutubeSafeSearch, + }, nil +} + +// toInternal converts protobuf safe-browsing settings to an internal structure. +// If x is nil, toInternal returns nil. +func (x *SafeBrowsingSettings) toInternal() (sb *agd.SafeBrowsingSettings) { + if x == nil { + return nil + } + + return &agd.SafeBrowsingSettings{ + Enabled: x.Enabled, + BlockDangerousDomains: x.BlockDangerousDomains, + BlockNewlyRegisteredDomains: x.BlockNrd, + } +} + +// blockedSvcsToInternal is a helper that converts the blocked service IDs from +// the backend response to AdGuard DNS blocked service IDs. +func blockedSvcsToInternal( + ctx context.Context, + errColl agd.ErrorCollector, + respSvcs []string, +) (svcs []agd.BlockedServiceID) { + l := len(respSvcs) + if l == 0 { + return nil + } + + svcs = make([]agd.BlockedServiceID, 0, l) + for i, s := range respSvcs { + id, err := agd.NewBlockedServiceID(s) + if err != nil { + reportf(ctx, errColl, "blocked service at index %d: %w", i, err) + + continue + } + + svcs = append(svcs, id) + } + + return svcs +} + +// toInternal converts a protobuf protection-schedule structure to an internal +// one. If x is nil, toInternal returns nil. +func (x *ScheduleSettings) toInternal() (sch *agd.ParentalProtectionSchedule, err error) { + if x == nil { + return nil, nil + } + + sch = &agd.ParentalProtectionSchedule{} + + sch.TimeZone, err = agdtime.LoadLocation(x.Tmz) + if err != nil { + return nil, fmt.Errorf("loading timezone: %w", err) + } + + sch.Week = &agd.WeeklySchedule{} + + w := x.WeeklyRange + days := []*DayRange{w.Sun, w.Mon, w.Tue, w.Wed, w.Thu, w.Fri, w.Sat} + for i, d := range days { + if d == nil { + sch.Week[i] = agd.ZeroLengthDayRange() + + continue + } + + sch.Week[i] = agd.DayRange{ + Start: uint16(d.Start.AsDuration().Minutes()), + End: uint16(d.End.AsDuration().Minutes()), + } + } + + for i, r := range sch.Week { + err = r.Validate() + if err != nil { + return nil, fmt.Errorf("weekday %s: %w", time.Weekday(i), err) + } + } + + return sch, nil +} + +// blockingModeToInternal converts a protobuf blocking-mode sum-type to an +// internal one. If pbm is nil, blockingModeToInternal returns a null-IP +// blocking mode. +func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingModeCodec, err error) { + switch pbm := pbm.(type) { + case nil: + m.Mode = &dnsmsg.BlockingModeNullIP{} + case *DNSProfile_BlockingModeCustomIp: + custom := &dnsmsg.BlockingModeCustomIP{} + err = custom.IPv4.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv4) + if err != nil { + return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv4: %w", err) + } + + err = custom.IPv6.UnmarshalBinary(pbm.BlockingModeCustomIp.Ipv6) + if err != nil { + return dnsmsg.BlockingModeCodec{}, fmt.Errorf("bad custom ipv6: %w", err) + } + + m.Mode = custom + case *DNSProfile_BlockingModeNxdomain: + m.Mode = &dnsmsg.BlockingModeNXDOMAIN{} + case *DNSProfile_BlockingModeNullIp: + m.Mode = &dnsmsg.BlockingModeNullIP{} + case *DNSProfile_BlockingModeRefused: + m.Mode = &dnsmsg.BlockingModeREFUSED{} + default: + // Consider unhandled type-switch cases programmer errors. + panic(fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)) + } + + return m, nil +} + +// devicesToInternal is a helper that converts the devices from protobuf to +// AdGuard DNS devices. +func devicesToInternal( + ctx context.Context, + ds []*DeviceSettings, + errColl agd.ErrorCollector, +) (out []*agd.Device, ids []agd.DeviceID) { + l := len(ds) + if l == 0 { + return nil, nil + } + + out = make([]*agd.Device, 0, l) + for _, d := range ds { + dev, err := d.toInternal() + if err != nil { + reportf(ctx, errColl, "invalid device settings: %w", err) + + continue + } + + ids = append(ids, dev.ID) + out = append(out, dev) + } + + return out, ids +} + +// toInternal is a helper that converts device settings from backend protobuf +// response to AdGuard DNS device object. +func (ds *DeviceSettings) toInternal() (dev *agd.Device, err error) { + if ds == nil { + return nil, fmt.Errorf("device is nil") + } + + var linkedIP netip.Addr + err = linkedIP.UnmarshalBinary(ds.LinkedIp) + if err != nil { + return nil, fmt.Errorf("linked ip: %w", err) + } + + var dedicatedIPs []netip.Addr + dedicatedIPs, err = agdprotobuf.ByteSlicesToIPs(ds.DedicatedIps) + if err != nil { + return nil, fmt.Errorf("dedicated ips: %w", err) + } + + id, err := agd.NewDeviceID(ds.Id) + if err != nil { + return nil, fmt.Errorf("device id: %s: %w", ds.Id, err) + } + + name, err := agd.NewDeviceName(ds.Name) + if err != nil { + return nil, fmt.Errorf("device name: %s: %w", ds.Name, err) + } + + return &agd.Device{ + ID: id, + Name: name, + LinkedIP: linkedIP, + DedicatedIPs: dedicatedIPs, + FilteringEnabled: ds.FilteringEnabled, + }, nil +} + +// rulesToInternal is a helper that converts the filter rules from the backend +// response to AdGuard DNS filtering rules. +func rulesToInternal( + ctx context.Context, + respRules []string, + errColl agd.ErrorCollector, +) (rules []agd.FilterRuleText) { + l := len(respRules) + if l == 0 { + return nil + } + + rules = make([]agd.FilterRuleText, 0, l) + for i, r := range respRules { + text, err := agd.NewFilterRuleText(r) + if err != nil { + reportf(ctx, errColl, "rule at index %d: %w", i, err) + + continue + } + + rules = append(rules, text) + } + + return rules +} + +// toInternal is a helper that converts the filter lists from the backend +// response to AdGuard DNS filter list ids. If x is nil, toInternal returns +// false and nil. +func (x *RuleListsSettings) toInternal( + ctx context.Context, + errColl agd.ErrorCollector, +) (enabled bool, filterLists []agd.FilterListID) { + if x == nil { + return false, nil + } + + l := len(x.Ids) + if l == 0 { + return x.Enabled, nil + } + + filterLists = make([]agd.FilterListID, 0, l) + for _, f := range x.Ids { + id, err := agd.NewFilterListID(f) + if err != nil { + reportf(ctx, errColl, "invalid filter id: %s: %w", f, err) + + continue + } + + filterLists = append(filterLists, id) + } + + return x.Enabled, filterLists +} + +// toProtobuf converts a storage request structure into the protobuf structure. +func toProtobuf(r *profiledb.StorageRequest) (req *DNSProfilesRequest) { + return &DNSProfilesRequest{ + SyncTime: timestamppb.New(r.SyncTime), + } +} + +// syncTimeFromTrailer returns sync time from trailer metadata. Trailer +// metadata must contain "sync_time" field with milliseconds presentation of +// sync time. +func syncTimeFromTrailer(trailer metadata.MD) (syncTime time.Time, err error) { + st := trailer.Get("sync_time") + if len(st) == 0 { + return syncTime, fmt.Errorf("empty value") + } + + syncTimeMs, err := strconv.ParseInt(st[0], 10, 64) + if err != nil { + return syncTime, fmt.Errorf("invalid value: %w", err) + } + + return time.Unix(0, syncTimeMs*time.Millisecond.Nanoseconds()), nil +} diff --git a/internal/backendpb/profiledb_internal_test.go b/internal/backendpb/profiledb_internal_test.go new file mode 100644 index 0000000..d1e74b1 --- /dev/null +++ b/internal/backendpb/profiledb_internal_test.go @@ -0,0 +1,459 @@ +package backendpb + +import ( + "context" + "net/netip" + "strconv" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestMain(m *testing.M) { + testutil.DiscardLogOutput(m) +} + +// testProfileID is the common profile ID for tests. +const testProfileID agd.ProfileID = "prof1234" + +// TestUpdTime is the common update time for tests. +var TestUpdTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) + +func TestDNSProfile_ToInternal(t *testing.T) { + ctx := context.Background() + + errColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, err error) { + panic(err) + }, + } + + t.Run("success", func(t *testing.T) { + got, gotDevices, err := NewTestDNSProfile(t).toInternal(ctx, TestUpdTime, errColl) + require.NoError(t, err) + + assert.Equal(t, newProfile(t), got) + assert.Equal(t, newDevices(t), gotDevices) + }) + + t.Run("success_bad_data", func(t *testing.T) { + var errCollErr error + savingErrColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, err error) { + errCollErr = err + }, + } + got, gotDevices, err := newDNSProfileWithBadData(t).toInternal( + ctx, + TestUpdTime, + savingErrColl, + ) + require.NoError(t, err) + require.Error(t, errCollErr) + + // See the TODO in [blockingModeToInternal]. + wantProf := newProfile(t) + wantProf.BlockingMode = dnsmsg.BlockingModeCodec{ + Mode: &dnsmsg.BlockingModeNullIP{}, + } + + assert.Equal(t, wantProf, got) + assert.Equal(t, newDevices(t), gotDevices) + }) + + t.Run("empty", func(t *testing.T) { + var emptyDNSProfile *DNSProfile + _, _, err := emptyDNSProfile.toInternal(ctx, TestUpdTime, errColl) + testutil.AssertErrorMsg(t, "profile is nil", err) + }) + + t.Run("deleted", func(t *testing.T) { + dp := &DNSProfile{ + DnsId: string(testProfileID), + Deleted: true, + } + + got, gotDevices, err := dp.toInternal(ctx, TestUpdTime, errColl) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, got.ID, testProfileID) + assert.True(t, got.Deleted) + assert.Empty(t, gotDevices) + }) + + t.Run("inv_parental_sch_tmz", func(t *testing.T) { + dp := NewTestDNSProfile(t) + dp.Parental.Schedule.Tmz = "invalid" + + _, _, err := dp.toInternal(ctx, TestUpdTime, errColl) + testutil.AssertErrorMsg(t, "parental: schedule: loading timezone: unknown time zone invalid", err) + }) + + t.Run("inv_parental_sch_day_range", func(t *testing.T) { + dp := NewTestDNSProfile(t) + dp.Parental.Schedule.WeeklyRange.Sun = &DayRange{ + Start: durationpb.New(1000000000000), + End: nil, + } + + _, _, err := dp.toInternal(ctx, TestUpdTime, errColl) + testutil.AssertErrorMsg(t, "parental: schedule: weekday Sunday: bad day range: end 0 less than start 16", err) + }) + + t.Run("inv_blocking_mode_v4", func(t *testing.T) { + dp := NewTestDNSProfile(t) + bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp) + bm.BlockingModeCustomIp.Ipv4 = []byte("1") + + _, _, err := dp.toInternal(ctx, TestUpdTime, errColl) + testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv4: unexpected slice size", err) + }) + + t.Run("inv_blocking_mode_v6", func(t *testing.T) { + dp := NewTestDNSProfile(t) + bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp) + bm.BlockingModeCustomIp.Ipv6 = []byte("1") + + _, _, err := dp.toInternal(ctx, TestUpdTime, errColl) + testutil.AssertErrorMsg(t, "blocking mode: bad custom ipv6: unexpected slice size", err) + }) +} + +// newDNSProfileWithBadData returns a new instance of *DNSProfile with bad data +// for tests. +func newDNSProfileWithBadData(tb testing.TB) (dp *DNSProfile) { + tb.Helper() + + dayRange := &DayRange{ + Start: durationpb.New(0), + End: durationpb.New(59 * time.Minute), + } + + devices := []*DeviceSettings{{ + Id: "118ffe93", + Name: "118ffe93-name", + FilteringEnabled: false, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")), + DedicatedIps: [][]byte{ipToBytes(tb, netip.MustParseAddr("1.1.1.2"))}, + }, { + Id: "b9e1a762", + Name: "b9e1a762-name", + FilteringEnabled: true, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")), + DedicatedIps: nil, + }, { + Id: "invalid-too-long-device-id", + Name: "device_name", + FilteringEnabled: true, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")), + DedicatedIps: nil, + }, { + Id: "dev-name", + Name: "invalid-too-long-device-name-invalid-too-long-device-name-" + + "invalid-too-long-device-name-invalid-too-long-device-name-" + + "invalid-too-long-device-name-invalid-too-long-device-name", + FilteringEnabled: true, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")), + DedicatedIps: nil, + }, { + Id: "inv-ip", + Name: "test-name", + FilteringEnabled: true, + LinkedIp: []byte("1"), + DedicatedIps: nil, + }, { + Id: "inv-d-ip", + Name: "test-name", + FilteringEnabled: true, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")), + DedicatedIps: [][]byte{[]byte("1")}, + }} + + return &DNSProfile{ + DnsId: string(testProfileID), + FilteringEnabled: true, + QueryLogEnabled: true, + Deleted: false, + SafeBrowsing: &SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNrd: false, + }, + Parental: &ParentalSettings{ + Enabled: false, + BlockAdult: false, + GeneralSafeSearch: false, + YoutubeSafeSearch: false, + BlockedServices: []string{"youtube", "inv_blocked_svc\r"}, + Schedule: &ScheduleSettings{ + Tmz: "GMT", + WeeklyRange: &WeeklyRange{ + Sun: nil, + Mon: dayRange, + Tue: dayRange, + Wed: dayRange, + Thu: dayRange, + Fri: dayRange, + Sat: nil, + }, + }, + }, + RuleLists: &RuleListsSettings{ + Enabled: true, + Ids: []string{"1", "inv_filter_id\r"}, + }, + Devices: devices, + CustomRules: []string{"||example.org^"}, + FilteredResponseTtl: durationpb.New(10 * time.Second), + BlockPrivateRelay: true, + BlockFirefoxCanary: true, + } +} + +// NewTestDNSProfile returns a new instance of *DNSProfile for tests. +func NewTestDNSProfile(tb testing.TB) (dp *DNSProfile) { + tb.Helper() + + dayRange := &DayRange{ + Start: durationpb.New(0), + End: durationpb.New(59 * time.Minute), + } + + devices := []*DeviceSettings{{ + Id: "118ffe93", + Name: "118ffe93-name", + FilteringEnabled: false, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")), + DedicatedIps: [][]byte{ipToBytes(tb, netip.MustParseAddr("1.1.1.2"))}, + }, { + Id: "b9e1a762", + Name: "b9e1a762-name", + FilteringEnabled: true, + LinkedIp: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")), + DedicatedIps: nil, + }} + + return &DNSProfile{ + DnsId: string(testProfileID), + FilteringEnabled: true, + QueryLogEnabled: true, + Deleted: false, + SafeBrowsing: &SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNrd: false, + }, + Parental: &ParentalSettings{ + Enabled: false, + BlockAdult: false, + GeneralSafeSearch: false, + YoutubeSafeSearch: false, + BlockedServices: []string{"youtube"}, + Schedule: &ScheduleSettings{ + Tmz: "GMT", + WeeklyRange: &WeeklyRange{ + Sun: nil, + Mon: dayRange, + Tue: dayRange, + Wed: dayRange, + Thu: dayRange, + Fri: dayRange, + Sat: nil, + }, + }, + }, + RuleLists: &RuleListsSettings{ + Enabled: true, + Ids: []string{"1"}, + }, + Devices: devices, + CustomRules: []string{"||example.org^"}, + FilteredResponseTtl: durationpb.New(10 * time.Second), + BlockPrivateRelay: true, + BlockFirefoxCanary: true, + BlockingMode: &DNSProfile_BlockingModeCustomIp{ + BlockingModeCustomIp: &BlockingModeCustomIP{ + Ipv4: ipToBytes(tb, netip.MustParseAddr("1.2.3.4")), + Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")), + }, + }, + } +} + +// newProfile returns a new profile for tests. +func newProfile(tb testing.TB) (p *agd.Profile) { + tb.Helper() + + wantLoc, err := agdtime.LoadLocation("GMT") + require.NoError(tb, err) + + dayRange := agd.DayRange{ + Start: 0, + End: 59, + } + + wantParental := &agd.ParentalProtectionSettings{ + Schedule: &agd.ParentalProtectionSchedule{ + Week: &agd.WeeklySchedule{ + agd.ZeroLengthDayRange(), + dayRange, + dayRange, + dayRange, + dayRange, + dayRange, + agd.ZeroLengthDayRange(), + }, + TimeZone: wantLoc, + }, + BlockedServices: []agd.BlockedServiceID{"youtube"}, + Enabled: false, + BlockAdult: false, + GeneralSafeSearch: false, + YoutubeSafeSearch: false, + } + + wantSafeBrowsing := &agd.SafeBrowsingSettings{ + Enabled: true, + BlockDangerousDomains: true, + BlockNewlyRegisteredDomains: false, + } + + wantBlockingMode := dnsmsg.BlockingModeCodec{ + Mode: &dnsmsg.BlockingModeCustomIP{ + IPv4: netip.MustParseAddr("1.2.3.4"), + IPv6: netip.MustParseAddr("1234::cdef"), + }, + } + + return &agd.Profile{ + Parental: wantParental, + BlockingMode: wantBlockingMode, + ID: testProfileID, + UpdateTime: TestUpdTime, + DeviceIDs: []agd.DeviceID{ + "118ffe93", + "b9e1a762", + }, + RuleListIDs: []agd.FilterListID{"1"}, + CustomRules: []agd.FilterRuleText{"||example.org^"}, + FilteredResponseTTL: 10 * time.Second, + SafeBrowsing: wantSafeBrowsing, + RuleListsEnabled: true, + FilteringEnabled: true, + QueryLogEnabled: true, + Deleted: false, + BlockPrivateRelay: true, + BlockFirefoxCanary: true, + } +} + +// newDevices returns a slice of test devices. +func newDevices(t *testing.T) (d []*agd.Device) { + t.Helper() + + return []*agd.Device{{ + ID: "118ffe93", + LinkedIP: netip.MustParseAddr("1.1.1.1"), + Name: "118ffe93-name", + DedicatedIPs: []netip.Addr{netip.MustParseAddr("1.1.1.2")}, + FilteringEnabled: false, + }, { + ID: "b9e1a762", + LinkedIP: netip.MustParseAddr("2.2.2.2"), + Name: "b9e1a762-name", + DedicatedIPs: nil, + FilteringEnabled: true, + }} +} + +// ipToBytes is a wrapper around netip.Addr.MarshalBinary. +func ipToBytes(tb testing.TB, ip netip.Addr) (b []byte) { + tb.Helper() + + b, err := ip.MarshalBinary() + require.NoError(tb, err) + + return b +} + +func TestSyncTimeFromTrailer(t *testing.T) { + milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10) + + testCases := []struct { + wantError string + want time.Time + name string + in metadata.MD + }{{ + wantError: "empty value", + want: time.Time{}, + name: "no_key", + in: metadata.MD{}, + }, { + wantError: "empty value", + want: time.Time{}, + name: "empty_key", + in: metadata.MD{"sync_time": []string{}}, + }, { + wantError: `invalid value: strconv.ParseInt: parsing "": invalid syntax`, + want: time.Time{}, + name: "empty_value", + in: metadata.MD{"sync_time": []string{""}}, + }, { + wantError: "", + want: TestUpdTime, + name: "success", + in: metadata.MD{"sync_time": []string{milliseconds}}, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + syncTime, err := syncTimeFromTrailer(tc.in) + testutil.AssertErrorMsg(t, tc.wantError, err) + assert.True(t, tc.want.Equal(syncTime), "want %s; got %s", tc.want, syncTime) + }) + } +} + +var ( + errSink error + profSink *agd.Profile +) + +func BenchmarkDNSProfile_ToInternal(b *testing.B) { + dp := NewTestDNSProfile(b) + ctx := context.Background() + + errColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, err error) { + panic(err) + }, + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + profSink, _, errSink = dp.toInternal(ctx, TestUpdTime, errColl) + } + + require.NotNil(b, profSink) + require.NoError(b, errSink) + + // Most recent result, on a ThinkPad X13: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkDNSProfile_ToInternal + // BenchmarkDNSProfile_ToInternal-16 157513 10340 ns/op 1148 B/op 27 allocs/op +} diff --git a/internal/backendpb/profiledb_test.go b/internal/backendpb/profiledb_test.go new file mode 100644 index 0000000..3382adc --- /dev/null +++ b/internal/backendpb/profiledb_test.go @@ -0,0 +1,96 @@ +package backendpb_test + +import ( + context "context" + "net" + "net/url" + "strconv" + "testing" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/backendpb" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +var ( + errSink error + respSink *profiledb.StorageResponse +) + +func BenchmarkProfileStorage_Profiles(b *testing.B) { + syncTime := strconv.FormatInt(backendpb.TestUpdTime.UnixMilli(), 10) + srvProf := backendpb.NewTestDNSProfile(b) + trailerMD := metadata.MD{ + "sync_time": []string{syncTime}, + } + + srv := &testDNSServiceServer{ + OnGetDNSProfiles: func( + req *backendpb.DNSProfilesRequest, + srv backendpb.DNSService_GetDNSProfilesServer, + ) (err error) { + sendErr := srv.Send(srvProf) + srv.SetTrailer(trailerMD) + + return sendErr + }, + } + + errColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, err error) { + panic(err) + }, + } + + l, err := net.Listen("tcp", "localhost:0") + require.NoError(b, err) + + s, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{ + ErrColl: errColl, + Endpoint: &url.URL{ + Scheme: "grpc", + Host: l.Addr().String(), + }, + }) + require.NoError(b, err) + + grpcSrv := grpc.NewServer( + grpc.ConnectionTimeout(1*time.Second), + grpc.Creds(insecure.NewCredentials()), + ) + backendpb.RegisterDNSServiceServer(grpcSrv, srv) + + go func() { + pt := &testutil.PanicT{} + + srvErr := grpcSrv.Serve(l) + require.NoError(pt, srvErr) + }() + b.Cleanup(grpcSrv.GracefulStop) + + ctx := context.Background() + req := &profiledb.StorageRequest{} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + respSink, errSink = s.Profiles(ctx, req) + } + + require.NoError(b, errSink) + require.NotNil(b, respSink) + + // Most recent result, on a ThinkPad X13: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkProfileStorage_Profiles + // BenchmarkProfileStorage_Profiles-16 5347 245341 ns/op 15129 B/op 265 allocs/op +} diff --git a/internal/backendpb/stats.go b/internal/backendpb/stats.go new file mode 100644 index 0000000..9135e86 --- /dev/null +++ b/internal/backendpb/stats.go @@ -0,0 +1,82 @@ +package backendpb + +import ( + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/golibs/log" +) + +// profilesCallStats is a stateful structure that collects and reports +// statistics about a [ProfileStorage.Profiles] call. +type profilesCallStats struct { + recvStart time.Time + decStart time.Time + + initRecv time.Duration + totalRecv time.Duration + totalDec time.Duration + + numRecv int + + isFullSync bool +} + +// startRecv starts the receive timer. +func (s *profilesCallStats) startRecv() { + s.recvStart = time.Now() +} + +// endRecv ends the receive timer and records the results. +func (s *profilesCallStats) endRecv() { + d := time.Since(s.recvStart) + if s.numRecv == 0 { + // Count the initial receive separately, since it is often not + // representative of an average receive, because this is when gRPC + // actually performs the call. + s.initRecv = d + } else { + s.totalRecv += d + } + + s.numRecv++ +} + +// startDec starts the decoding timer. +func (s *profilesCallStats) startDec() { + s.decStart = time.Now() +} + +// endDec ends the decoding timer and records the results. +func (s *profilesCallStats) endDec() { + s.totalDec += time.Since(s.decStart) +} + +// report writes the statistics to the log and the metrics. +func (s *profilesCallStats) report() { + logFunc := log.Debug + if s.isFullSync { + logFunc = log.Info + } + + if s.numRecv == 0 { + logFunc("backendpb: no recv") + + return + } + + n := time.Duration(s.numRecv) + avgRecv := s.totalRecv / n + avgDec := s.totalDec / n + + logFunc( + "backendpb: total recv: %s; agv recv: %s; init recv: %s", + s.totalRecv, + avgRecv, + s.initRecv, + ) + logFunc("backendpb: total dec: %s; agv dec: %s", s.totalDec, avgDec) + + metrics.GRPCAvgProfileRecvDuration.Observe(avgRecv.Seconds()) + metrics.GRPCAvgProfileDecDuration.Observe(avgDec.Seconds()) +} diff --git a/internal/billstat/runtime.go b/internal/billstat/runtime.go index 0f00387..a10349c 100644 --- a/internal/billstat/runtime.go +++ b/internal/billstat/runtime.go @@ -86,10 +86,17 @@ var _ agd.Refresher = (*RuntimeRecorder)(nil) // uploads the currently available data and resets it. func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) { records := r.resetRecords() + + startTime := time.Now() defer func() { + dur := time.Since(startTime).Seconds() + metrics.BillStatUploadDuration.Observe(dur) + if err != nil { r.remergeRecords(records) log.Info("billstat: refresh failed, records remerged") + } else { + metrics.BillStatUploadTimestamp.SetToCurrentTime() } metrics.SetStatusGauge(metrics.BillStatUploadStatus, err) @@ -100,8 +107,6 @@ func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) { return fmt.Errorf("uploading billstat records: %w", err) } - metrics.BillStatUploadTimestamp.SetToCurrentTime() - return nil } diff --git a/internal/bindtodevice/chanlistener_linux.go b/internal/bindtodevice/chanlistener_linux.go index 503dedc..c1ec81d 100644 --- a/internal/bindtodevice/chanlistener_linux.go +++ b/internal/bindtodevice/chanlistener_linux.go @@ -6,30 +6,35 @@ import ( "net" "net/netip" "sync" + + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/prometheus/client_golang/prometheus" ) // chanListener is a [net.Listener] that returns data sent to it through a // channel. // -// Listeners of this type are returned by [chanListenConfig.Listen] and are used -// in module dnsserver to make the bind-to-device logic work in DNS-over-TCP. +// Listeners of this type are returned by [ListenConfig.Listen] and are used in +// module dnsserver to make the bind-to-device logic work in DNS-over-TCP. type chanListener struct { // mu protects conns (against closure) and isClosed. - mu *sync.Mutex - conns chan net.Conn - laddr net.Addr - subnet netip.Prefix - isClosed bool + mu *sync.Mutex + conns chan net.Conn + connsGauge prometheus.Gauge + laddr net.Addr + subnet netip.Prefix + isClosed bool } // newChanListener returns a new properly initialized *chanListener. func newChanListener(conns chan net.Conn, subnet netip.Prefix, laddr net.Addr) (l *chanListener) { return &chanListener{ - mu: &sync.Mutex{}, - conns: conns, - laddr: laddr, - subnet: subnet, - isClosed: false, + mu: &sync.Mutex{}, + conns: conns, + connsGauge: metrics.BindToDeviceTCPConnsChanSize.WithLabelValues(subnet.String()), + laddr: laddr, + subnet: subnet, + isClosed: false, } } @@ -77,5 +82,7 @@ func (l *chanListener) send(conn net.Conn) (ok bool) { l.conns <- conn + l.connsGauge.Set(float64(len(l.conns))) + return true } diff --git a/internal/bindtodevice/chanpacketconn_linux.go b/internal/bindtodevice/chanpacketconn_linux.go index 94e3051..b362fc2 100644 --- a/internal/bindtodevice/chanpacketconn_linux.go +++ b/internal/bindtodevice/chanpacketconn_linux.go @@ -11,13 +11,15 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/prometheus/client_golang/prometheus" ) // chanPacketConn is a [netext.SessionPacketConn] that returns data sent to it // through the channel. // -// Connections of this type are returned by [chanListenConfig.ListenPacket] and -// are used in module dnsserver to make the bind-to-device logic work in +// Connections of this type are returned by [ListenConfig.ListenPacket] and are +// used in module dnsserver to make the bind-to-device logic work in // DNS-over-UDP. type chanPacketConn struct { // mu protects sessions (against closure) and isClosed. @@ -26,6 +28,9 @@ type chanPacketConn struct { writeRequests chan *packetConnWriteReq + sessionsGauge prometheus.Gauge + writeRequestsGauge prometheus.Gauge + // deadlineMu protects readDeadline and writeDeadline. deadlineMu *sync.RWMutex readDeadline time.Time @@ -48,6 +53,9 @@ func newChanPacketConn( sessions: sessions, writeRequests: writeRequests, + sessionsGauge: metrics.BindToDeviceUDPSessionsChanSize.WithLabelValues(subnet.String()), + writeRequestsGauge: metrics.BindToDeviceUDPWriteRequestsChanSize.WithLabelValues(subnet.String()), + deadlineMu: &sync.RWMutex{}, laddr: laddr, @@ -257,6 +265,8 @@ func (c *chanPacketConn) writeToSession( return 0, wrapConnError(tnChanPConn, fnName, c.laddr, err) } + c.writeRequestsGauge.Set(float64(len(c.writeRequests))) + r, err := receiveWithTimer(resp, timerCh) if err != nil { err = fmt.Errorf("receiving write response: %w", err) @@ -309,5 +319,7 @@ func (c *chanPacketConn) send(sess *packetSession) (ok bool) { c.sessions <- sess + c.sessionsGauge.Set(float64(len(c.sessions))) + return true } diff --git a/internal/bindtodevice/connindex_linux.go b/internal/bindtodevice/connindex_linux.go index 856d376..68ee8f3 100644 --- a/internal/bindtodevice/connindex_linux.go +++ b/internal/bindtodevice/connindex_linux.go @@ -20,27 +20,23 @@ type connIndex struct { listeners []*chanListener } -// subnetSortsBefore returns true if subnet x sorts before subnet y. -func subnetSortsBefore(x, y netip.Prefix) (isBefore bool) { - xAddr, xBits := x.Addr(), x.Bits() - yAddr, yBits := y.Addr(), y.Bits() - if xBits == yBits { - return xAddr.Less(yAddr) - } - - return xBits > yBits -} - // subnetCompare is a comparison function for the two subnets. It returns -1 if // x sorts before y, 1 if x sorts after y, and 0 if their relative sorting // position is the same. func subnetCompare(x, y netip.Prefix) (cmp int) { - switch { - case x == y: + if x == y { return 0 - case subnetSortsBefore(x, y): + } + + xAddr, xBits := x.Addr(), x.Bits() + yAddr, yBits := y.Addr(), y.Bits() + if xBits == yBits { + return xAddr.Compare(yAddr) + } + + if xBits > yBits { return -1 - default: + } else { return 1 } } diff --git a/internal/bindtodevice/connindex_linux_internal_test.go b/internal/bindtodevice/connindex_linux_internal_test.go index 778c49e..2d9245b 100644 --- a/internal/bindtodevice/connindex_linux_internal_test.go +++ b/internal/bindtodevice/connindex_linux_internal_test.go @@ -10,7 +10,7 @@ import ( "golang.org/x/exp/slices" ) -func TestSubnetSortsBefore(t *testing.T) { +func TestSubnetCompare(t *testing.T) { want := []netip.Prefix{ netip.MustParsePrefix("1.0.0.0/24"), netip.MustParsePrefix("1.2.3.0/24"), @@ -24,6 +24,6 @@ func TestSubnetSortsBefore(t *testing.T) { netip.MustParsePrefix("1.2.3.0/24"), } - slices.SortFunc(got, subnetSortsBefore) + slices.SortFunc(got, subnetCompare) assert.Equalf(t, want, got, "got (as strings): %q", got) } diff --git a/internal/bindtodevice/interfacelistener_linux.go b/internal/bindtodevice/interfacelistener_linux.go index 0d2f33e..d75eda0 100644 --- a/internal/bindtodevice/interfacelistener_linux.go +++ b/internal/bindtodevice/interfacelistener_linux.go @@ -9,18 +9,22 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdsync" + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" - "github.com/miekg/dns" ) // interfaceListener contains information about a single interface listener. type interfaceListener struct { conns *connIndex + listenConf *net.ListenConfig + bodyPool *agdsync.TypedPool[[]byte] + oobPool *agdsync.TypedPool[[]byte] writeRequests chan *packetConnWriteReq done chan unit - listenConf *net.ListenConfig errColl agd.ErrorCollector ifaceName string port uint16 @@ -63,17 +67,30 @@ func (l *interfaceListener) listenTCP(errCh chan<- error) { continue } - laddr := netutil.NetAddrToAddrPort(conn.LocalAddr()) - lsnr := l.conns.listener(laddr.Addr()) - if lsnr == nil { - log.Info("%s: no channel for laddr %s", logPrefix, laddr) - - continue - } + l.processConn(conn, logPrefix) + } +} +// processConn processes a single connection. If the connection doesn't have a +// connected channel-listener, it is closed. +func (l *interfaceListener) processConn(conn net.Conn, logPrefix string) { + laddr := netutil.NetAddrToAddrPort(conn.LocalAddr()) + raddr := conn.RemoteAddr() + if lsnr := l.conns.listener(laddr.Addr()); lsnr != nil { if !lsnr.send(conn) { - log.Info("%s: channel for laddr %s is closed", logPrefix, laddr) + log.Info("%s: from raddr %s: channel for laddr %s is closed", logPrefix, raddr, laddr) } + + return + } + + metrics.BindToDeviceUnknownTCPRequestsTotal.Inc() + + optlog.Debug3("%s: from raddr %s: no stream channel for laddr %s", logPrefix, raddr, laddr) + + err := conn.Close() + if err != nil { + log.Debug("%s: from raddr %s: closing: %s", logPrefix, raddr, err) } } @@ -112,29 +129,62 @@ func (l *interfaceListener) listenUDP(errCh chan<- error) { // Go on. } - // TODO(a.garipov): Consider customization of body sizes. - var sess *packetSession - sess, err = readPacketSession(udpConn, dns.DefaultMsgSize) + err = l.readUDP(udpConn, logPrefix) if err != nil { agd.Collectf(ctx, l.errColl, "%s: reading session: %w", logPrefix, err) - - continue - } - - laddr := sess.laddr.AddrPort().Addr() - chanPConn := l.conns.packetConn(laddr) - if chanPConn == nil { - log.Info("%s: no channel for laddr %s", logPrefix, laddr) - - continue - } - - if !chanPConn.send(sess) { - log.Info("%s: channel for laddr %s is closed", logPrefix, laddr) } } } +// readUDP reads a UDP session from c and sends it to the appropriate channel. +func (l *interfaceListener) readUDP(c *net.UDPConn, logPrefix string) (err error) { + bodyPtr := l.bodyPool.Get() + body := *bodyPtr + + // Extend body to the capacity in case it had already been used and sliced + // by [readPacketSession]. + body = body[:cap(body)] + + oobPtr := l.oobPool.Get() + oob := *oobPtr + + defer func() { + l.oobPool.Put(oobPtr) + + // Only return the body to the pool in case of error here. The actual + // return is done in writeUDP. + if err != nil { + l.bodyPool.Put(bodyPtr) + } + }() + + sess, err := readPacketSession(c, body, oob) + if err != nil { + return fmt.Errorf("reading session: %w", err) + } + + laddr := sess.laddr.AddrPort().Addr() + chanPacketConn := l.conns.packetConn(laddr) + if chanPacketConn == nil { + metrics.BindToDeviceUnknownUDPRequestsTotal.Inc() + + optlog.Debug3( + "%s: from raddr %s: no packet channel for laddr %s", + logPrefix, + sess.raddr, + laddr, + ) + + return nil + } + + if !chanPacketConn.send(sess) { + log.Info("%s: channel for laddr %s is closed", logPrefix, laddr) + } + + return nil +} + // writeUDP runs the UDP write loop. It is intended to be used as a goroutine. func (l *interfaceListener) writeUDP(c *net.UDPConn) { defer log.OnPanic("interfaceListener.writeUDP") @@ -167,6 +217,8 @@ func (l *interfaceListener) writeUDP(c *net.UDPConn) { s.respOOB, req.session.raddr, ) + + l.bodyPool.Put(&s.readBody) } resetDeadlineErr := c.SetWriteDeadline(time.Time{}) diff --git a/internal/bindtodevice/interfacestorage.go b/internal/bindtodevice/interfacestorage.go index 58fcd76..2986097 100644 --- a/internal/bindtodevice/interfacestorage.go +++ b/internal/bindtodevice/interfacestorage.go @@ -18,7 +18,7 @@ type NetInterface interface { // type check var _ NetInterface = osInterface{} -// osInterface is a wapper around [*net.Interface] that implements the +// osInterface is a wrapper around [*net.Interface] that implements the // [NetInterface] interface. type osInterface struct { iface *net.Interface diff --git a/internal/bindtodevice/chanlistenconfig_linux.go b/internal/bindtodevice/listenconfig_linux.go similarity index 60% rename from internal/bindtodevice/chanlistenconfig_linux.go rename to internal/bindtodevice/listenconfig_linux.go index a6915bc..7e8e4db 100644 --- a/internal/bindtodevice/chanlistenconfig_linux.go +++ b/internal/bindtodevice/listenconfig_linux.go @@ -9,23 +9,24 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" ) -// chanListenConfig is a [netext.ListenConfig] implementation that uses the +// ListenConfig is a [netext.ListenConfig] implementation that uses the // provided channel-based packet connection and listener to implement the // methods of the interface. // // netext.ListenConfig instances of this type are the ones that are going to be // set as [dnsserver.ConfigBase.ListenConfig] to make the bind-to-device logic // work. -type chanListenConfig struct { +type ListenConfig struct { packetConn *chanPacketConn listener *chanListener + addr string } // type check -var _ netext.ListenConfig = (*chanListenConfig)(nil) +var _ netext.ListenConfig = (*ListenConfig)(nil) -// Listen implements the [netext.ListenConfig] interface for *chanListenConfig. -func (lc *chanListenConfig) Listen( +// Listen implements the [netext.ListenConfig] interface for *ListenConfig. +func (lc *ListenConfig) Listen( ctx context.Context, network string, address string, @@ -34,11 +35,17 @@ func (lc *chanListenConfig) Listen( } // ListenPacket implements the [netext.ListenConfig] interface for -// *chanListenConfig. -func (lc *chanListenConfig) ListenPacket( +// *ListenConfig. +func (lc *ListenConfig) ListenPacket( ctx context.Context, network string, address string, ) (c net.PacketConn, err error) { return lc.packetConn, nil } + +// Addr returns the address on which lc accepts connections. See +// [agdnet.FormatPrefixAddr] for the format. +func (lc *ListenConfig) Addr() (addr string) { + return lc.addr +} diff --git a/internal/bindtodevice/chanlistenconfig_linux_internal_test.go b/internal/bindtodevice/listenconfig_linux_internal_test.go similarity index 69% rename from internal/bindtodevice/chanlistenconfig_linux_internal_test.go rename to internal/bindtodevice/listenconfig_linux_internal_test.go index c00a924..2022f35 100644 --- a/internal/bindtodevice/chanlistenconfig_linux_internal_test.go +++ b/internal/bindtodevice/listenconfig_linux_internal_test.go @@ -6,16 +6,19 @@ import ( "context" "testing" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestChanListenConfig(t *testing.T) { +func TestListenConfig(t *testing.T) { pc := newChanPacketConn(nil, testSubnetIPv4, nil, testLAddr) lsnr := newChanListener(nil, testSubnetIPv4, testLAddr) - c := chanListenConfig{ + addr := agdnet.FormatPrefixAddr(testSubnetIPv4, 1234) + c := &ListenConfig{ packetConn: pc, listener: lsnr, + addr: addr, } ctx := context.Background() @@ -29,4 +32,7 @@ func TestChanListenConfig(t *testing.T) { require.NoError(t, err) assert.Equal(t, lsnr, gotLsnr) + + gotAddr := c.Addr() + assert.Equal(t, addr, gotAddr) } diff --git a/internal/bindtodevice/listenconfig_others.go b/internal/bindtodevice/listenconfig_others.go new file mode 100644 index 0000000..99286eb --- /dev/null +++ b/internal/bindtodevice/listenconfig_others.go @@ -0,0 +1,55 @@ +//go:build !linux + +package bindtodevice + +import ( + "context" + "net" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" +) + +// ListenConfig is a [netext.ListenConfig] implementation that uses the +// provided channel-based packet connection and listener to implement the +// methods of the interface. +// +// netext.ListenConfig instances of this type are the ones that are going to be +// set as [dnsserver.ConfigBase.ListenConfig] to make the bind-to-device logic +// work. +// +// It is only supported on Linux. +type ListenConfig struct{} + +// type check +var _ netext.ListenConfig = (*ListenConfig)(nil) + +// Listen implements the [netext.ListenConfig] interface for *ListenConfig. +// +// It is only supported on Linux. +func (lc *ListenConfig) Listen( + ctx context.Context, + network string, + address string, +) (l net.Listener, err error) { + return nil, errUnsupported +} + +// ListenPacket implements the [netext.ListenConfig] interface for +// *ListenConfig. +// +// It is only supported on Linux. +func (lc *ListenConfig) ListenPacket( + ctx context.Context, + network string, + address string, +) (c net.PacketConn, err error) { + return nil, errUnsupported +} + +// Addr returns the address on which lc accepts connections. See +// [agdnet.FormatPrefixAddr] for the format. +// +// It is only supported on Linux. +func (lc *ListenConfig) Addr() (addr string) { + return "" +} diff --git a/internal/bindtodevice/manager_linux.go b/internal/bindtodevice/manager_linux.go index e906064..7a99db9 100644 --- a/internal/bindtodevice/manager_linux.go +++ b/internal/bindtodevice/manager_linux.go @@ -10,10 +10,13 @@ import ( "sync" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" + "github.com/AdguardTeam/AdGuardDNS/internal/agdsync" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/mapsutil" + "github.com/miekg/dns" ) // Manager creates individual listeners and dispatches connections to them. @@ -49,7 +52,7 @@ var defaultCtrlConf = &ControlConfig{ // configuration is used. // // Add must not be called after Start is called. -func (m *Manager) Add(id ID, ifaceName string, port uint16, conf *ControlConfig) (err error) { +func (m *Manager) Add(id ID, ifaceName string, port uint16, ctrlConf *ControlConfig) (err error) { defer func() { err = errors.Annotate(err, "adding interface listener with id %q: %w", id) }() _, err = m.interfaces.InterfaceByName(ifaceName) @@ -86,29 +89,51 @@ func (m *Manager) Add(id ID, ifaceName string, port uint16, conf *ControlConfig) return err } - if conf == nil { - conf = defaultCtrlConf + if ctrlConf == nil { + ctrlConf = defaultCtrlConf } - m.ifaceListeners[id] = &interfaceListener{ - conns: &connIndex{}, - writeRequests: make(chan *packetConnWriteReq, m.chanBufSize), - done: m.done, - listenConf: newListenConfig(ifaceName, conf), - errColl: m.errColl, - ifaceName: ifaceName, - port: port, - } + // TODO(a.garipov): Consider customization of body sizes. + m.ifaceListeners[id] = m.newInterfaceListener(ctrlConf, ifaceName, dns.DefaultMsgSize, port) return nil } -// ListenConfig returns a new netext.ListenConfig that receives connections from -// the interface listener with the given id and the destination addresses of -// which fall within subnet. subnet should be masked. +// newInterfaceListener returns a new properly initialized *interfaceListener +// for this manager. +func (m *Manager) newInterfaceListener( + ctrlConf *ControlConfig, + ifaceName string, + bodySize int, + port uint16, +) (l *interfaceListener) { + return &interfaceListener{ + conns: &connIndex{}, + listenConf: newListenConfig(ifaceName, ctrlConf), + bodyPool: agdsync.NewTypedPool(func() (v *[]byte) { + b := make([]byte, bodySize) + + return &b + }), + oobPool: agdsync.NewTypedPool(func() (v *[]byte) { + b := make([]byte, netext.IPDstOOBSize) + + return &b + }), + writeRequests: make(chan *packetConnWriteReq, m.chanBufSize), + done: m.done, + errColl: m.errColl, + ifaceName: ifaceName, + port: port, + } +} + +// ListenConfig returns a new *ListenConfig that receives connections from the +// interface listener with the given id and the destination addresses of which +// fall within subnet. subnet should be masked. // // ListenConfig must not be called after Start is called. -func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c netext.ListenConfig, err error) { +func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err error) { defer func() { err = errors.Annotate( err, @@ -154,9 +179,10 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c netext.ListenConfi return nil, fmt.Errorf("adding udp conn: %w", err) } - return &chanListenConfig{ + return &ListenConfig{ packetConn: pConn, listener: lsnr, + addr: agdnet.FormatPrefixAddr(subnet, l.port), }, nil } diff --git a/internal/bindtodevice/manager_linux_test.go b/internal/bindtodevice/manager_linux_test.go index e7531d8..d102eeb 100644 --- a/internal/bindtodevice/manager_linux_test.go +++ b/internal/bindtodevice/manager_linux_test.go @@ -221,6 +221,8 @@ func TestManager(t *testing.T) { err := m.Add(testID1, ifaceName, testPort1, nil) require.NoError(t, err) + // TODO(a.garipov): Add tests for addresses within ifaceNet but outside of a + // narrower subnet. subnet, err := netutil.IPNetToPrefixNoMapped(&net.IPNet{ IP: ifaceNet.IP.Mask(ifaceNet.Mask), Mask: ifaceNet.Mask, diff --git a/internal/bindtodevice/manager_others.go b/internal/bindtodevice/manager_others.go index d31f91b..9e916f6 100644 --- a/internal/bindtodevice/manager_others.go +++ b/internal/bindtodevice/manager_others.go @@ -7,7 +7,6 @@ import ( "net/netip" "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" ) @@ -25,6 +24,8 @@ func NewManager(c *ManagerConfig) (m *Manager) { // errUnsupported is returned from all [Manager] methods on OSs other than // Linux. +// +// TODO(a.garipov): Consider using [errors.ErrUnsupported] in Go 1.21. const errUnsupported errors.Error = "bindtodevice is only supported on linux" // Add creates a new interface-listener record in m. @@ -34,12 +35,12 @@ func (m *Manager) Add(id ID, ifaceName string, port uint16, cc *ControlConfig) ( return errUnsupported } -// ListenConfig returns a new netext.ListenConfig that receives connections from -// the interface listener with the given id and the destination addresses of -// which fall within subnet. subnet should be masked. +// ListenConfig returns a new *ListenConfig that receives connections from the +// interface listener with the given id and the destination addresses of which +// fall within subnet. subnet should be masked. // // It is only supported on Linux. -func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c netext.ListenConfig, err error) { +func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err error) { return nil, errUnsupported } diff --git a/internal/bindtodevice/prefixaddr_linux.go b/internal/bindtodevice/prefixaddr_linux.go index aa8f9e6..472d7d8 100644 --- a/internal/bindtodevice/prefixaddr_linux.go +++ b/internal/bindtodevice/prefixaddr_linux.go @@ -3,9 +3,10 @@ package bindtodevice import ( - "fmt" "net" "net/netip" + + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" ) // prefixNetAddr is a wrapper around netip.Prefix that makes it a [net.Addr]. @@ -21,16 +22,11 @@ type prefixNetAddr struct { // type check var _ net.Addr = (*prefixNetAddr)(nil) -// String implements the [net.Addr] interface for *prefixNetAddr. It returns an -// address of the form "1.2.3.0:56789/24". That is, IP:port with a subnet after -// a slash. This is done to make using the IP:port part easier to split off -// using something like [strings.Cut]. +// String implements the [net.Addr] interface for *prefixNetAddr. +// +// See [agdnet.FormatPrefixAddr] for the format. func (addr *prefixNetAddr) String() (n string) { - return fmt.Sprintf( - "%s/%d", - netip.AddrPortFrom(addr.prefix.Addr(), addr.port), - addr.prefix.Bits(), - ) + return agdnet.FormatPrefixAddr(addr.prefix, addr.port) } // Network implements the [net.Addr] interface for *prefixNetAddr. diff --git a/internal/bindtodevice/prefixaddr_linux_internal_test.go b/internal/bindtodevice/prefixaddr_linux_internal_test.go index 7df5be7..3d8a28c 100644 --- a/internal/bindtodevice/prefixaddr_linux_internal_test.go +++ b/internal/bindtodevice/prefixaddr_linux_internal_test.go @@ -16,6 +16,8 @@ func TestPrefixAddr(t *testing.T) { network = "tcp" ) + fullPrefix := netip.MustParsePrefix("1.2.3.4/32") + testCases := []struct { in *prefixNetAddr want string @@ -42,6 +44,14 @@ func TestPrefixAddr(t *testing.T) { netip.AddrPortFrom(testSubnetIPv6.Addr(), port), testSubnetIPv6.Bits(), ), name: "ipv6", + }, { + in: &prefixNetAddr{ + prefix: fullPrefix, + network: network, + port: port, + }, + want: netip.AddrPortFrom(fullPrefix.Addr(), port).String(), + name: "ipv4_full", }} for _, tc := range testCases { diff --git a/internal/bindtodevice/socket_linux.go b/internal/bindtodevice/socket_linux.go index ad0e910..54f7bc7 100644 --- a/internal/bindtodevice/socket_linux.go +++ b/internal/bindtodevice/socket_linux.go @@ -7,7 +7,6 @@ import ( "net" "syscall" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/errors" "golang.org/x/sys/unix" ) @@ -106,14 +105,16 @@ func listenControlWithSO( return errors.WithDeferred(opErr, err) } +// msgUDPReader is an interface for types of connections that can read UDP +// messages. See [*net.UDPConn]. +type msgUDPReader interface { + ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) +} + // readPacketSession is a helper that reads a packet-session data from a UDP // connection. -func readPacketSession(c *net.UDPConn, bodySize int) (sess *packetSession, err error) { - // TODO(a.garipov): Consider adding pooling. - b := make([]byte, bodySize) - oob := make([]byte, netext.IPDstOOBSize) - - n, oobn, _, raddr, err := c.ReadMsgUDP(b, oob) +func readPacketSession(c msgUDPReader, body, oob []byte) (sess *packetSession, err error) { + n, oobn, _, raddr, err := c.ReadMsgUDP(body, oob) if err != nil { return nil, fmt.Errorf("reading: %w", err) } @@ -142,7 +143,7 @@ func readPacketSession(c *net.UDPConn, bodySize int) (sess *packetSession, err e sess = &packetSession{ laddr: origDstAddr, raddr: raddr, - readBody: b[:n], + readBody: body[:n], respOOB: respOOB, } diff --git a/internal/bindtodevice/socket_linux_internal_test.go b/internal/bindtodevice/socket_linux_internal_test.go index bb062f5..98a82a9 100644 --- a/internal/bindtodevice/socket_linux_internal_test.go +++ b/internal/bindtodevice/socket_linux_internal_test.go @@ -3,7 +3,9 @@ package bindtodevice import ( + "bytes" "context" + "encoding/binary" "fmt" "net" "net/netip" @@ -16,6 +18,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/slices" @@ -240,10 +243,13 @@ func testListenControlUDPQuery(t *testing.T, packetConn net.PacketConn, reqAddr err := packetConn.SetReadDeadline(time.Now().Add(testTimeout)) require.NoError(t, err) + b := make([]byte, reqLen) + oob := make([]byte, netext.IPDstOOBSize) + var sess *packetSession switch c := packetConn.(type) { case *net.UDPConn: - sess, err = readPacketSession(c, reqLen) + sess, err = readPacketSession(c, b, oob) require.NoError(t, err) case netext.SessionPacketConn: var s netext.PacketSession @@ -460,3 +466,84 @@ func TestListenControlWithSO(t *testing.T) { require.NoError(t, err) }) } + +// testMsgUDPReader is a [msgUDPReader] for tests. +type testMsgUDPReader struct { + onReadMsgUDP func(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) +} + +// type check +var _ msgUDPReader = (*testMsgUDPReader)(nil) + +// ReadMsgUDP implements the [msgUDPReader] interface for *testMsgUDPReader. +func (r *testMsgUDPReader) ReadMsgUDP( + b []byte, + oob []byte, +) (n, oobn, flags int, addr *net.UDPAddr, err error) { + return r.onReadMsgUDP(b, oob) +} + +// Sinks for benchmarks. +var ( + sessSink *packetSession + errSink error +) + +func BenchmarkReadPacketSession(b *testing.B) { + bodyData := []byte("message body data") + + // TODO(a.garipov): Find a better way to pack these control messages than + // just [binary.Write]. + oobBuf := &bytes.Buffer{} + ctrlMsgHdr := unix.Cmsghdr{ + Len: 24, + Level: unix.SOL_IP, + Type: unix.IP_ORIGDSTADDR, + } + + // TODO(a.garipov): Use binary.NativeEndian in Go 1.21 here and below. + err := binary.Write(oobBuf, binary.LittleEndian, ctrlMsgHdr) + require.NoError(b, err) + + pktInfo := unix.Inet4Pktinfo{ + Spec_dst: *(*[4]byte)(testRAddr.IP), + Addr: *(*[4]byte)(testRAddr.IP), + } + + err = binary.Write(oobBuf, binary.LittleEndian, pktInfo) + require.NoError(b, err) + + oobData := oobBuf.Bytes() + + c := &testMsgUDPReader{ + onReadMsgUDP: func(body, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { + copy(body, bodyData) + copy(oob, oobData) + + return len(bodyData), len(oobData), 0, testRAddr, nil + }, + } + + body := make([]byte, dns.DefaultMsgSize) + oob := make([]byte, netext.IPDstOOBSize) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sessSink, errSink = readPacketSession(c, body, oob) + } + + require.NoError(b, errSink) + require.NotNil(b, sessSink) + + assert.Equal(b, sessSink.raddr, testRAddr) + assert.Equal(b, sessSink.readBody, bodyData) + + // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkReadPacketSession + // BenchmarkReadPacketSession-16 3311841 458.1 ns/op 224 B/op 5 allocs/op +} diff --git a/internal/cmd/access.go b/internal/cmd/access.go new file mode 100644 index 0000000..dbf71dc --- /dev/null +++ b/internal/cmd/access.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + "net/netip" +) + +// accessConfig is the configuration that controls IP and hosts blocking. +type accessConfig struct { + // BlockedQuestionDomains is a list of AdBlock rules used to block access. + BlockedQuestionDomains []string `yaml:"blocked_question_domains"` + + // BlockedClientSubnets is a list of IP addresses or subnets to block. + BlockedClientSubnets []string `yaml:"blocked_client_subnets"` +} + +// validate returns an error if the access configuration is invalid. +func (a *accessConfig) validate() (err error) { + if a == nil { + return errNilConfig + } + + for i, s := range a.BlockedClientSubnets { + // TODO(a.garipov): Use [netutil.ParseSubnet] after refactoring it to + // [netip.Addr]. + _, parseErr := netip.ParseAddr(s) + if parseErr == nil { + continue + } + + _, parseErr = netip.ParsePrefix(s) + if parseErr == nil { + continue + } + + return fmt.Errorf("value %q at index %d: bad ip or cidr: %w", s, i, parseErr) + } + + return nil +} diff --git a/internal/cmd/backend.go b/internal/cmd/backend.go index 175b5b5..b01f158 100644 --- a/internal/cmd/backend.go +++ b/internal/cmd/backend.go @@ -3,10 +3,12 @@ package cmd import ( "context" "fmt" + "net/url" "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/backend" + "github.com/AdguardTeam/AdGuardDNS/internal/backendpb" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" "github.com/AdguardTeam/golibs/netutil" @@ -87,12 +89,14 @@ func setupBillStat( sigHdlr signalHandler, errColl agd.ErrorCollector, ) (rec *billstat.RuntimeRecorder, err error) { - billStatConf := &backend.BillStatConfig{ - BaseEndpoint: netutil.CloneURL(&envs.BillStatURL.URL), + apiURL := netutil.CloneURL(&envs.BillStatURL.URL) + billStatUploader, err := setupBillStatUploader(apiURL, errColl) + if err != nil { + return nil, fmt.Errorf("creating bill stat uploader: %w", err) } rec = billstat.NewRuntimeRecorder(&billstat.RuntimeRecorderConfig{ - Uploader: backend.NewBillStat(billStatConf), + Uploader: billStatUploader, }) refrIvl := conf.RefreshIvl.Duration @@ -127,13 +131,12 @@ func setupProfDB( sigHdlr signalHandler, errColl agd.ErrorCollector, ) (profDB *profiledb.Default, err error) { - profStrgConf := &backend.ProfileStorageConfig{ - BaseEndpoint: netutil.CloneURL(&envs.ProfilesURL.URL), - Now: time.Now, - ErrColl: errColl, + apiURL := netutil.CloneURL(&envs.ProfilesURL.URL) + profStrg, err := setupProfStorage(apiURL, errColl) + if err != nil { + return nil, fmt.Errorf("creating profile storage: %w", err) } - 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) @@ -162,3 +165,55 @@ func setupProfDB( return profDB, nil } + +// Backend API URL schemes. +const ( + schemeHTTP = "http" + schemeHTTPS = "https" + schemeGRPC = "grpc" + schemeGRPCS = "grpcs" +) + +// setupProfStorage creates and returns a profile storage depending on the +// provided API URL. +func setupProfStorage( + apiURL *url.URL, + errColl agd.ErrorCollector, +) (s profiledb.Storage, err error) { + switch apiURL.Scheme { + case schemeGRPC, schemeGRPCS: + return backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{ + Endpoint: apiURL, + ErrColl: errColl, + }) + case schemeHTTP, schemeHTTPS: + return backend.NewProfileStorage(&backend.ProfileStorageConfig{ + BaseEndpoint: apiURL, + Now: time.Now, + ErrColl: errColl, + }), nil + default: + return nil, fmt.Errorf("invalid backend api url: %s", apiURL) + } +} + +// setupBillStatUploader creates and returns a billstat uploader depending on +// the provided API URL. +func setupBillStatUploader( + apiURL *url.URL, + errColl agd.ErrorCollector, +) (s billstat.Uploader, err error) { + switch apiURL.Scheme { + case schemeGRPC, schemeGRPCS: + return backendpb.NewBillStat(&backendpb.BillStatConfig{ + ErrColl: errColl, + Endpoint: apiURL, + }) + case schemeHTTP, schemeHTTPS: + return backend.NewBillStat(&backend.BillStatConfig{ + BaseEndpoint: apiURL, + }), nil + default: + return nil, fmt.Errorf("invalid backend api url: %s", apiURL) + } +} diff --git a/internal/cmd/check.go b/internal/cmd/check.go index 93c6657..5c9d405 100644 --- a/internal/cmd/check.go +++ b/internal/cmd/check.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "net" "net/netip" "net/url" "strings" @@ -53,18 +52,6 @@ func (c *checkConfig) toInternal( sessURL = netutil.CloneURL(&envs.ConsulDNSCheckSessionURL.URL) } - // TODO(a.garipov): Use netip.Addrs in dnscheck, which also means using it - // in dnsmsg.Constructor. - ipv4 := make([]net.IP, len(c.IPv4)) - for i, ip := range c.IPv4 { - ipv4[i] = ip.AsSlice() - } - - ipv6 := make([]net.IP, len(c.IPv6)) - for i, ip := range c.IPv6 { - ipv6[i] = ip.AsSlice() - } - domains := make([]string, len(c.Domains)) for i, d := range c.Domains { domains[i] = strings.ToLower(d) @@ -78,8 +65,8 @@ func (c *checkConfig) toInternal( Domains: domains, NodeLocation: c.NodeLocation, NodeName: c.NodeName, - IPv4: ipv4, - IPv6: ipv6, + IPv4: c.IPv4, + IPv6: c.IPv6, TTL: c.TTL.Duration, } } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 65cc23f..78d73f9 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -8,6 +8,7 @@ import ( "os" "runtime" + "github.com/AdguardTeam/AdGuardDNS/internal/access" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/debugsvc" @@ -15,6 +16,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" + "github.com/AdguardTeam/AdGuardDNS/internal/errcoll" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" @@ -160,6 +162,11 @@ func Main() { sigHdlr.add(btdMgr) + // access + + accessManager, err := access.New(c.Access.BlockedQuestionDomains, c.Access.BlockedClientSubnets) + check(err) + // Server groups messages := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, c.Filters.ResponseTTL.Duration) @@ -239,6 +246,7 @@ func Main() { } dnsConf := &dnssvc.Config{ + AccessManager: accessManager, Messages: messages, SafeBrowsing: hashprefix.NewMatcher(hashStorages), BillStat: billStatRec, @@ -327,5 +335,10 @@ func collectPanics(errColl agd.ErrorCollector) { errColl.Collect(context.Background(), err) + errFlushColl, ok := errColl.(errcoll.ErrorFlushCollector) + if ok { + errFlushColl.Flush() + } + panic(v) } diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 3dc1f7a..c09d046 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -71,6 +71,9 @@ type configuration struct { // Network is the configuration for network listeners. Network *network `yaml:"network"` + // Access is the configuration of the service managing access control. + Access *accessConfig `yaml:"access"` + // AdditionalMetricsInfo is extra information, which is exposed by metrics. AdditionalMetricsInfo additionalInfo `yaml:"additional_metrics_info"` @@ -159,6 +162,9 @@ func (c *configuration) validate() (err error) { }, { validate: c.Network.validate, name: "network", + }, { + validate: c.Access.validate, + name: "access", }, { validate: c.AdditionalMetricsInfo.validate, name: "additional_metrics_info", diff --git a/internal/cmd/conncheck.go b/internal/cmd/conncheck.go index 720f3dd..9013f98 100644 --- a/internal/cmd/conncheck.go +++ b/internal/cmd/conncheck.go @@ -36,10 +36,10 @@ func (c *connCheckConfig) validate() (err error) { // connectivityCheck performs connectivity checks for bind addresses with // provided dialer and probe addresses. For each server group it reviews each -// server bind addresses looking up for an IPv6 addresses. If an IPv6 address -// is found, then additionally to a general probe to IPv4 it will perform a -// check to IPv6 probe address. -func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) error { +// server bind addresses looking up for IPv6 addresses. If an IPv6 address is +// found, then additionally to a general probe to IPv4 it will perform a check +// to IPv6 probe address. +func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) (err error) { probeIPv4 := net.TCPAddrFromAddrPort(connCheck.ProbeIPv4) // General check to IPv4 probe address. @@ -49,48 +49,60 @@ func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) error { } defer func() { - err = conn.Close() - if err != nil { - log.Fatalf("connectivity check: ipv4: %v", err) + closeErr := conn.Close() + if closeErr != nil { + log.Fatalf("connectivity check: ipv4: %v", closeErr) } }() - if containsIPv6BindAddress(c.ServerGroups) { - if (connCheck.ProbeIPv6 == netip.AddrPort{}) { - log.Fatal("connectivity check: no ipv6 probe address in config") - } - - probeIPv6 := net.TCPAddrFromAddrPort(connCheck.ProbeIPv6) - - // Check to IPv6 probe address. - connV6, errV6 := net.DialTCP("tcp6", nil, probeIPv6) - if errV6 != nil { - return fmt.Errorf("connectivity check: ipv6: %w", errV6) - } - - defer func() { - err = connV6.Close() - if err != nil { - log.Fatalf("connectivity check: ipv6: %v", err) - } - }() + if !requireIPv6ConnCheck(c.ServerGroups) { + return nil } + if (connCheck.ProbeIPv6 == netip.AddrPort{}) { + log.Fatal("connectivity check: no ipv6 probe address in config") + } + + probeIPv6 := net.TCPAddrFromAddrPort(connCheck.ProbeIPv6) + + // Check to IPv6 probe address. + connV6, err := net.DialTCP("tcp6", nil, probeIPv6) + if err != nil { + return fmt.Errorf("connectivity check: ipv6: %w", err) + } + + defer func() { + closeErr := connV6.Close() + if closeErr != nil { + log.Fatalf("connectivity check: ipv6: %v", closeErr) + } + }() + return nil } -// containsIPv6BindAddress returns true if provided serverGroups require -// IPv6 connectivity check. -func containsIPv6BindAddress(serverGroups []*agd.ServerGroup) (ok bool) { +// requireIPv6ConnCheck returns true if provided serverGroups require IPv6 +// connectivity check. +func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) { for _, srvGrp := range serverGroups { for _, s := range srvGrp.Servers { - for _, bindData := range s.BindData { - if addr := bindData.AddrPort; addr.IsValid() && addr.Addr().Is6() { - return true - } + if containsIPv6BindAddress(s.BindData) { + return true } } } return false } + +// containsIPv6BindAddress returns true if provided bindData contains valid IPv6 +// address. +func containsIPv6BindAddress(bindData []*agd.ServerBindData) (ok bool) { + for _, bData := range bindData { + if addr := bData.AddrPort; addr.Addr().Is6() { + return true + } + } + + return false +} diff --git a/internal/cmd/ddr.go b/internal/cmd/ddr.go index 09c16f8..bf8667f 100644 --- a/internal/cmd/ddr.go +++ b/internal/cmd/ddr.go @@ -57,8 +57,16 @@ func ddrRecsToSVCBTmpls( tmpls = appendDDRSVCBTmpls(tmpls, msgs, r, target) } - slices.SortStableFunc(tmpls, func(a, b *dns.SVCB) (less bool) { - return a.Priority < b.Priority + // TODO(e.burkov): Use cmp.Compare when updated to go1.21. + slices.SortStableFunc(tmpls, func(a, b *dns.SVCB) (res int) { + switch x, y := a.Priority, b.Priority; { + case x < y: + return -1 + case x > y: + return +1 + default: + return 0 + } }) return targets, tmpls @@ -118,26 +126,16 @@ func (c *ddrConfig) validate() (err error) { } domainSuf := wildcard[2:] - err = netutil.ValidateHostname(domainSuf) + err = errors.Join(netutil.ValidateHostname(domainSuf), r.validate()) if err != nil { - return fmt.Errorf("device_records: %w", err) - } - - err = r.validate() - if err != nil { - return fmt.Errorf("device_records: record for wildcard %q: %w", wildcard, err) + return fmt.Errorf("device_records: wildcard %q: %w", wildcard, err) } } for domain, r := range c.PublicRecords { - err = netutil.ValidateHostname(domain) + err = errors.Join(netutil.ValidateHostname(domain), r.validate()) if err != nil { - return fmt.Errorf("public_records: %w", err) - } - - err = r.validate() - if err != nil { - return fmt.Errorf("public_records: record for domain %q: %w", domain, err) + return fmt.Errorf("public_records: domain %q: %w", domain, err) } } diff --git a/internal/cmd/env.go b/internal/cmd/env.go index defd1f2..1e4a3d2 100644 --- a/internal/cmd/env.go +++ b/internal/cmd/env.go @@ -41,7 +41,7 @@ type environments struct { ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"` FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"` - ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.json"` + ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.pb"` GeoIPASNPath string `env:"GEOIP_ASN_PATH" envDefault:"./asn.mmdb"` GeoIPCountryPath string `env:"GEOIP_COUNTRY_PATH" envDefault:"./country.mmdb"` QueryLogPath string `env:"QUERYLOG_PATH" envDefault:"./querylog.jsonl"` diff --git a/internal/cmd/server.go b/internal/cmd/server.go index f43227a..5e43e00 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -6,7 +6,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/stringutil" @@ -205,10 +204,8 @@ func (s *server) bindData( ifaces := s.BindInterfaces bindData = make([]*agd.ServerBindData, 0, len(ifaces)) for i, iface := range ifaces { - address := string(iface.ID) - for j, subnet := range iface.Subnets { - var lc netext.ListenConfig + var lc *bindtodevice.ListenConfig lc, err = btdMgr.ListenConfig(iface.ID, subnet) if err != nil { const errStr = "bind_interface at index %d: subnet at index %d: %w" @@ -218,7 +215,7 @@ func (s *server) bindData( bindData = append(bindData, &agd.ServerBindData{ ListenConfig: lc, - Address: address, + Address: lc.Addr(), }) } } diff --git a/internal/cmd/tls.go b/internal/cmd/tls.go index 88a4cd7..e560058 100644 --- a/internal/cmd/tls.go +++ b/internal/cmd/tls.go @@ -136,14 +136,15 @@ func (certs tlsConfigCerts) toInternal() (conf *tls.Config, err error) { return nil, fmt.Errorf("certificate at index %d: %w", i, err) } - tlsCerts[i] = cert - var leaf *x509.Certificate leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, fmt.Errorf("invalid leaf, certificate at index %d: %w", i, err) } + cert.Leaf = leaf + tlsCerts[i] = cert + authAlgo, subj := leaf.PublicKeyAlgorithm.String(), leaf.Subject.String() metrics.TLSCertificateInfo.With(prometheus.Labels{ "auth_algo": authAlgo, diff --git a/internal/dnscheck/consul.go b/internal/dnscheck/consul.go index d48a5f0..7817ddc 100644 --- a/internal/dnscheck/consul.go +++ b/internal/dnscheck/consul.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "net" "net/http" "net/netip" "net/url" @@ -23,6 +22,7 @@ import ( "github.com/miekg/dns" cache "github.com/patrickmn/go-cache" "github.com/prometheus/client_golang/prometheus" + "golang.org/x/exp/slices" "golang.org/x/time/rate" ) @@ -46,8 +46,8 @@ type Consul struct { nodeLocation string nodeName string - ipv4 []net.IP - ipv6 []net.IP + ipv4 []netip.Addr + ipv6 []netip.Addr } // ConsulConfig is the configuration structure for Consul KV based DNS checker. @@ -77,10 +77,10 @@ type ConsulConfig struct { NodeName string // IPv4 are the IPv4 addresses to respond with to A requests. - IPv4 []net.IP + IPv4 []netip.Addr // IPv6 are the IPv6 addresses to respond with to AAAA requests. - IPv6 []net.IP + IPv6 []netip.Addr // TTL defines, for how long to keep the information about a single client. TTL time.Duration @@ -108,8 +108,8 @@ func NewConsul(c *ConsulConfig) (cc *Consul, err error) { nodeLocation: c.NodeLocation, nodeName: c.NodeName, - ipv4: netutil.CloneIPs(c.IPv4), - ipv6: netutil.CloneIPs(c.IPv6), + ipv4: slices.Clone(c.IPv4), + ipv6: slices.Clone(c.IPv6), } // TODO(e.burkov): Validate also c.ConsulSessionURL? diff --git a/internal/dnscheck/consul_test.go b/internal/dnscheck/consul_test.go index e868e9e..ec2e5d2 100644 --- a/internal/dnscheck/consul_test.go +++ b/internal/dnscheck/consul_test.go @@ -6,6 +6,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/netip" "net/url" "strings" "testing" @@ -222,8 +223,8 @@ func TestConsul_Check(t *testing.T) { conf := &dnscheck.ConsulConfig{ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second), Domains: []string{checkDomain}, - IPv4: []net.IP{{1, 2, 3, 4}}, - IPv6: []net.IP{net.ParseIP("1234::5678")}, + IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")}, + IPv6: []netip.Addr{netip.MustParseAddr("1234::5678")}, } dnsCk, err := dnscheck.NewConsul(conf) diff --git a/internal/dnsdb/http_test.go b/internal/dnsdb/http_test.go index 6bec674..c13759b 100644 --- a/internal/dnsdb/http_test.go +++ b/internal/dnsdb/http_test.go @@ -5,9 +5,9 @@ import ( "compress/gzip" "context" "io" - "net" "net/http" "net/http/httptest" + "net/netip" "net/url" "strings" "testing" @@ -25,7 +25,8 @@ import ( func TestDefault_ServeHTTP(t *testing.T) { const dname = "some-domain.name" - testIP := net.IP{1, 2, 3, 4} + + testIP := netip.MustParseAddr("1.2.3.4") successHdr := http.Header{ httphdr.ContentType: []string{agdhttp.HdrValTextCSV}, @@ -100,7 +101,7 @@ func TestDefault_ServeHTTP(t *testing.T) { db.Record(ctx, m, &agd.RequestInfo{ // Emulate the logic from init middleware. // - // See [dnssvc.initMw.newRequestInfo]. + // See [initial.Middleware.newRequestInfo]. Host: strings.TrimSuffix(m.Question[0].Name, "."), }) } diff --git a/internal/dnsdb/record.go b/internal/dnsdb/record.go index 8975034..a059812 100644 --- a/internal/dnsdb/record.go +++ b/internal/dnsdb/record.go @@ -56,6 +56,7 @@ func answerString(rr dns.RR) (s string) { case *dns.AAAA: return v.AAAA.String() case *dns.CNAME: + // TODO(a.garipov): Consider lowercasing target hostname. return strings.TrimSuffix(v.Target, ".") default: return "" diff --git a/internal/dnsmsg/blockingmode.go b/internal/dnsmsg/blockingmode.go index 048944b..31ad638 100644 --- a/internal/dnsmsg/blockingmode.go +++ b/internal/dnsmsg/blockingmode.go @@ -21,6 +21,8 @@ type BlockingMode interface { // BlockingModeCodec is a wrapper around a BlockingMode that implements the // [json.Marshaler] and [json.Unmarshaler] interfaces. +// +// TODO(s.chzhen): Remove once it's not used anymore. type BlockingModeCodec struct { Mode BlockingMode } diff --git a/internal/dnsmsg/cloner.go b/internal/dnsmsg/cloner.go new file mode 100644 index 0000000..89f685e --- /dev/null +++ b/internal/dnsmsg/cloner.go @@ -0,0 +1,510 @@ +package dnsmsg + +import ( + "fmt" + "net" + + "github.com/AdguardTeam/AdGuardDNS/internal/agdsync" + "github.com/miekg/dns" + "golang.org/x/exp/slices" +) + +// Cloner is a pool that can clone common parts of DNS messages with fewer +// allocations. +// +// TODO(a.garipov): Add ECS/OPT. +// +// TODO(a.garipov): Use. +// +// TODO(a.garipov): Consider merging into [Constructor]. +type Cloner struct { + // Top-level structures. + + msg *agdsync.TypedPool[dns.Msg] + question *agdsync.TypedPool[[]dns.Question] + + // Mostly-answer structures. + + a *agdsync.TypedPool[dns.A] + aaaa *agdsync.TypedPool[dns.AAAA] + cname *agdsync.TypedPool[dns.CNAME] + ptr *agdsync.TypedPool[dns.PTR] + srv *agdsync.TypedPool[dns.SRV] + txt *agdsync.TypedPool[dns.TXT] + + // Mostly-answer custom cloners. + + https *httpsCloner + + // Mostly-NS structures. + + soa *agdsync.TypedPool[dns.SOA] +} + +// NewCloner returns a new properly initialized *Cloner. +func NewCloner() (c *Cloner) { + return &Cloner{ + msg: agdsync.NewTypedPool(func() (v *dns.Msg) { + return &dns.Msg{} + }), + question: agdsync.NewTypedPool(func() (v *[]dns.Question) { + q := make([]dns.Question, 1) + + return &q + }), + + a: agdsync.NewTypedPool(func() (v *dns.A) { + return &dns.A{} + }), + aaaa: agdsync.NewTypedPool(func() (v *dns.AAAA) { + return &dns.AAAA{} + }), + cname: agdsync.NewTypedPool(func() (v *dns.CNAME) { + return &dns.CNAME{} + }), + ptr: agdsync.NewTypedPool(func() (v *dns.PTR) { + return &dns.PTR{} + }), + srv: agdsync.NewTypedPool(func() (v *dns.SRV) { + return &dns.SRV{} + }), + txt: agdsync.NewTypedPool(func() (v *dns.TXT) { + return &dns.TXT{} + }), + + https: newHTTPSCloner(), + + soa: agdsync.NewTypedPool(func() (v *dns.SOA) { + return &dns.SOA{} + }), + } +} + +// Clone returns a deep clone of msg. full is true if msg was cloned entirely +// without the use of [dns.Copy]. +// +// msg must have exactly one question. +// +// TODO(a.garipov): Don't require one question? +func (c *Cloner) Clone(msg *dns.Msg) (clone *dns.Msg, full bool) { + if msg == nil { + return nil, true + } + + clone = c.msg.Get() + + clone.MsgHdr = msg.MsgHdr + clone.Compress = msg.Compress + + clone.Question = *c.question.Get() + clone.Question[0] = msg.Question[0] + + clone.Answer, full = c.appendAnswer(clone.Answer[:0], msg.Answer) + + clone.Ns = clone.Ns[:0] + for _, orig := range msg.Ns { + var nsClone dns.RR + switch orig := orig.(type) { + case *dns.SOA: + ns := c.soa.Get() + *ns = *orig + + nsClone = ns + // TODO(a.garipov): Add more if necessary. + default: + nsClone = dns.Copy(orig) + full = false + } + + clone.Ns = append(clone.Ns, nsClone) + } + + clone.Extra = clone.Extra[:0] + for _, orig := range msg.Extra { + var exClone dns.RR + switch orig := orig.(type) { + // TODO(a.garipov): Add more if necessary. + default: + exClone = dns.Copy(orig) + full = false + } + + clone.Extra = append(clone.Extra, exClone) + } + + return clone, full +} + +// appendAnswer appends deep clones of all resource recornds from original to +// clones and returns it. +func (c *Cloner) appendAnswer(clones, original []dns.RR) (res []dns.RR, full bool) { + full = true + for _, orig := range original { + var ansClone dns.RR + switch orig := orig.(type) { + case *dns.A: + ans := c.a.Get() + ans.Hdr = orig.Hdr + + ans.A = append(ans.A[:0], orig.A...) + + ansClone = ans + case *dns.AAAA: + ans := c.aaaa.Get() + ans.Hdr = orig.Hdr + + ans.AAAA = append(ans.AAAA[:0], orig.AAAA...) + + ansClone = ans + case *dns.CNAME: + ans := c.cname.Get() + *ans = *orig + + ansClone = ans + case *dns.HTTPS: + var httpsFull bool + ansClone, httpsFull = c.https.clone(orig) + full = full && httpsFull + case *dns.PTR: + ans := c.ptr.Get() + *ans = *orig + + ansClone = ans + case *dns.SRV: + ans := c.srv.Get() + *ans = *orig + + ansClone = ans + case *dns.TXT: + ans := c.txt.Get() + ans.Hdr = orig.Hdr + + ans.Txt = append(ans.Txt[:0], orig.Txt...) + + ansClone = ans + default: + ansClone = dns.Copy(orig) + full = false + } + + clones = append(clones, ansClone) + } + + return clones, full +} + +// Put returns structures from msg into c's pools. Neither msg nor any of its +// parts must not be used after this. +// +// msg must have exactly one question. +// +// TODO(a.garipov): Don't require one question? +func (c *Cloner) Put(msg *dns.Msg) { + if msg == nil { + return + } + + c.putAnswers(msg.Answer) + + for _, ns := range msg.Ns { + switch ns := ns.(type) { + case *dns.SOA: + c.soa.Put(ns) + default: + // Go on. + } + } + + for _, ex := range msg.Extra { + // TODO(a.garipov): Add OPT. + _ = ex + } + + c.question.Put(&msg.Question) + + c.msg.Put(msg) +} + +// putAnswers returns answers into c's pools. +func (c *Cloner) putAnswers(answers []dns.RR) { + for _, ans := range answers { + switch ans := ans.(type) { + case *dns.A: + c.a.Put(ans) + case *dns.AAAA: + c.aaaa.Put(ans) + case *dns.CNAME: + c.cname.Put(ans) + case *dns.HTTPS: + c.https.put(ans) + case *dns.PTR: + c.ptr.Put(ans) + case *dns.SRV: + c.srv.Put(ans) + case *dns.TXT: + c.txt.Put(ans) + default: + // Go on. + } + } +} + +// httpsCloner is a pool that can clone common parts of DNS messages of type +// HTTPS with fewer allocations. +type httpsCloner struct { + // Top-level structures. + + rr *agdsync.TypedPool[dns.HTTPS] + + // Values. + + alpn *agdsync.TypedPool[dns.SVCBAlpn] + dohpath *agdsync.TypedPool[dns.SVCBDoHPath] + echconfig *agdsync.TypedPool[dns.SVCBECHConfig] + ipv4hint *agdsync.TypedPool[dns.SVCBIPv4Hint] + ipv6hint *agdsync.TypedPool[dns.SVCBIPv6Hint] + local *agdsync.TypedPool[dns.SVCBLocal] + mandatory *agdsync.TypedPool[dns.SVCBMandatory] + noDefALPN *agdsync.TypedPool[dns.SVCBNoDefaultAlpn] + port *agdsync.TypedPool[dns.SVCBPort] + + // Miscellaneous. + + ip *agdsync.TypedPool[net.IP] +} + +// newHTTPSCloner returns a new properly initialized *httpsCloner. +func newHTTPSCloner() (c *httpsCloner) { + return &httpsCloner{ + rr: agdsync.NewTypedPool(func() (v *dns.HTTPS) { + return &dns.HTTPS{} + }), + + alpn: agdsync.NewTypedPool(func() (v *dns.SVCBAlpn) { + return &dns.SVCBAlpn{} + }), + dohpath: agdsync.NewTypedPool(func() (v *dns.SVCBDoHPath) { + return &dns.SVCBDoHPath{} + }), + echconfig: agdsync.NewTypedPool(func() (v *dns.SVCBECHConfig) { + return &dns.SVCBECHConfig{} + }), + ipv4hint: agdsync.NewTypedPool(func() (v *dns.SVCBIPv4Hint) { + return &dns.SVCBIPv4Hint{} + }), + ipv6hint: agdsync.NewTypedPool(func() (v *dns.SVCBIPv6Hint) { + return &dns.SVCBIPv6Hint{} + }), + local: agdsync.NewTypedPool(func() (v *dns.SVCBLocal) { + return &dns.SVCBLocal{} + }), + mandatory: agdsync.NewTypedPool(func() (v *dns.SVCBMandatory) { + return &dns.SVCBMandatory{} + }), + noDefALPN: agdsync.NewTypedPool(func() (v *dns.SVCBNoDefaultAlpn) { + return &dns.SVCBNoDefaultAlpn{} + }), + port: agdsync.NewTypedPool(func() (v *dns.SVCBPort) { + return &dns.SVCBPort{} + }), + + ip: agdsync.NewTypedPool(func() (v *net.IP) { + // Use the IPv6 length to increase the effectiveness of the pool. + ip := make(net.IP, 16) + + return &ip + }), + } +} + +// clone returns a deep clone of rr. full is true if rr was cloned entirely +// without the use of [dns.Copy]. +func (c *httpsCloner) clone(rr *dns.HTTPS) (clone *dns.HTTPS, full bool) { + if rr == nil { + return nil, true + } + + clone = c.rr.Get() + + clone.Hdr = rr.Hdr + clone.Priority = rr.Priority + clone.Target = rr.Target + + clone.Value = clone.Value[:0] + for _, orig := range rr.Value { + valClone, knownKV := c.cloneKV(orig) + if !knownKV { + // This branch is only reached if there is a new SVCB key-value type + // in miekg/dns. Give up and just use their copy function. + return dns.Copy(rr).(*dns.HTTPS), false + } + + clone.Value = append(clone.Value, valClone) + } + + return clone, true +} + +// cloneKV returns a deep clone of orig. full is true if orig was recognized. +func (c *httpsCloner) cloneKV(orig dns.SVCBKeyValue) (clone dns.SVCBKeyValue, known bool) { + switch orig := orig.(type) { + case *dns.SVCBAlpn: + v := c.alpn.Get() + + v.Alpn = append(v.Alpn[:0], orig.Alpn...) + + clone = v + case *dns.SVCBDoHPath: + v := c.dohpath.Get() + *v = *orig + + clone = v + case *dns.SVCBECHConfig: + v := c.echconfig.Get() + + v.ECH = append(v.ECH[:0], orig.ECH...) + + clone = v + case *dns.SVCBIPv4Hint: + v := c.ipv4hint.Get() + + v.Hint = c.appendIPs(v.Hint[:0], orig.Hint) + + clone = v + case *dns.SVCBIPv6Hint: + v := c.ipv6hint.Get() + + v.Hint = c.appendIPs(v.Hint[:0], orig.Hint) + + clone = v + case *dns.SVCBLocal: + v := c.local.Get() + v.KeyCode = orig.KeyCode + + v.Data = append(v.Data[:0], orig.Data...) + + clone = v + case *dns.SVCBMandatory: + v := c.mandatory.Get() + + v.Code = append(v.Code[:0], orig.Code...) + + clone = v + case *dns.SVCBNoDefaultAlpn: + clone = c.noDefALPN.Get() + case *dns.SVCBPort: + v := c.port.Get() + *v = *orig + + clone = v + default: + // This branch is only reached if there is a new SVCB key-value type + // in miekg/dns. + return nil, false + } + + return clone, true +} + +// appendIPs appends the clones of IP addresses from orig to hints and returns +// the resulting slice. clone is allocated as a single continuous slice. +func (c *httpsCloner) appendIPs(hints, orig []net.IP) (clone []net.IP) { + if len(orig) == 0 { + if orig == nil { + return nil + } + + return []net.IP{} + } + + // Use a single large slice and subslice it to make it easier to maintain a + // pool of these. + ips := *c.ip.Get() + ips = ips[:0] + + neededCap := 0 + for _, origIP := range orig { + neededCap += len(origIP) + } + + ips = slices.Grow(ips, neededCap) + + hints = hints[:0] + for _, origIP := range orig { + ips = append(ips, origIP...) + origLen := len(origIP) + lastIdx := len(ips) + hints = append(hints, ips[lastIdx-origLen:lastIdx]) + } + + return hints +} + +// put returns structures from rr into c's pools. +func (c *httpsCloner) put(rr *dns.HTTPS) { + if rr == nil { + return + } + + for _, kv := range rr.Value { + c.putKV(kv) + } + + c.rr.Put(rr) +} + +// putKV returns structures from kv into c's pools. +func (c *httpsCloner) putKV(kv dns.SVCBKeyValue) { + switch kv := kv.(type) { + case *dns.SVCBAlpn: + c.alpn.Put(kv) + case *dns.SVCBDoHPath: + c.dohpath.Put(kv) + case *dns.SVCBECHConfig: + c.echconfig.Put(kv) + case *dns.SVCBIPv4Hint: + putIPHint(c, kv) + case *dns.SVCBIPv6Hint: + putIPHint(c, kv) + case *dns.SVCBLocal: + c.local.Put(kv) + case *dns.SVCBMandatory: + c.mandatory.Put(kv) + case *dns.SVCBNoDefaultAlpn: + c.noDefALPN.Put(kv) + case *dns.SVCBPort: + c.port.Put(kv) + default: + // This branch is only reached if there is a new SVCB key-value type + // in miekg/dns. Noting to do. + } +} + +// putIPHint is a generic helper that returns the structures of kv into c. +func putIPHint[T *dns.SVCBIPv4Hint | *dns.SVCBIPv6Hint](c *httpsCloner, kv T) { + switch kv := any(kv).(type) { + case *dns.SVCBIPv4Hint: + // TODO(a.garipov): Put the common code above the switch when Go learns + // about common fields between types. + if len(kv.Hint) > 0 { + // Assume that the array underlying these slices is a single and + // continuous one. + c.ip.Put(&kv.Hint[0]) + } + + c.ipv4hint.Put(kv) + case *dns.SVCBIPv6Hint: + // TODO(a.garipov): Put the common code above the switch when Go learns + // about common fields between types. + if len(kv.Hint) > 0 { + // Assume that the array underlying these slices is a single and + // continuous one. + c.ip.Put(&kv.Hint[0]) + } + + c.ipv6hint.Put(kv) + default: + // Must not happen, because there is a strict type parameter above. + panic(fmt.Errorf("bad type %T", kv)) + } +} diff --git a/internal/dnsmsg/cloner_test.go b/internal/dnsmsg/cloner_test.go new file mode 100644 index 0000000..c79c64f --- /dev/null +++ b/internal/dnsmsg/cloner_test.go @@ -0,0 +1,280 @@ +package dnsmsg_test + +import ( + "net" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// clonerTestCase is the type for the common test cases for the cloner tests and +// benchmarks. +type clonerTestCase struct { + msg *dns.Msg + wantFull assert.BoolAssertionFunc + name string +} + +// clonerTestCases are the common test cases for the clone benchmarks. +var clonerTestCases = []clonerTestCase{{ + msg: dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET), + name: "req_a", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewA(testFQDN, 10, testIPv4), + }, + ), + name: "resp_a", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewA(testFQDN, 10, testIPv4), + dnsservertest.NewA(testFQDN, 10, testIPv4.Next()), + }, + ), + name: "resp_a_many", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewA(testFQDN, 10, testIPv4), + }, + dnsservertest.SectionNs{ + dnsservertest.NewSOA(testFQDN, 10, "ns.example.", "mbox.example."), + }, + ), + name: "resp_a_soa", + wantFull: assert.True, +}, { + msg: dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET), + name: "req_aaaa", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewAAAA(testFQDN, 10, testIPv6), + }, + ), + name: "resp_aaaa", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewCNAME(testFQDN, 10, "cname.example."), + dnsservertest.NewA("cname.example.", 10, testIPv4), + }, + ), + name: "resp_cname_a", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq("4.3.2.1.in-addr.arpa", dns.TypePTR, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewPTR("4.3.2.1.in-addr.arpa", 10, "ptr.example."), + }, + ), + name: "resp_ptr", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeTXT, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewTXT(testFQDN, 10, "a", "b", "c"), + }, + ), + name: "resp_txt", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeSRV, dns.ClassINET), + dnsservertest.SectionAnswer{ + dnsservertest.NewSRV(testFQDN, 10, "target.example.", 1, 1, 8080), + }, + ), + name: "resp_srv", + wantFull: assert.True, +}, { + msg: dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeDNSKEY, dns.ClassINET), + dnsservertest.SectionAnswer{ + &dns.DNSKEY{}, + }, + ), + name: "resp_not_full", + wantFull: assert.False, +}, { + msg: newHTTPSResp([]dns.SVCBKeyValue{ + &dns.SVCBAlpn{Alpn: []string{"http/1.1", "h2", "h3"}}, + &dns.SVCBDoHPath{Template: "/dns-query"}, + &dns.SVCBECHConfig{ECH: []byte{0, 1, 2, 3}}, + &dns.SVCBIPv4Hint{Hint: []net.IP{ + testIPv4.AsSlice(), + testIPv4.Next().AsSlice(), + }}, + &dns.SVCBIPv6Hint{Hint: []net.IP{ + testIPv6.AsSlice(), + testIPv6.Next().AsSlice(), + }}, + &dns.SVCBLocal{KeyCode: dns.SVCBKey(1234), Data: []byte{3, 2, 1, 0}}, + &dns.SVCBMandatory{Code: []dns.SVCBKey{dns.SVCB_ALPN}}, + &dns.SVCBNoDefaultAlpn{}, + &dns.SVCBPort{Port: 443}, + }), + name: "resp_https", + wantFull: assert.True, +}, { + msg: newHTTPSResp([]dns.SVCBKeyValue{ + &dns.SVCBIPv4Hint{Hint: []net.IP{}}, + &dns.SVCBIPv6Hint{Hint: []net.IP{}}, + }), + name: "resp_https_empty_hint", + wantFull: assert.True, +}} + +// newHTTPSResp is a hepler that returns a response of type HTTPS with the given +// parameter values. +func newHTTPSResp(kv []dns.SVCBKeyValue) (resp *dns.Msg) { + ans := &dns.HTTPS{ + SVCB: dns.SVCB{ + Hdr: dns.RR_Header{ + Name: testFQDN, + Rrtype: dns.TypeHTTPS, + Class: dns.ClassINET, + Ttl: 10, + }, + Priority: 10, + Target: testFQDN, + Value: kv, + }, + } + + return dnsservertest.NewResp( + dns.RcodeSuccess, + dnsservertest.NewReq(testFQDN, dns.TypeHTTPS, dns.ClassINET), + dnsservertest.SectionAnswer{ans}, + ) +} + +func TestCloner_Clone(t *testing.T) { + c := dnsmsg.NewCloner() + + for _, tc := range clonerTestCases { + t.Run(tc.name, func(t *testing.T) { + clone, full := c.Clone(tc.msg) + assert.NotSame(t, tc.msg, clone) + assert.Equal(t, tc.msg, clone) + tc.wantFull(t, full) + + // Check again after putting it back. + c.Put(clone) + + clone, full = c.Clone(tc.msg) + assert.NotSame(t, tc.msg, clone) + assert.Equal(t, tc.msg, clone) + tc.wantFull(t, full) + }) + } +} + +// Sinks for benchmarks +var ( + msgSink *dns.Msg + boolSink bool +) + +func BenchmarkClone(b *testing.B) { + for _, tc := range clonerTestCases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + msgSink = dnsmsg.Clone(tc.msg) + } + + require.Equal(b, tc.msg, msgSink) + }) + } + + // Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkClone/req_a-16 32849714 231.7 ns/op 168 B/op 2 allocs/op + // BenchmarkClone/resp_a-16 12051967 509.1 ns/op 256 B/op 5 allocs/op + // BenchmarkClone/resp_a_many-16 8579755 669.4 ns/op 344 B/op 7 allocs/op + // BenchmarkClone/resp_a_soa-16 10393932 681.9 ns/op 368 B/op 6 allocs/op + // BenchmarkClone/req_aaaa-16 25616247 232.1 ns/op 168 B/op 2 allocs/op + // BenchmarkClone/resp_aaaa-16 14519920 493.4 ns/op 264 B/op 5 allocs/op + // BenchmarkClone/resp_cname_a-16 8652282 662.2 ns/op 320 B/op 6 allocs/op + // BenchmarkClone/resp_ptr-16 13558555 370.0 ns/op 232 B/op 4 allocs/op + // BenchmarkClone/resp_txt-16 12322016 532.7 ns/op 296 B/op 5 allocs/op + // BenchmarkClone/resp_srv-16 15878784 396.3 ns/op 248 B/op 4 allocs/op + // BenchmarkClone/resp_not_full-16 15718658 384.6 ns/op 248 B/op 4 allocs/op + // BenchmarkClone/resp_https-16 2621149 2020 ns/op 880 B/op 24 allocs/op + // BenchmarkClone/resp_https_empty_hint-16 6829873 890.8 ns/op 424 B/op 8 allocs/op +} + +func BenchmarkCloner_Clone(b *testing.B) { + c := dnsmsg.NewCloner() + + for _, tc := range clonerTestCases { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + msgSink, boolSink = c.Clone(tc.msg) + if i < b.N-1 { + // Don't put the last one to be sure that we can compare + // that one. + c.Put(msgSink) + } + } + + require.Equal(b, tc.msg, msgSink) + tc.wantFull(b, boolSink) + }) + } + + // Most recent results, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/querylog + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkCloner_Clone/req_a-16 163590546 36.33 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_a-16 100000000 56.55 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_a_many-16 72498543 84.52 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_a_soa-16 81750753 73.07 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/req_aaaa-16 165287482 39.00 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_aaaa-16 99625165 59.56 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_cname_a-16 72154432 81.15 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_ptr-16 100418211 60.88 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_txt-16 80963180 73.66 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_srv-16 89021206 69.35 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_not_full-16 31277523 187.6 ns/op 64 B/op 1 allocs/op + // BenchmarkCloner_Clone/resp_https-16 14601229 396.3 ns/op 0 B/op 0 allocs/op + // BenchmarkCloner_Clone/resp_https_empty_hint-16 45725181 127.4 ns/op 0 B/op 0 allocs/op +} diff --git a/internal/dnsmsg/constructor.go b/internal/dnsmsg/constructor.go index e1cd697..9e7e06e 100644 --- a/internal/dnsmsg/constructor.go +++ b/internal/dnsmsg/constructor.go @@ -2,10 +2,9 @@ package dnsmsg import ( "fmt" - "net" + "net/netip" "time" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" ) @@ -38,7 +37,7 @@ func (c *Constructor) NewBlockedRespMsg(req *dns.Msg) (msg *dns.Msg, err error) case *BlockingModeNullIP: switch qt := req.Question[0].Qtype; qt { case dns.TypeA, dns.TypeAAAA: - return c.NewIPRespMsg(req, nil) + return c.NewIPRespMsg(req, netip.Addr{}) default: return c.NewMsgNODATA(req), nil } @@ -62,11 +61,11 @@ func (c *Constructor) newBlockedCustomIPRespMsg( switch qt := req.Question[0].Qtype; qt { case dns.TypeA: if m.IPv4.IsValid() { - return c.NewIPRespMsg(req, m.IPv4.AsSlice()) + return c.NewIPRespMsg(req, m.IPv4) } case dns.TypeAAAA: if m.IPv6.IsValid() { - return c.NewIPRespMsg(req, m.IPv6.AsSlice()) + return c.NewIPRespMsg(req, m.IPv6) } default: // Go on. @@ -78,7 +77,7 @@ func (c *Constructor) newBlockedCustomIPRespMsg( // NewIPRespMsg returns a DNS A or AAAA response message with the given IP // addresses. If any IP address is nil, it is replaced by an unspecified (aka // null) IP. The TTL is also set to c.FilteredResponseTTL. -func (c *Constructor) NewIPRespMsg(req *dns.Msg, ips ...net.IP) (msg *dns.Msg, err error) { +func (c *Constructor) NewIPRespMsg(req *dns.Msg, ips ...netip.Addr) (msg *dns.Msg, err error) { switch qt := req.Question[0].Qtype; qt { case dns.TypeA: return c.newMsgA(req, ips...) @@ -205,40 +204,38 @@ func (c *Constructor) newHdrWithClass(req *dns.Msg, rrType RRType, cl dns.Class) } // NewAnsA returns a new resource record with an IPv4 address. ip must be an -// IPv4 address. If ip is nil, it is replaced by an unspecified (aka null) IP, -// 0.0.0.0. -func (c *Constructor) NewAnsA(req *dns.Msg, ip net.IP) (ans *dns.A, err error) { - var ip4 net.IP - if ip == nil { - ip4 = net.IP{0, 0, 0, 0} - } else if err = netutil.ValidateIP(ip); err != nil { - return nil, err - } else if ip4 = ip.To4(); ip4 == nil { +// IPv4 address. If ip is a zero netip.Addr, it is replaced by an unspecified +// (aka null) IP, 0.0.0.0. +func (c *Constructor) NewAnsA(req *dns.Msg, ip netip.Addr) (ans *dns.A, err error) { + if ip == (netip.Addr{}) { + ip = netip.IPv4Unspecified() + } else if !ip.Is4() { return nil, fmt.Errorf("bad ipv4: %s", ip) } + data := ip.As4() + return &dns.A{ Hdr: c.newHdr(req, dns.TypeA), - A: ip4, + A: data[:], }, nil } // NewAnsAAAA returns a new resource record with an IPv6 address. ip must be an -// IPv6 address. If ip is nil, it is replaced by an unspecified (aka null) IP, -// [::]. -func (c *Constructor) NewAnsAAAA(req *dns.Msg, ip net.IP) (ans *dns.AAAA, err error) { - var ip6 net.IP - if ip == nil { - ip6 = net.IPv6unspecified - } else if err = netutil.ValidateIP(ip); err != nil { - return nil, err - } else { - ip6 = ip.To16() +// IPv6 address. If ip is a zero netip.Addr, it is replaced by an unspecified +// (aka null) IP, [::]. +func (c *Constructor) NewAnsAAAA(req *dns.Msg, ip netip.Addr) (ans *dns.AAAA, err error) { + if ip == (netip.Addr{}) { + ip = netip.IPv6Unspecified() + } else if !ip.Is6() { + return nil, fmt.Errorf("bad ipv6: %s", ip) } + data := ip.As16() + return &dns.AAAA{ Hdr: c.newHdr(req, dns.TypeAAAA), - AAAA: ip6, + AAAA: data[:], }, nil } @@ -358,7 +355,7 @@ func (c *Constructor) NewRespMsg(req *dns.Msg) (resp *dns.Msg) { // newMsgA returns a new DNS response with the given IPv4 addresses. If any IP // address is nil, it is replaced by an unspecified (aka null) IP, 0.0.0.0. -func (c *Constructor) newMsgA(req *dns.Msg, ips ...net.IP) (msg *dns.Msg, err error) { +func (c *Constructor) newMsgA(req *dns.Msg, ips ...netip.Addr) (msg *dns.Msg, err error) { msg = c.NewRespMsg(req) for i, ip := range ips { var ans dns.RR @@ -375,7 +372,7 @@ func (c *Constructor) newMsgA(req *dns.Msg, ips ...net.IP) (msg *dns.Msg, err er // newMsgAAAA returns a new DNS response with the given IPv6 addresses. If any // IP address is nil, it is replaced by an unspecified (aka null) IP, [::]. -func (c *Constructor) newMsgAAAA(req *dns.Msg, ips ...net.IP) (msg *dns.Msg, err error) { +func (c *Constructor) newMsgAAAA(req *dns.Msg, ips ...netip.Addr) (msg *dns.Msg, err error) { msg = c.NewRespMsg(req) for i, ip := range ips { var ans dns.RR diff --git a/internal/dnsmsg/constructor_test.go b/internal/dnsmsg/constructor_test.go index 7aa553b..6b1821f 100644 --- a/internal/dnsmsg/constructor_test.go +++ b/internal/dnsmsg/constructor_test.go @@ -2,7 +2,6 @@ package dnsmsg_test import ( "net" - "net/netip" "strings" "testing" @@ -75,9 +74,6 @@ func TestConstructor_NewBlockedRespMsg_nullIP(t *testing.T) { } func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) { - wantIPv4 := netip.MustParseAddr("1.2.3.4") - wantIPv6 := netip.MustParseAddr("1234::cdef") - testCases := []struct { messages *dnsmsg.Constructor name string @@ -85,22 +81,22 @@ func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) { wantAAAA bool }{{ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{ - IPv4: wantIPv4, - IPv6: wantIPv6, + IPv4: testIPv4, + IPv6: testIPv6, }, testFltRespTTL), name: "both", wantA: true, wantAAAA: true, }, { messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{ - IPv4: wantIPv4, + IPv4: testIPv4, }, testFltRespTTL), name: "ipv4_only", wantA: true, wantAAAA: false, }, { messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{ - IPv6: wantIPv6, + IPv6: testIPv6, }, testFltRespTTL), name: "ipv6_only", wantA: false, @@ -120,7 +116,7 @@ func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) { require.Len(t, respA.Answer, 1) a := testutil.RequireTypeAssert[*dns.A](t, respA.Answer[0]) - assert.Equal(t, net.IP(wantIPv4.AsSlice()), a.A) + assert.Equal(t, net.IP(testIPv4.AsSlice()), a.A) } else { assert.Empty(t, respA.Answer) } @@ -136,7 +132,7 @@ func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) { require.Len(t, respAAAA.Answer, 1) aaaa := testutil.RequireTypeAssert[*dns.AAAA](t, respAAAA.Answer[0]) - assert.Equal(t, net.IP(wantIPv6.AsSlice()), aaaa.AAAA) + assert.Equal(t, net.IP(testIPv6.AsSlice()), aaaa.AAAA) } else { assert.Empty(t, respAAAA.Answer) } diff --git a/internal/dnsmsg/dnsmsg_test.go b/internal/dnsmsg/dnsmsg_test.go index cf1e0d6..1605431 100644 --- a/internal/dnsmsg/dnsmsg_test.go +++ b/internal/dnsmsg/dnsmsg_test.go @@ -31,6 +31,12 @@ const ( testFQDN = testDomain + "." ) +// Common IP addresses for tests. +var ( + testIPv4 = netip.MustParseAddr("1.2.3.4") + testIPv6 = netip.MustParseAddr("1234::cdef") +) + // Common test constants. const ( ipv4MaskBits = 24 diff --git a/internal/dnsmsg/svcbmsg_test.go b/internal/dnsmsg/svcbmsg_test.go index 3a80ac6..3b32f9e 100644 --- a/internal/dnsmsg/svcbmsg_test.go +++ b/internal/dnsmsg/svcbmsg_test.go @@ -14,14 +14,6 @@ import ( "github.com/stretchr/testify/require" ) -var ( - // testIPv4 is an IPv4 for tests. - testIPv4 = netip.MustParseAddr("1.2.3.4") - - // testIPv6 is an IPv6 for tests. - testIPv6 = netip.MustParseAddr("::1") -) - func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) { // Preconditions. diff --git a/internal/dnsserver/cache/cache_test.go b/internal/dnsserver/cache/cache_test.go index 615808c..1483a71 100644 --- a/internal/dnsserver/cache/cache_test.go +++ b/internal/dnsserver/cache/cache_test.go @@ -3,6 +3,7 @@ package cache_test import ( "context" "net" + "net/netip" "testing" "time" @@ -26,6 +27,7 @@ func TestMiddleware_Wrap(t *testing.T) { defaultTTL uint32 = 3600 ) + reqAddr := netip.MustParseAddr("1.2.3.4") testTTL := 60 * time.Second aReq := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET) @@ -44,7 +46,7 @@ func TestMiddleware_Wrap(t *testing.T) { }{{ req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, defaultTTL, reqAddr), }), name: "simple_a", wantNumReq: 1, @@ -114,7 +116,7 @@ func TestMiddleware_Wrap(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 0, reqAddr), }), name: "expired_one", wantNumReq: N, @@ -123,7 +125,7 @@ func TestMiddleware_Wrap(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 10, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 10, reqAddr), }), name: "override_ttl_ok", wantNumReq: 1, @@ -132,7 +134,7 @@ func TestMiddleware_Wrap(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 1000, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 1000, reqAddr), }), name: "override_ttl_max", wantNumReq: 1, @@ -141,7 +143,7 @@ func TestMiddleware_Wrap(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 0, reqAddr), }), name: "override_ttl_zero", wantNumReq: N, @@ -150,7 +152,7 @@ func TestMiddleware_Wrap(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, servFailMaxCacheTTL, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, servFailMaxCacheTTL, reqAddr), }), name: "override_ttl_servfail", wantNumReq: 1, diff --git a/internal/dnsserver/dnsservertest/msg.go b/internal/dnsserver/dnsservertest/msg.go index 272562d..e2c144a 100644 --- a/internal/dnsserver/dnsservertest/msg.go +++ b/internal/dnsserver/dnsservertest/msg.go @@ -2,6 +2,7 @@ package dnsservertest import ( "net" + "net/netip" "testing" "github.com/AdguardTeam/golibs/testutil" @@ -127,6 +128,38 @@ func NewResp(rcode int, req *dns.Msg, rrs ...RRSection) (resp *dns.Msg) { return resp } +// NewA constructs the new resource record of type A. a must be a valid 4-byte +// IPv4-address. +func NewA(name string, ttl uint32, a netip.Addr) (rr dns.RR) { + data := a.As4() + + return &dns.A{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: ttl, + }, + A: data[:], + } +} + +// NewAAAA constructs the new resource record of type AAAA. aaaa must be a +// valid 16-byte IPv6-address. +func NewAAAA(name string, ttl uint32, aaaa netip.Addr) (rr dns.RR) { + data := aaaa.As16() + + return &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: ttl, + }, + AAAA: data[:], + } +} + // NewCNAME constructs the new resource record of type CNAME. func NewCNAME(name string, ttl uint32, target string) (rr dns.RR) { return &dns.CNAME{ @@ -140,39 +173,20 @@ func NewCNAME(name string, ttl uint32, target string) (rr dns.RR) { } } -// NewA constructs the new resource record of type A. a must be a valid 4-byte -// IPv4-address. -func NewA(name string, ttl uint32, a net.IP) (rr dns.RR) { - return &dns.A{ - Hdr: dns.RR_Header{ - Name: dns.Fqdn(name), - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: ttl, - }, - A: a, - } -} - -// NewAAAA constructs the new resource record of type AAAA. aaaa must be a -// valid 16-byte IPv6-address. -func NewAAAA(name string, ttl uint32, aaaa net.IP) (rr dns.RR) { - return &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: dns.Fqdn(name), - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: ttl, - }, - AAAA: aaaa, - } -} - // 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) { +func NewHTTPS(name string, ttl uint32, v4Hints, v6Hints []netip.Addr) (rr dns.RR) { + v4Hint := &dns.SVCBIPv4Hint{} + for _, ip := range v4Hints { + v4Hint.Hint = append(v4Hint.Hint, ip.AsSlice()) + } + v6Hint := &dns.SVCBIPv6Hint{} + for _, ip := range v6Hints { + v6Hint.Hint = append(v6Hint.Hint, ip.AsSlice()) + } + svcb := dns.SVCB{ Hdr: dns.RR_Header{ Name: dns.Fqdn(name), @@ -181,10 +195,7 @@ func NewHTTPS(name string, ttl uint32, v4Hint, v6Hint []net.IP) (rr dns.RR) { Ttl: ttl, }, Target: dns.Fqdn(name), - Value: []dns.SVCBKeyValue{ - &dns.SVCBIPv4Hint{Hint: v4Hint}, - &dns.SVCBIPv6Hint{Hint: v6Hint}, - }, + Value: []dns.SVCBKeyValue{v4Hint, v6Hint}, } return &dns.HTTPS{ @@ -192,6 +203,49 @@ func NewHTTPS(name string, ttl uint32, v4Hint, v6Hint []net.IP) (rr dns.RR) { } } +// NewPTR constructs the new resource record of type PTR. +func NewPTR(name string, ttl uint32, target string) (rr dns.RR) { + return &dns.PTR{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypePTR, + Class: dns.ClassINET, + Ttl: ttl, + }, + Ptr: dns.Fqdn(target), + } +} + +// NewSRV constructs the new resource record of type SRV. +func NewSRV(name string, ttl uint32, target string, prio, weight, port uint16) (rr dns.RR) { + return &dns.SRV{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + Ttl: ttl, + }, + Priority: prio, + Weight: weight, + Port: port, + Target: target, + } +} + +// NewTXT constructs the new resource record of type TXT. txts are put into the +// TXT record as is. +func NewTXT(name string, ttl uint32, txts ...string) (rr dns.RR) { + return &dns.TXT{ + Hdr: dns.RR_Header{ + Name: dns.Fqdn(name), + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + Ttl: ttl, + }, + Txt: txts, + } +} + // 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/dnsservertest/msg_test.go b/internal/dnsserver/dnsservertest/msg_test.go index 88e9682..1d30be0 100644 --- a/internal/dnsserver/dnsservertest/msg_test.go +++ b/internal/dnsserver/dnsservertest/msg_test.go @@ -2,7 +2,7 @@ package dnsservertest_test import ( "fmt" - "net" + "net/netip" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/golibs/netutil" @@ -45,7 +45,7 @@ func ExampleNewResp() { m = dnsservertest.NewResp(dns.RcodeSuccess, m, dnsservertest.SectionAnswer{ dnsservertest.NewCNAME(testFQDN, 3600, realTestFQDN), - dnsservertest.NewA(realTestFQDN, 3600, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(realTestFQDN, 3600, netip.MustParseAddr("1.2.3.4")), }, dnsservertest.SectionNs{ dnsservertest.NewSOA(realTestFQDN, 1000, "ns."+realTestFQDN, "mbox."+realTestFQDN), dnsservertest.NewNS(testFQDN, 1000, "ns."+testFQDN), diff --git a/internal/dnsserver/go.mod b/internal/dnsserver/go.mod index 3a83fb1..2ff270a 100644 --- a/internal/dnsserver/go.mod +++ b/internal/dnsserver/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver go 1.20 require ( - github.com/AdguardTeam/golibs v0.13.6 + github.com/AdguardTeam/golibs v0.14.0 github.com/ameshkov/dnscrypt/v2 v2.2.7 github.com/ameshkov/dnsstamps v1.0.3 github.com/bluele/gcache v0.0.2 @@ -11,11 +11,11 @@ require ( 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.15.1 - github.com/quic-go/quic-go v0.35.1 + github.com/quic-go/quic-go v0.38.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/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 ) require ( @@ -27,21 +27,20 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // 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.10.0 // indirect + github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/pmezard/go-difflib v1.0.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.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 + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/dnsserver/go.sum b/internal/dnsserver/go.sum index 7b09c3b..400bc57 100644 --- a/internal/dnsserver/go.sum +++ b/internal/dnsserver/go.sum @@ -1,5 +1,5 @@ -github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ= -github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk= +github.com/AdguardTeam/golibs v0.14.0 h1:/vfJshXBVaevMuBgzAIr+F64XdNqZL+j9F33GXJmgeQ= +github.com/AdguardTeam/golibs v0.14.0/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= @@ -29,8 +29,7 @@ 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-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= 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= @@ -38,9 +37,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= -github.com/onsi/ginkgo/v2 v2.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/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/panjf2000/ants/v2 v2.7.5 h1:/vhh0Hza9G1vP1PdCj9hl6MUzCRbmtcTJL0OsnmytuU= github.com/panjf2000/ants/v2 v2.7.5/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo= @@ -57,12 +55,8 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi 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= -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/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= github.com/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= @@ -76,18 +70,14 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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.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/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= 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= @@ -98,18 +88,15 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w 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.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/dnsserver/normalize.go b/internal/dnsserver/normalize.go index c458d90..76a2170 100644 --- a/internal/dnsserver/normalize.go +++ b/internal/dnsserver/normalize.go @@ -9,7 +9,7 @@ import ( // responsePaddingMaxSize is used to calculate the EDNS padding length. We use // the Random-Length Padding strategy from RFC 8467 as we find it more // efficient, it requires less extra traffic while provides comparable entropy. -const responsePaddingMaxSize = 128 +const responsePaddingMaxSize = 32 // respPadBuf is a fixed buffer to draw on for padding. var respPadBuf [responsePaddingMaxSize]byte @@ -64,7 +64,7 @@ func normalize(network Network, proto Protocol, req, resp *dns.Msg) { // In the case of encrypted protocols we should pad responses. if proto.HasPaddingSupport() { - padAnswer(resp, reqOpt, respOpt) + padAnswer(reqOpt, respOpt) } } @@ -123,7 +123,7 @@ func filterUnsupportedOptions(o []dns.EDNS0) (supported []dns.EDNS0) { // padAnswer adds padding to a DNS response before it's sent back over an // encrypted DNS protocol according to RFC 8467. Unencrypted responses should // not be padded. Inspired by github.com/folbricht/routedns padding. -func padAnswer(resp *dns.Msg, reqOpt, respOpt *dns.OPT) { +func padAnswer(reqOpt, respOpt *dns.OPT) { if findOption[*dns.EDNS0_PADDING](reqOpt) == nil { // According to the RFC, responders MAY (or may not) pad responses when // the padding option is not included in the request. In our case, we @@ -146,22 +146,14 @@ func padAnswer(resp *dns.Msg, reqOpt, respOpt *dns.OPT) { // TODO(ameshkov): Consider changing to crypto/rand, need to hold a vote. // #nosec G404 -- We don't need a real random for a simple padding // randomization, pseudo-random is enough. - padLen := rand.Intn(responsePaddingMaxSize-1) + 1 - - // If padding would make the packet larger than the request EDNS0 allows, - // we need to truncate it. // - // TODO(ameshkov): Consider removing this check and not calling resp.Len(). - // resp.Len() is a rather heavy function which we'd better avoid calling. - // However, we risk having a message larger than 64kB in this case. - answerLen := resp.Len() - if packetSize := int(reqOpt.UDPSize()); answerLen+padLen > packetSize { - padLen = packetSize - answerLen - if padLen < 0 { - // Still doesn't fit? Give up on padding. - padLen = 0 - } - } + // Note, that we don't check for whether reqOpt.UDPSize() here is smaller + // than resp.Len() + padLen so in theory the padded response may be larger + // than 64kB. This is an acceptable risk considering the savings on + // avoiding calling resp.Len(). + // + // TODO(ameshkov): Return this check if we optimize resp.Len(). + padLen := rand.Intn(responsePaddingMaxSize-1) + 1 paddingOpt.Padding = respPadBuf[:padLen:padLen] } diff --git a/internal/dnsserver/prometheus/cache_test.go b/internal/dnsserver/prometheus/cache_test.go index 83d7b85..5c30a6b 100644 --- a/internal/dnsserver/prometheus/cache_test.go +++ b/internal/dnsserver/prometheus/cache_test.go @@ -2,7 +2,6 @@ package prometheus_test import ( "context" - "net" "testing" "time" @@ -28,20 +27,17 @@ func TestCacheMetricsListener_integration_cache(t *testing.T) { cacheMiddleware, ) - // Pass 10 requests through the middleware - // This way we'll increment and set both hits and misses. + // Pass 10 requests through the middleware. This way we'll increment and + // set both hits and misses. for i := 0; i < 10; i++ { - req := dnsservertest.CreateMessage("example.org.", dns.TypeA) - addr := &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 53} - nrw := dnsserver.NewNonWriterResponseWriter(addr, addr) - ctx := dnsserver.ContextWithServerInfo(context.Background(), dnsserver.ServerInfo{ - Name: "test_server", - Addr: "127.0.0.1:0", - Proto: dnsserver.ProtoDNS, - }) + ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) ctx = dnsserver.ContextWithStartTime(ctx, time.Now()) ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{}) + nrw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr) + + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) + err := handlerWithMiddleware.ServeDNS(ctx, nrw, req) require.NoError(t, err) dnsservertest.RequireResponse(t, req, nrw.Msg(), 1, dns.RcodeSuccess, false) diff --git a/internal/dnsserver/prometheus/forward_test.go b/internal/dnsserver/prometheus/forward_test.go index da5975f..928b5b4 100644 --- a/internal/dnsserver/prometheus/forward_test.go +++ b/internal/dnsserver/prometheus/forward_test.go @@ -29,7 +29,7 @@ func TestForwardMetricsListener_integration_request(t *testing.T) { // Prepare a test DNS message and call the handler's ServeDNS function. // It will then call the metrics listener and prom metrics should be // incremented. - req := dnsservertest.CreateMessage("example.org.", dns.TypeA) + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) rw := dnsserver.NewNonWriterResponseWriter(srv.LocalUDPAddr(), srv.LocalUDPAddr()) err := handler.ServeDNS(context.Background(), rw, req) diff --git a/internal/dnsserver/prometheus/helper.go b/internal/dnsserver/prometheus/helper.go index 2675852..2c86f14 100644 --- a/internal/dnsserver/prometheus/helper.go +++ b/internal/dnsserver/prometheus/helper.go @@ -1,95 +1,95 @@ package prometheus import ( + "context" + "net" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" ) -// counterWithRequestLabels is a helper method that gets or creates a -// [prometheus.Counter] from the specified *prometheus.CounterVec. The point of -// this method is to avoid allocating [prometheus.Labels] and instead use the -// WithLabelValues function. This way extra allocations are avoided, but it is -// sensitive to the labels order. -func counterWithRequestLabels( - serverInfo dnsserver.ServerInfo, +// reqLabelMetricKey contains the information for a request label. +type reqLabelMetricKey struct { + network string + qType string + family string + srvInfo dnsserver.ServerInfo +} + +// newReqLabelMetricKey returns a new metric key from the given data. +func newReqLabelMetricKey( + ctx context.Context, req *dns.Msg, rw dnsserver.ResponseWriter, - vec *prometheus.CounterVec, -) (c prometheus.Counter) { - ip, _ := netutil.IPAndPortFromAddr(rw.RemoteAddr()) +) (k reqLabelMetricKey) { + return reqLabelMetricKey{ + network: string(dnsserver.NetworkFromAddr(rw.LocalAddr())), + qType: typeToString(req), + family: raddrToFamily(rw.RemoteAddr()), + srvInfo: dnsserver.MustServerInfoFromContext(ctx), + } +} - // Address family metric. - var family string - if ip == nil { - // Unknown. - family = "0" - } else if ip.To4() != nil { - // IPv4. - family = "1" - } else { - // IPv6. - family = "2" +// withLabelValues returns a counter with the given arguments in the correct +// order. +func (k reqLabelMetricKey) withLabelValues(vec *prometheus.CounterVec) (c prometheus.Counter) { + // The labels must be in the following order: + // 1. server name; + // 2. server protocol; + // 3. server socket network ("tcp"/"udp"); + // 4. server addr; + // 5. question type (see [typeToString]); + // 6. IP family (see [raddrToFamily]). + return vec.WithLabelValues( + k.srvInfo.Name, + k.srvInfo.Proto.String(), + k.network, + k.srvInfo.Addr, + k.qType, + k.family, + ) +} + +// prometheusVector is the interface for vectors of counters, histograms, etc. +type prometheusVector[T any] interface { + WithLabelValues(labelValues ...string) (m T) +} + +// withSrvInfoLabelValues returns a metric with the server info data in the +// correct order. +func withSrvInfoLabelValues[T any]( + vec prometheusVector[T], + srvInfo dnsserver.ServerInfo, +) (m T) { + // The labels must be in the following order: + // 1. server name; + // 2. server protocol; + // 3. server addr; + return vec.WithLabelValues( + srvInfo.Name, + srvInfo.Proto.String(), + srvInfo.Addr, + ) +} + +// raddrToFamily returns a family metric value for raddr. +// The values are: +// +// 0. Unknown. +// 1. IPv4. +// 2. IPv6. +func raddrToFamily(raddr net.Addr) (family string) { + ip := netutil.NetAddrToAddrPort(raddr).Addr() + + if !ip.IsValid() { + return "0" + } else if ip.Is4() { + return "1" } - // The metric's labels MUST be in the following order: - // "name", "proto", "network", "addr", "type", "family" - return vec.WithLabelValues( - serverInfo.Name, - serverInfo.Proto.String(), - string(dnsserver.NetworkFromAddr(rw.LocalAddr())), - serverInfo.Addr, - typeToString(req), - family, - ) -} - -// counterWithRequestLabels is a helper method that gets or creates a -// [prometheus.Counter] from the specified *prometheus.CounterVec. The point of -// this method is to avoid allocating [prometheus.Labels] and instead use the -// WithLabelValues function. This way extra allocations are avoided, but it is -// sensitive to the labels order. -func counterWithServerLabels( - serverInfo dnsserver.ServerInfo, - vec *prometheus.CounterVec, -) (c prometheus.Counter) { - // The metric's labels MUST be in the following order: - // "name", "proto", "addr" - return vec.WithLabelValues( - serverInfo.Name, - serverInfo.Proto.String(), - serverInfo.Addr, - ) -} - -// histogramWithServerLabels is a helper method that gets or creates a -// [prometheus.Observer] from the specified *prometheus.HistogramVec. The point -// of this method is to avoid allocating [prometheus.Labels] and instead use the -// WithLabelValues function. This way extra allocations are avoided, but it is -// sensitive to the labels order. -func histogramWithServerLabels( - serverInfo dnsserver.ServerInfo, - vec *prometheus.HistogramVec, -) (h prometheus.Observer) { - // The metric's labels MUST be in the following order: - // "name", "proto", "addr" - return vec.WithLabelValues(serverInfo.Name, serverInfo.Proto.String(), serverInfo.Addr) -} - -// counterWithServerLabelsPlusRCode is a helper method that gets or creates a -// [prometheus.Counter] from the specified *prometheus.CounterVec. The point of -// this method is to avoid allocating [prometheus.Labels] and instead use the -// WithLabelValues function. This way extra allocations are avoided, but it is -// sensitive to the labels order. -func counterWithServerLabelsPlusRCode( - serverInfo dnsserver.ServerInfo, - rCode string, - vec *prometheus.CounterVec, -) (c prometheus.Counter) { - // The metric's labels MUST be in the following order: - // "name", "proto", "addr", "rcode" - return vec.WithLabelValues(serverInfo.Name, serverInfo.Proto.String(), serverInfo.Addr, rCode) + return "2" } // setBoolGauge sets gauge to the numeric value corresponding to the val. diff --git a/internal/dnsserver/prometheus/initsyncmap.go b/internal/dnsserver/prometheus/initsyncmap.go new file mode 100644 index 0000000..25b7903 --- /dev/null +++ b/internal/dnsserver/prometheus/initsyncmap.go @@ -0,0 +1,51 @@ +package prometheus + +import "sync" + +// initSyncMap is a wrapper around [*sync.Map] that initializes the data if it's +// not present in an atomic way. +// +// TODO(a.garipov): Move to golibs and use more. +type initSyncMap[K, V any] struct { + inner *sync.Map + new func(k K) (v V) +} + +// newInitSyncMap returns a new properly initialized *initSyncMap that uses +// newFunc to return a value for the given key. +func newInitSyncMap[K, V any](newFunc func(k K) (v V)) (m *initSyncMap[K, V]) { + return &initSyncMap[K, V]{ + inner: &sync.Map{}, + new: newFunc, + } +} + +// get returns a value for the given key. If a value isn't available, it waits +// until it is. +func (m *initSyncMap[K, V]) get(key K) (v V) { + // Step 1. The fast track: check if there is already a value present. + loadVal, inited := m.inner.Load(key) + if inited { + return loadVal.(func() (v V))() + } + + // Step 2. Allocate a done channel and create a function that waits for one + // single initialization. Use the one returned from LoadOrStore regardless + // of whether it's this one. + var cached V + done := make(chan struct{}, 1) + done <- struct{}{} + loadVal, _ = m.inner.LoadOrStore(key, func() (loaded V) { + _, ok := <-done + if ok { + // The only real receive. Initialize the cached value and close the + // channel so that other goroutines receive the same value. + cached = m.new(key) + close(done) + } + + return cached + }) + + return loadVal.(func() (v V))() +} diff --git a/internal/dnsserver/prometheus/initsyncmap_internal_test.go b/internal/dnsserver/prometheus/initsyncmap_internal_test.go new file mode 100644 index 0000000..c64f0c9 --- /dev/null +++ b/internal/dnsserver/prometheus/initsyncmap_internal_test.go @@ -0,0 +1,41 @@ +package prometheus + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" +) + +func TestInitSyncMap(t *testing.T) { + numCalls := atomic.Uint32{} + m := newInitSyncMap[int, int](func(k int) (v int) { + numCalls.Add(1) + + return k + 1 + }) + + const ( + n = 1_000 + + key = 1 + want = key + 1 + ) + + results := make(chan int, n) + + for i := 0; i < n; i++ { + go func() { + results <- m.get(key) + }() + } + + for i := 0; i < n; i++ { + got, _ := testutil.RequireReceive(t, results, 1*time.Second) + assert.Equal(t, want, got) + } + + assert.Equal(t, uint32(1), numCalls.Load()) +} diff --git a/internal/dnsserver/prometheus/prometheus_test.go b/internal/dnsserver/prometheus/prometheus_test.go index eacb45e..3539b04 100644 --- a/internal/dnsserver/prometheus/prometheus_test.go +++ b/internal/dnsserver/prometheus/prometheus_test.go @@ -1,8 +1,10 @@ package prometheus_test import ( + "net" "testing" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/golibs/testutil" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" @@ -12,6 +14,22 @@ func TestMain(m *testing.M) { testutil.DiscardLogOutput(m) } +// testReqDomain is the common request domain for tests. +const testReqDomain = "request-domain.example" + +// testServerInfo is the common server information structure for tests. +var testServerInfo = dnsserver.ServerInfo{ + Name: "test_server", + Addr: "127.0.0.1:80", + Proto: dnsserver.ProtoDNS, +} + +// testUDPAddr is the common UDP address for tests. +var testUDPAddr = &net.UDPAddr{ + IP: net.IP{1, 2, 3, 4}, + Port: 53, +} + // requireMetrics accepts a list of metrics names and checks that // they exist in the prom registry. func requireMetrics(t testing.TB, args ...string) { @@ -34,6 +52,5 @@ func requireMetrics(t testing.TB, args ...string) { delete(metricsToCheck, m.GetName()) } - require.Len(t, metricsToCheck, 0, - "Some metrics weren't reported: %v", metricsToCheck) + require.Len(t, metricsToCheck, 0, "Some metrics weren't reported: %v", metricsToCheck) } diff --git a/internal/dnsserver/prometheus/ratelimit.go b/internal/dnsserver/prometheus/ratelimit.go index c476053..e57ffe2 100644 --- a/internal/dnsserver/prometheus/ratelimit.go +++ b/internal/dnsserver/prometheus/ratelimit.go @@ -10,33 +10,47 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) -// RateLimitMetricsListener implements the ratelimit.MetricsListener interface +// RateLimitMetricsListener implements the [ratelimit.MetricsListener] interface // and increments prom counters. -type RateLimitMetricsListener struct{} +type RateLimitMetricsListener struct { + dropCounters *initSyncMap[reqLabelMetricKey, prometheus.Counter] + allowlistedCounters *initSyncMap[reqLabelMetricKey, prometheus.Counter] +} + +// NewRateLimitMetricsListener returns a new properly initialized +// *RateLimitMetricsListener. +func NewRateLimitMetricsListener() (l *RateLimitMetricsListener) { + return &RateLimitMetricsListener{ + dropCounters: newInitSyncMap(func(k reqLabelMetricKey) (c prometheus.Counter) { + return k.withLabelValues(droppedTotal) + }), + allowlistedCounters: newInitSyncMap(func(k reqLabelMetricKey) (c prometheus.Counter) { + return k.withLabelValues(allowlistedTotal) + }), + } +} // type check var _ ratelimit.MetricsListener = (*RateLimitMetricsListener)(nil) // OnRateLimited implements the ratelimit.MetricsListener interface for // *RateLimitMetricsListener. -func (r *RateLimitMetricsListener) OnRateLimited( +func (l *RateLimitMetricsListener) OnRateLimited( ctx context.Context, req *dns.Msg, rw dnsserver.ResponseWriter, ) { - s := dnsserver.MustServerInfoFromContext(ctx) - counterWithRequestLabels(s, req, rw, droppedTotal).Inc() + l.dropCounters.get(newReqLabelMetricKey(ctx, req, rw)).Inc() } // OnAllowlisted implements the ratelimit.MetricsListener interface for // *RateLimitMetricsListener. -func (r *RateLimitMetricsListener) OnAllowlisted( +func (l *RateLimitMetricsListener) OnAllowlisted( ctx context.Context, req *dns.Msg, rw dnsserver.ResponseWriter, ) { - s := dnsserver.MustServerInfoFromContext(ctx) - counterWithRequestLabels(s, req, rw, allowlistedTotal).Inc() + l.allowlistedCounters.get(newReqLabelMetricKey(ctx, req, rw)).Inc() } // This block contains prometheus metrics declarations for ratelimit.Middleware diff --git a/internal/dnsserver/prometheus/ratelimit_test.go b/internal/dnsserver/prometheus/ratelimit_test.go index 1cc1157..aff617c 100644 --- a/internal/dnsserver/prometheus/ratelimit_test.go +++ b/internal/dnsserver/prometheus/ratelimit_test.go @@ -2,7 +2,6 @@ package prometheus_test import ( "context" - "net" "net/netip" "testing" "time" @@ -33,7 +32,7 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) { }) rlMw, err := ratelimit.NewMiddleware(rl, nil) require.NoError(t, err) - rlMw.Metrics = &prometheus.RateLimitMetricsListener{} + rlMw.Metrics = prometheus.NewRateLimitMetricsListener() handlerWithMiddleware := dnsserver.WithMiddlewares( dnsservertest.DefaultHandler(), @@ -42,17 +41,14 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) { // Pass 10 requests through the middleware. for i := 0; i < 10; i++ { - req := dnsservertest.CreateMessage("example.org.", dns.TypeA) - addr := &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 53} - nrw := dnsserver.NewNonWriterResponseWriter(addr, addr) - ctx := dnsserver.ContextWithServerInfo(context.Background(), dnsserver.ServerInfo{ - Name: "test", - Addr: "127.0.0.1", - Proto: dnsserver.ProtoDNS, - }) + ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) ctx = dnsserver.ContextWithStartTime(ctx, time.Now()) ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{}) + nrw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr) + + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) + err = handlerWithMiddleware.ServeDNS(ctx, nrw, req) require.NoError(t, err) if i < rps { @@ -65,3 +61,35 @@ func TestRateLimiterMetricsListener_integration_cache(t *testing.T) { // Now make sure that prometheus metrics were incremented properly. requireMetrics(t, "dns_ratelimit_dropped_total") } + +func BenchmarkRateLimitMetricsListener(b *testing.B) { + l := prometheus.NewRateLimitMetricsListener() + + ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) + rw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr) + + b.Run("OnAllowlisted", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnAllowlisted(ctx, req, rw) + } + }) + + b.Run("OnRateLimited", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnRateLimited(ctx, req, rw) + } + }) + + // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkRateLimitMetricsListener/OnAllowlisted-16 6025423 209.5 ns/op 0 B/op 0 allocs/op + // BenchmarkRateLimitMetricsListener/OnRateLimited-16 5798031 209.4 ns/op 0 B/op 0 allocs/op +} diff --git a/internal/dnsserver/prometheus/server.go b/internal/dnsserver/prometheus/server.go index 4cd01d7..49a04fa 100644 --- a/internal/dnsserver/prometheus/server.go +++ b/internal/dnsserver/prometheus/server.go @@ -12,7 +12,80 @@ import ( // ServerMetricsListener implements the [dnsserver.MetricsListener] interface // and increments prom counters. -type ServerMetricsListener struct{} +type ServerMetricsListener struct { + reqTotalCounters *initSyncMap[reqLabelMetricKey, prometheus.Counter] + + respRCodeCounters *initSyncMap[srvInfoRCode, prometheus.Counter] + + invalidMsgCounters *initSyncMap[dnsserver.ServerInfo, prometheus.Counter] + errorCounters *initSyncMap[dnsserver.ServerInfo, prometheus.Counter] + panicCounters *initSyncMap[dnsserver.ServerInfo, prometheus.Counter] + + reqDurationHistograms *initSyncMap[dnsserver.ServerInfo, prometheus.Observer] + reqSizeHistograms *initSyncMap[dnsserver.ServerInfo, prometheus.Observer] + respSizeHistograms *initSyncMap[dnsserver.ServerInfo, prometheus.Observer] +} + +// srvInfoRCode is a struct containing the server information along with a +// response code. +type srvInfoRCode struct { + rCode string + dnsserver.ServerInfo +} + +// withLabelValues returns a counter with the server info and rcode data in the +// correct order. +func (i srvInfoRCode) withLabelValues( + vec *prometheus.CounterVec, +) (c prometheus.Counter) { + // The labels must be in the following order: + // 1. server name; + // 2. server protocol; + // 3. server addr; + // 4. response code; + return vec.WithLabelValues( + i.Name, + i.Proto.String(), + i.Addr, + i.rCode, + ) +} + +// NewServerMetricsListener returns a new properly initialized +// *ServerMetricsListener. +func NewServerMetricsListener() (l *ServerMetricsListener) { + return &ServerMetricsListener{ + reqTotalCounters: newInitSyncMap(func(k reqLabelMetricKey) (c prometheus.Counter) { + return k.withLabelValues(requestTotal) + }), + + respRCodeCounters: newInitSyncMap(func(k srvInfoRCode) (c prometheus.Counter) { + return k.withLabelValues(responseRCode) + }), + + invalidMsgCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) { + // TODO(a.garipov): Here and below, remove explicit type + // declarations in Go 1.21. + return withSrvInfoLabelValues[prometheus.Counter](invalidMsgTotal, k) + }), + errorCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) { + return withSrvInfoLabelValues[prometheus.Counter](errorTotal, k) + }), + panicCounters: newInitSyncMap(func(k dnsserver.ServerInfo) (c prometheus.Counter) { + return withSrvInfoLabelValues[prometheus.Counter](panicTotal, k) + }), + + reqDurationHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) { + return withSrvInfoLabelValues[prometheus.Observer](requestDuration, k) + }), + reqSizeHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) { + return withSrvInfoLabelValues[prometheus.Observer](requestSize, k) + }), + respSizeHistograms: newInitSyncMap(func(k dnsserver.ServerInfo) (o prometheus.Observer) { + return withSrvInfoLabelValues[prometheus.Observer](responseSize, k) + }), + } +} // type check var _ dnsserver.MetricsListener = (*ServerMetricsListener)(nil) @@ -21,51 +94,57 @@ var _ dnsserver.MetricsListener = (*ServerMetricsListener)(nil) // [*ServerMetricsListener]. func (l *ServerMetricsListener) OnRequest( ctx context.Context, - req, resp *dns.Msg, + req *dns.Msg, + resp *dns.Msg, rw dnsserver.ResponseWriter, ) { serverInfo := dnsserver.MustServerInfoFromContext(ctx) startTime := dnsserver.MustStartTimeFromContext(ctx) // Increment total requests count metrics. - counterWithRequestLabels(serverInfo, req, rw, requestTotal).Inc() + l.reqTotalCounters.get(newReqLabelMetricKey(ctx, req, rw)).Inc() // Increment request duration histogram. elapsed := time.Since(startTime).Seconds() - histogramWithServerLabels(serverInfo, requestDuration).Observe(elapsed) + l.reqDurationHistograms.get(serverInfo).Observe(elapsed) // Increment request size. ri := dnsserver.MustRequestInfoFromContext(ctx) - histogramWithServerLabels(serverInfo, requestSize).Observe(float64(ri.RequestSize)) + l.reqSizeHistograms.get(serverInfo).Observe(float64(ri.RequestSize)) // If resp is not nil, increment response-related metrics. if resp != nil { - histogramWithServerLabels(serverInfo, responseSize).Observe(float64(ri.ResponseSize)) - rCode := rCodeToString(resp.Rcode) - counterWithServerLabelsPlusRCode(serverInfo, rCode, responseRCode).Inc() + l.respSizeHistograms.get(serverInfo).Observe(float64(ri.ResponseSize)) + l.respRCodeCounters.get(srvInfoRCode{ + ServerInfo: serverInfo, + rCode: rCodeToString(resp.Rcode), + }).Inc() } else { - // If resp is nil, increment responseRCode with a special "rcode" - // label value ("DROPPED"). - counterWithServerLabelsPlusRCode(serverInfo, "DROPPED", responseRCode).Inc() + // If resp is nil, increment responseRCode with a special "rcode" label + // value ("DROPPED"). + l.respRCodeCounters.get(srvInfoRCode{ + ServerInfo: serverInfo, + rCode: "DROPPED", + }).Inc() } } // OnInvalidMsg implements the [dnsserver.MetricsListener] interface for // [*ServerMetricsListener]. func (l *ServerMetricsListener) OnInvalidMsg(ctx context.Context) { - counterWithServerLabels(dnsserver.MustServerInfoFromContext(ctx), invalidMsgTotal).Inc() + l.invalidMsgCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc() } // OnError implements the [dnsserver.MetricsListener] interface for // [*ServerMetricsListener]. func (l *ServerMetricsListener) OnError(ctx context.Context, _ error) { - counterWithServerLabels(dnsserver.MustServerInfoFromContext(ctx), errorTotal).Inc() + l.errorCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc() } // OnPanic implements the [dnsserver.MetricsListener] interface for // [*ServerMetricsListener]. func (l *ServerMetricsListener) OnPanic(ctx context.Context, _ any) { - counterWithServerLabels(dnsserver.MustServerInfoFromContext(ctx), panicTotal).Inc() + l.panicCounters.get(dnsserver.MustServerInfoFromContext(ctx)).Inc() } // OnQUICAddressValidation implements the [dnsserver.MetricsListener] interface diff --git a/internal/dnsserver/prometheus/server_test.go b/internal/dnsserver/prometheus/server_test.go index 6a11c1f..aed94ed 100644 --- a/internal/dnsserver/prometheus/server_test.go +++ b/internal/dnsserver/prometheus/server_test.go @@ -3,10 +3,11 @@ package prometheus_test import ( "context" "testing" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" - prom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/require" @@ -24,7 +25,7 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { Name: "test", Addr: "127.0.0.1:0", Handler: dnsservertest.DefaultHandler(), - Metrics: &prom.ServerMetricsListener{}, + Metrics: prometheus.NewServerMetricsListener(), }, } srv := dnsserver.NewServerDNS(conf) @@ -39,7 +40,7 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { }) // Create a test message. - req := dnsservertest.CreateMessage("example.org", dns.TypeA) + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) c := &dns.Client{Net: "tcp"} @@ -48,8 +49,8 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { // Pass 10 requests to make the test less flaky. for i := 0; i < 10; i++ { - res, _, eerr := c.Exchange(req, addr) - require.NoError(t, eerr) + res, _, exchErr := c.Exchange(req, addr) + require.NoError(t, exchErr) require.NotNil(t, res) require.Equal(t, dns.RcodeSuccess, res.Rcode) } @@ -64,3 +65,61 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) { "dns_server_response_rcode_total", ) } + +func BenchmarkServerMetricsListener(b *testing.B) { + l := prometheus.NewServerMetricsListener() + + ctx := dnsserver.ContextWithServerInfo(context.Background(), testServerInfo) + ctx = dnsserver.ContextWithStartTime(ctx, time.Now()) + + req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA) + resp := (&dns.Msg{}).SetRcode(req, dns.RcodeSuccess) + ctx = dnsserver.ContextWithRequestInfo(ctx, dnsserver.RequestInfo{ + RequestSize: req.Len(), + ResponseSize: resp.Len(), + }) + + rw := dnsserver.NewNonWriterResponseWriter(testUDPAddr, testUDPAddr) + + b.Run("OnRequest", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnRequest(ctx, req, resp, rw) + } + }) + + b.Run("OnInvalidMsg", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnInvalidMsg(ctx) + } + }) + + b.Run("OnError", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnError(ctx, nil) + } + }) + + b.Run("OnPanic", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.OnPanic(ctx, nil) + } + }) + + // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkServerMetricsListener/OnRequest-16 1550391 716.7 ns/op 0 B/op 0 allocs/op + // BenchmarkServerMetricsListener/OnInvalidMsg-16 13041940 91.75 ns/op 0 B/op 0 allocs/op + // BenchmarkServerMetricsListener/OnError-16 12297494 97.04 ns/op 0 B/op 0 allocs/op + // BenchmarkServerMetricsListener/OnPanic-16 14029394 89.19 ns/op 0 B/op 0 allocs/op +} diff --git a/internal/dnsserver/serverbase.go b/internal/dnsserver/serverbase.go index 5ea8206..720e16e 100644 --- a/internal/dnsserver/serverbase.go +++ b/internal/dnsserver/serverbase.go @@ -19,8 +19,8 @@ type ConfigBase struct { // Name is used for logging, and it may be used for perf counters reporting. Name string - // Addr is the address the server listens to. See go doc net.Dial for - // the documentation on the address format. + // Addr is the address the server listens to. See [net.Dial] for the + // documentation on the address format. Addr string // Network is the network this server listens to. If empty, the server will diff --git a/internal/dnssvc/debug_internal_test.go b/internal/dnssvc/debug_internal_test.go index 574d18a..4170226 100644 --- a/internal/dnssvc/debug_internal_test.go +++ b/internal/dnssvc/debug_internal_test.go @@ -8,6 +8,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -47,8 +48,8 @@ func TestService_writeDebugResponse(t *testing.T) { blockRule = "||example.com^" ) - clientIPStr := testClientIP.String() - serverIPStr := testServerAddr.String() + clientIPStr := dnssvctest.ClientIP.String() + serverIPStr := dnssvctest.ServerAddr.String() testCases := []struct { name string ri *agd.RequestInfo @@ -129,26 +130,26 @@ func TestService_writeDebugResponse(t *testing.T) { }), }, { name: "device", - ri: &agd.RequestInfo{Device: &agd.Device{ID: testDeviceID}}, + ri: &agd.RequestInfo{Device: &agd.Device{ID: dnssvctest.DeviceID}}, reqRes: nil, respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, {"server-ip.adguard-dns.com.", serverIPStr}, - {"device-id.adguard-dns.com.", testDeviceID}, + {"device-id.adguard-dns.com.", dnssvctest.DeviceIDStr}, {"resp.res-type.adguard-dns.com.", "normal"}, }), }, { name: "profile", ri: &agd.RequestInfo{ - Profile: &agd.Profile{ID: testProfileID}, + Profile: &agd.Profile{ID: dnssvctest.ProfileID}, }, reqRes: nil, respRes: nil, wantExtra: newTXTExtra([][2]string{ {"client-ip.adguard-dns.com.", clientIPStr}, {"server-ip.adguard-dns.com.", serverIPStr}, - {"profile-id.adguard-dns.com.", testProfileID}, + {"profile-id.adguard-dns.com.", dnssvctest.ProfileIDStr}, {"resp.res-type.adguard-dns.com.", "normal"}, }), }, { @@ -182,7 +183,7 @@ func TestService_writeDebugResponse(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rw := dnsserver.NewNonWriterResponseWriter(testLocalAddr, testRAddr) + rw := dnsserver.NewNonWriterResponseWriter(dnssvctest.LocalAddr, dnssvctest.RemoteAddr) ctx := agd.ContextWithRequestInfo(context.Background(), tc.ri) diff --git a/internal/dnssvc/dnssvc.go b/internal/dnssvc/dnssvc.go index fb09152..6768d06 100644 --- a/internal/dnssvc/dnssvc.go +++ b/internal/dnssvc/dnssvc.go @@ -10,6 +10,7 @@ import ( "net/http" "time" + "github.com/AdguardTeam/AdGuardDNS/internal/access" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/connlimiter" @@ -20,6 +21,8 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/accessmw" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" @@ -48,6 +51,9 @@ type Config struct { // active stream-connections. ConnLimiter *connlimiter.Limiter + // AccessManager is used to block requests. + AccessManager access.Interface + // SafeBrowsing is the safe browsing TXT hash matcher. SafeBrowsing filter.HashMatcher @@ -397,7 +403,7 @@ func NewListener( metricsListener := &errCollMetricsListener{ errColl: errColl, - baseListener: &prometheus.ServerMetricsListener{}, + baseListener: prometheus.NewServerMetricsListener(), } confBase := dnsserver.ConfigBase{ @@ -473,61 +479,47 @@ func newServers( rlProtos := []agd.Protocol{agd.ProtoDNS} var rlm *ratelimit.Middleware + srvName := s.Name rlm, err = ratelimit.NewMiddleware(c.RateLimit, rlProtos) if err != nil { - return nil, fmt.Errorf("ratelimit: %w", err) + return nil, fmt.Errorf("server %q: ratelimit: %w", srvName, err) } - rlm.Metrics = &prometheus.RateLimitMetricsListener{} + rlm.Metrics = prometheus.NewRateLimitMetricsListener() - imw := &initMw{ - messages: c.Messages, - fltGrp: fg, - srvGrp: srvGrp, - srv: s, - db: c.ProfileDB, - geoIP: c.GeoIP, - errColl: c.ErrColl, - } + amw := accessmw.New(&accessmw.Config{ + AccessManager: c.AccessManager, + }) + + imw := initial.New(&initial.Config{ + Messages: c.Messages, + FilteringGroup: fg, + ServerGroup: srvGrp, + Server: s, + ProfileDB: c.ProfileDB, + GeoIP: c.GeoIP, + ErrColl: c.ErrColl, + }) h := dnsserver.WithMiddlewares( handler, - // Keep the rate limiting middleware as the outer one to make sure - // that the application logic isn't touched if the request is - // ratelimited. + // Keep the rate limiting and access middlewares as the outer ones + // to make sure that the application logic isn't touched if the + // request is ratelimited or blocked by access settings. rlm, + amw, imw, ) - listeners := make([]*listener, 0, len(s.BindData)) - for _, bindData := range s.BindData { - addr := bindData.Address - if addr == "" { - addr = bindData.AddrPort.String() - } - - name := listenerName(s.Name, addr, s.Protocol) - - lc := bindData.ListenConfig - if lc == nil { - lc = newListenConfig(c.ControlConf, c.ConnLimiter, s.Protocol) - } - - var l Listener - l, err = newListener(s, name, addr, h, c.NonDNS, c.ErrColl, lc) - if err != nil { - return nil, fmt.Errorf("server %q: %w", s.Name, err) - } - - listeners = append(listeners, &listener{ - name: name, - Listener: l, - }) + var listeners []*listener + listeners, err = newListeners(c, s, h, newListener) + if err != nil { + return nil, fmt.Errorf("server %q: %w", srvName, err) } servers[i] = &server{ - name: s.Name, + name: srvName, handler: h, listeners: listeners, } @@ -536,16 +528,59 @@ func newServers( return servers, nil } +// newServers creates a slice of listeners for a server. +func newListeners( + c *Config, + srv *agd.Server, + handler dnsserver.Handler, + newListener NewListenerFunc, +) (listeners []*listener, err error) { + listeners = make([]*listener, 0, len(srv.BindData)) + for i, bindData := range srv.BindData { + addr := bindData.Address + if addr == "" { + addr = bindData.AddrPort.String() + } + + proto := srv.Protocol + name := listenerName(srv.Name, addr, proto) + + lc := newListenConfig(bindData.ListenConfig, c.ControlConf, c.ConnLimiter, proto) + + var l Listener + l, err = newListener(srv, name, addr, handler, c.NonDNS, c.ErrColl, lc) + if err != nil { + return nil, fmt.Errorf("bind data at index %d: %w", i, err) + } + + listeners = append(listeners, &listener{ + name: name, + Listener: l, + }) + } + + return listeners, nil +} + // newListenConfig returns the netext.ListenConfig used by the plain-DNS // servers. The resulting ListenConfig sets additional socket flags and // processes the control messages of connections created with ListenPacket. // Additionally, if l is not nil, it is used to limit the number of // simultaneously active stream-connections. func newListenConfig( + original netext.ListenConfig, ctrlConf *netext.ControlConfig, l *connlimiter.Limiter, p agd.Protocol, ) (lc netext.ListenConfig) { + if original != nil { + if l == nil { + return original + } + + return connlimiter.NewListenConfig(original, l) + } + if p == agd.ProtoDNS { lc = netext.DefaultListenConfigWithOOB(ctrlConf) } else { diff --git a/internal/dnssvc/dnssvc_internal_test.go b/internal/dnssvc/dnssvc_internal_test.go deleted file mode 100644 index 9af7cdb..0000000 --- a/internal/dnssvc/dnssvc_internal_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package dnssvc - -import ( - "net" - "net/netip" -) - -// Common addresses for tests. -var ( - testClientIP = net.IP{1, 2, 3, 4} - testRAddr = &net.TCPAddr{ - IP: testClientIP, - Port: 12345, - } - - testClientAddrPort = testRAddr.AddrPort() - 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 -const testDeviceID = "dev1234" - -// testProfileID is the common profile ID for tests -const testProfileID = "prof1234" diff --git a/internal/dnssvc/initmw.go b/internal/dnssvc/initmw.go deleted file mode 100644 index 541ac87..0000000 --- a/internal/dnssvc/initmw.go +++ /dev/null @@ -1,334 +0,0 @@ -package dnssvc - -import ( - "context" - "fmt" - "net" - "net/netip" - "strings" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" - "github.com/AdguardTeam/AdGuardDNS/internal/geoip" - "github.com/AdguardTeam/AdGuardDNS/internal/optlog" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/netutil" - "github.com/miekg/dns" -) - -// The Initial Middleware - -// initMw is the outermost middleware of the AdGuard DNS server. It filters out -// the Firefox canary domain logic, sets and resets the AD bit for further -// processing, as well as puts as much information as it can into the context. -// -// This middleware must be the most outer middleware apart from the ratelimit -// one. -// -// TODO(a.garipov): Add tests. -type initMw struct { - // messages is used to build the responses specific for the request's - // context. - messages *dnsmsg.Constructor - - // fltGrp is the filtering group to which srv belongs. - fltGrp *agd.FilteringGroup - - // srvGrp is the server group to which srv belongs. - srvGrp *agd.ServerGroup - - // srv is the current server which serves the request. - srv *agd.Server - - // db is the database of user profiles and devices. - db profiledb.Interface - - // geoIP detects the location of the request source. - geoIP geoip.Interface - - // errColl collects and reports the errors considered non-critical. - errColl agd.ErrorCollector -} - -// type check -var _ dnsserver.Middleware = (*initMw)(nil) - -// Wrap implements the [dnsserver.Middleware] interface for *initMw. -func (mw *initMw) Wrap(h dnsserver.Handler) (wrapped dnsserver.Handler) { - return &initMwHandler{ - mw: mw, - next: h, - } -} - -// newRequestInfo returns the new request information structure using the -// middleware's configuration and values from ctx. -func (mw *initMw) newRequestInfo( - ctx context.Context, - req *dns.Msg, - laddr net.Addr, - raddr net.Addr, - fqdn string, - qt dnsmsg.RRType, - cl dnsmsg.Class, -) (ri *agd.RequestInfo, err error) { - // Put the host, server, and client IP data into the request information - // immediately. - ri = &agd.RequestInfo{ - FilteringGroup: mw.fltGrp, - Messages: mw.messages, - ServerGroup: mw.srvGrp.Name, - Server: mw.srv.Name, - Host: strings.TrimSuffix(fqdn, "."), - QType: qt, - QClass: cl, - } - - ri.RemoteIP = netutil.NetAddrToAddrPort(raddr).Addr() - - // As an optimization, put the request ID closer to the top of the context - // stack. - ri.ID, _ = agd.RequestIDFromContext(ctx) - - // Add the GeoIP information, if any. - err = mw.addLocation(ctx, ri, req) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err - } - - // Add the profile information, if any. - localIP := netutil.NetAddrToAddrPort(laddr).Addr() - err = mw.addProfile(ctx, ri, req, localIP) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return nil, err - } - - return ri, nil -} - -// addLocation adds GeoIP location information about the client's remote address -// as well as the EDNS Client Subnet information, if there is one, to ri. err -// is not nil only if req contains a malformed EDNS Client Subnet option. -func (mw *initMw) addLocation(ctx context.Context, ri *agd.RequestInfo, req *dns.Msg) (err error) { - ri.Location = mw.locationData(ctx, ri.RemoteIP, "client") - - ecs, scope, err := dnsmsg.ECSFromMsg(req) - if err != nil { - return fmt.Errorf("adding ecs info: %w", err) - } else if ecs != (netip.Prefix{}) { - ri.ECS = &agd.ECS{ - Location: mw.locationData(ctx, ecs.Addr(), "ecs"), - Subnet: ecs, - Scope: scope, - } - } - - return nil -} - -// locationData returns the GeoIP location information about the IP address. -// typ is the type of data being requested for error reporting and logging. -func (mw *initMw) locationData(ctx context.Context, ip netip.Addr, typ string) (l *agd.Location) { - l, err := mw.geoIP.Data("", ip) - if err != nil { - // Consider GeoIP errors non-critical. Report and go on. - agd.Collectf(ctx, mw.errColl, "init mw: getting geoip for %s ip: %w", typ, err) - } - - if l == nil { - optlog.Debug2("init mw: no geoip for %s ip %s", typ, ip) - } else { - optlog.Debug4("init mw: found country/asn %q/%d for %s ip %s", l.Country, l.ASN, typ, ip) - } - - return l -} - -// addProfile adds profile and device information, if any, to the request -// information. -func (mw *initMw) addProfile( - ctx context.Context, - ri *agd.RequestInfo, - req *dns.Msg, - localIP netip.Addr, -) (err error) { - defer func() { err = errors.Annotate(err, "getting profile from req: %w") }() - - var id agd.DeviceID - if p := mw.srv.Protocol; p.IsStdEncrypted() { - // Assume that mw.srvGrp.TLS is non-nil if p.IsStdEncrypted() is true. - wildcards := mw.srvGrp.TLS.DeviceIDWildcards - id, err = deviceIDFromContext(ctx, mw.srv.Protocol, wildcards) - } else if p == agd.ProtoDNS { - id, err = deviceIDFromEDNS(req) - } else { - // No DeviceID for DNSCrypt yet. - return nil - } - - if err != nil { - return err - } - - 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 { - if !errors.Is(err, profiledb.ErrDeviceNotFound) { - // Very unlikely, since those two error types are the only ones - // currently returned from the default profile DB. - return fmt.Errorf("unexpected profiledb error: %s", err) - } - - optlog.Debug1("init mw: profile or device not found: %s", err) - } else if prof.Deleted { - optlog.Debug1("init mw: profile %s is deleted", prof.ID) - } else { - optlog.Debug3("init mw: found profile %s and device %s by %s", prof.ID, dev.ID, byWhat) - - ri.Device, ri.Profile = dev, prof - ri.Messages = dnsmsg.NewConstructor(prof.BlockingMode.Mode, prof.FilteredResponseTTL) - } - - return nil -} - -// Constants for the parameter by which a device has been found. -const ( - byDeviceID = "device id" - byDedicatedIP = "dedicated ip" - byLinkedIP = "linked ip" -) - -// profile finds the profile by the client data. -func (mw *initMw) profile( - ctx context.Context, - localIP netip.Addr, - remoteIP netip.Addr, - id agd.DeviceID, -) (prof *agd.Profile, dev *agd.Device, byWhat string, err error) { - if id != "" { - prof, dev, err = mw.db.ProfileByDeviceID(ctx, id) - if err != nil { - return nil, nil, "", err - } - - return prof, dev, byDeviceID, nil - } - - if !mw.srv.LinkedIPEnabled { - optlog.Debug1("init mw: not matching by linked or dedicated ip for server %s", mw.srv.Name) - - return nil, nil, "", profiledb.ErrDeviceNotFound - } else if p := mw.srv.Protocol; p != agd.ProtoDNS { - optlog.Debug1("init mw: not matching by linked or dedicated ip for proto %v", p) - - return nil, nil, "", profiledb.ErrDeviceNotFound - } - - byWhat = byDedicatedIP - prof, dev, err = mw.db.ProfileByDedicatedIP(ctx, localIP) - if errors.Is(err, profiledb.ErrDeviceNotFound) { - byWhat = byLinkedIP - prof, dev, err = mw.db.ProfileByLinkedIP(ctx, remoteIP) - } - - if err != nil { - return nil, nil, "", err - } - - return prof, dev, byWhat, nil -} - -// initMwHandler implements the [dnsserver.Handler] interface and will be used -// as a [dnsserver.Handler] that the initMw middleware returns from the Wrap -// function call. -type initMwHandler struct { - mw *initMw - next dnsserver.Handler -} - -// type check -var _ dnsserver.Handler = (*initMwHandler)(nil) - -// ServeDNS implements the [dnsserver.Handler] interface for *initMwHandler. -func (mh *initMwHandler) ServeDNS( - ctx context.Context, - rw dnsserver.ResponseWriter, - req *dns.Msg, -) (err error) { - defer func() { err = errors.Annotate(err, "init mw: %w") }() - - // Save the actual value of the request AD and DO bits and set the AD - // bit in the request to true, so that the upstream validates the data - // and caches the actual value of the response AD bit. Restore it - // later, depending on the request and response data. - reqAD := req.AuthenticatedData - reqDO := dnsmsg.IsDO(req) - req.AuthenticatedData = true - - // Assume that module dnsserver has already validated that the request - // always has exactly one question for us. - q := req.Question[0] - fqdn := strings.ToLower(q.Name) - qt := q.Qtype - cl := q.Qclass - - // Copy middleware to the local variable to make the code simpler. - mw := mh.mw - - // Get the request's information, such as GeoIP data and user profiles. - ri, err := mw.newRequestInfo(ctx, req, rw.LocalAddr(), rw.RemoteAddr(), fqdn, qt, cl) - if err != nil { - var ecsErr dnsmsg.BadECSError - if errors.As(err, &ecsErr) { - // We've got a bad ECS option. Log and respond with a FORMERR - // immediately. - optlog.Debug1("init mw: %s", err) - - err = rw.WriteMsg(ctx, req, mw.messages.NewMsgFORMERR(req)) - err = errors.Annotate(err, "writing formerr resp: %w") - } - - // Don't wrap the error, because this is the main flow, and there is - // already errors.Annotate here. - return err - } - - if specHdlr, name := mw.reqInfoSpecialHandler(ri, cl); specHdlr != nil { - optlog.Debug1("init mw: got req-info special handler %s", name) - - // Don't wrap the error, because it's informative enough as is, and - // because if handled is true, the main flow terminates here. - return specHdlr(ctx, rw, req, ri) - } - - ctx = agd.ContextWithRequestInfo(ctx, ri) - - // Record the response, restore the AD bit value in both the request and - // the response, and write the response. - nwrw := makeNonWriter(rw) - err = mh.next.ServeDNS(ctx, nwrw, req) - if err != nil { - // Don't wrap the error, because this is the main flow, and there is - // already errors.Annotate here. - return err - } - - resp := nwrw.Msg() - - // Following RFC 6840, set the AD bit in the response only when the - // response is authenticated, and the request contained either a set DO - // bit or a set AD bit. - // - // See https://datatracker.ietf.org/doc/html/rfc6840#section-5.8. - resp.AuthenticatedData = resp.AuthenticatedData && (reqAD || reqDO) - - err = rw.WriteMsg(ctx, req, resp) - - return errors.Annotate(err, "writing resp: %w") -} diff --git a/internal/dnssvc/initmw_internal_test.go b/internal/dnssvc/initmw_internal_test.go deleted file mode 100644 index fd10fce..0000000 --- a/internal/dnssvc/initmw_internal_test.go +++ /dev/null @@ -1,855 +0,0 @@ -package dnssvc - -import ( - "context" - "crypto/tls" - "net/netip" - "testing" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" - "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/netutil" - "github.com/AdguardTeam/golibs/stringutil" - "github.com/AdguardTeam/golibs/testutil" - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" -) - -func TestInitMw_profile(t *testing.T) { - prof := &agd.Profile{ - ID: testProfileID, - DeviceIDs: []agd.DeviceID{ - testDeviceID, - }, - } - dev := &agd.Device{ - ID: testDeviceID, - LinkedIP: testClientAddr, - DedicatedIPs: []netip.Addr{ - testServerAddr, - }, - } - - testCases := []struct { - wantDev *agd.Device - wantProf *agd.Profile - wantByWhat string - wantErrMsg string - name string - id agd.DeviceID - proto agd.Protocol - linkedIPEnabled bool - }{{ - wantDev: nil, - wantProf: nil, - wantByWhat: "", - wantErrMsg: "device not found", - name: "no_device_id", - id: "", - proto: agd.ProtoDNS, - linkedIPEnabled: true, - }, { - wantDev: dev, - wantProf: prof, - wantByWhat: byDeviceID, - wantErrMsg: "", - name: "device_id", - id: testDeviceID, - proto: agd.ProtoDNS, - linkedIPEnabled: true, - }, { - wantDev: dev, - wantProf: prof, - wantByWhat: byLinkedIP, - wantErrMsg: "", - name: "linked_ip", - id: "", - proto: agd.ProtoDNS, - linkedIPEnabled: true, - }, { - wantDev: nil, - wantProf: nil, - wantByWhat: "", - wantErrMsg: "device not found", - name: "linked_ip_dot", - id: "", - proto: agd.ProtoDoT, - linkedIPEnabled: true, - }, { - wantDev: nil, - wantProf: nil, - wantByWhat: "", - wantErrMsg: "device not found", - name: "linked_ip_disabled", - id: "", - proto: agd.ProtoDoT, - linkedIPEnabled: false, - }, { - wantDev: dev, - wantProf: prof, - wantByWhat: byDedicatedIP, - wantErrMsg: "", - name: "dedicated_ip", - id: "", - proto: agd.ProtoDNS, - linkedIPEnabled: true, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - db := &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - gotID agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - assert.Equal(t, tc.id, gotID) - - if tc.wantByWhat == byDeviceID { - return prof, dev, nil - } - - return nil, nil, profiledb.ErrDeviceNotFound - }, - OnProfileByDedicatedIP: func( - _ context.Context, - gotLocalIP netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - assert.Equal(t, testServerAddr, gotLocalIP) - - if tc.wantByWhat == byDedicatedIP { - return prof, dev, nil - } - - return nil, nil, profiledb.ErrDeviceNotFound - }, - OnProfileByLinkedIP: func( - _ context.Context, - gotRemoteIP netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - assert.Equal(t, testClientAddr, gotRemoteIP) - - if tc.wantByWhat == byLinkedIP { - return prof, dev, nil - } - - return nil, nil, profiledb.ErrDeviceNotFound - }, - } - - mw := &initMw{ - srv: &agd.Server{ - Protocol: tc.proto, - LinkedIPEnabled: tc.linkedIPEnabled, - }, - db: db, - } - ctx := context.Background() - - gotProf, gotDev, gotByWhat, err := mw.profile( - ctx, - testServerAddr, - testClientAddr, - tc.id, - ) - testutil.AssertErrorMsg(t, tc.wantErrMsg, err) - assert.Equal(t, tc.wantProf, gotProf) - assert.Equal(t, tc.wantDev, gotDev) - assert.Equal(t, tc.wantByWhat, gotByWhat) - }) - } -} - -func TestInitMw_ServeDNS_ddr(t *testing.T) { - const ( - resolverName = "dns.example.com" - resolverFQDN = resolverName + "." - - targetWithID = testDeviceID + ".d." + resolverName + "." - - ddrFQDN = ddrDomain + "." - - dohPath = "/dns-query" - ) - - testDevice := &agd.Device{ID: testDeviceID} - - srvs := map[agd.ServerName]*agd.Server{ - "dot": { - TLS: &tls.Config{}, - BindData: []*agd.ServerBindData{{ - AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"), - }}, - Protocol: agd.ProtoDoT, - }, - "doh": { - TLS: &tls.Config{}, - BindData: []*agd.ServerBindData{{ - AddrPort: netip.MustParseAddrPort("5.6.7.8:54321"), - }}, - Protocol: agd.ProtoDoH, - }, - "dns": { - BindData: []*agd.ServerBindData{{ - AddrPort: netip.MustParseAddrPort("2.4.6.8:53"), - }}, - Protocol: agd.ProtoDNS, - LinkedIPEnabled: true, - }, - "dns_nolink": { - BindData: []*agd.ServerBindData{{ - AddrPort: netip.MustParseAddrPort("2.4.6.8:53"), - }}, - Protocol: agd.ProtoDNS, - }, - } - - srvGrp := &agd.ServerGroup{ - TLS: &agd.TLS{ - DeviceIDWildcards: []string{"*.d." + resolverName}, - }, - DDR: &agd.DDR{ - DeviceTargets: stringutil.NewSet(), - PublicTargets: stringutil.NewSet(), - Enabled: true, - }, - Name: agd.ServerGroupName("test_server_group"), - Servers: maps.Values(srvs), - } - - srvGrp.DDR.DeviceTargets.Add("d." + resolverName) - srvGrp.DDR.PublicTargets.Add(resolverName) - - var dev *agd.Device - mw := &initMw{ - messages: agdtest.NewConstructor(), - fltGrp: &agd.FilteringGroup{}, - srvGrp: srvGrp, - db: &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - p = &agd.Profile{} - - return p, dev, nil - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - return nil, nil, profiledb.ErrDeviceNotFound - }, - OnProfileByLinkedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - p = &agd.Profile{} - - return p, dev, nil - }, - }, - geoIP: &agdtest.GeoIP{ - OnSubnetByLocation: func( - _ agd.Country, - _ agd.ASN, - _ netutil.AddrFamily, - ) (_ netip.Prefix, _ error) { - panic("not implemented") - }, - OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { - return nil, nil - }, - }, - errColl: &agdtest.ErrorCollector{ - OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, - }, - } - - pubSVCBTmpls := []*dns.SVCB{ - mw.messages.NewDDRTemplate(agd.ProtoDoH, resolverName, dohPath, nil, nil, 443, 1), - mw.messages.NewDDRTemplate(agd.ProtoDoT, resolverName, "", nil, nil, 853, 1), - mw.messages.NewDDRTemplate(agd.ProtoDoQ, resolverName, "", nil, nil, 853, 1), - } - - devSVCBTmpls := []*dns.SVCB{ - mw.messages.NewDDRTemplate(agd.ProtoDoH, "d."+resolverName, dohPath, nil, nil, 443, 1), - mw.messages.NewDDRTemplate(agd.ProtoDoT, "d."+resolverName, "", nil, nil, 853, 1), - mw.messages.NewDDRTemplate(agd.ProtoDoQ, "d."+resolverName, "", nil, nil, 853, 1), - } - - srvGrp.DDR.PublicRecordTemplates = pubSVCBTmpls - srvGrp.DDR.DeviceRecordTemplates = devSVCBTmpls - - var handler dnsserver.Handler = dnsserver.HandlerFunc(func( - _ context.Context, - _ dnsserver.ResponseWriter, - _ *dns.Msg, - ) (_ error) { - // Make sure we haven't reached the following middleware. - panic("not implemented") - }) - - testCases := []struct { - device *agd.Device - name string - srv *agd.Server - host string - wantTarget string - wantNum int - qtype uint16 - }{{ - device: testDevice, - name: "id", - srv: srvs["dot"], - host: ddrFQDN, - wantTarget: targetWithID, - wantNum: len(pubSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: testDevice, - name: "id_specific", - srv: srvs["dot"], - host: ddrLabel + "." + targetWithID, - wantTarget: targetWithID, - wantNum: len(devSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: nil, - name: "no_id", - srv: srvs["dot"], - host: ddrFQDN, - wantTarget: resolverFQDN, - wantNum: len(pubSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: testDevice, - name: "linked_ip", - srv: srvs["dns"], - host: ddrFQDN, - wantTarget: targetWithID, - wantNum: len(pubSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: testDevice, - name: "no_linked_ip", - srv: srvs["dns_nolink"], - host: ddrFQDN, - wantTarget: resolverFQDN, - wantNum: len(pubSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: testDevice, - name: "public_resolver_name", - srv: srvs["dot"], - host: ddrLabel + "." + resolverFQDN, - wantTarget: targetWithID, - wantNum: len(pubSVCBTmpls), - qtype: dns.TypeSVCB, - }, { - device: nil, - name: "arpa_not_ddr_svcb", - srv: srvs["dot"], - host: dns.Fqdn(ddrLabel + ".something.else." + resolverArpaDomain), - wantTarget: "", - wantNum: 0, - qtype: dns.TypeSVCB, - }, { - device: nil, - name: "arpa_ddr_not_svcb", - srv: srvs["dot"], - host: ddrFQDN, - wantTarget: "", - wantNum: 0, - qtype: dns.TypeA, - }} - - for _, tc := range testCases { - mw.srv = tc.srv - dev = tc.device - - var tlsServerName string - switch mw.srv.Protocol { - case agd.ProtoDoT, agd.ProtoDoQ: - tlsServerName = resolverName - if dev != nil { - tlsServerName = string(dev.ID) + ".d." + tlsServerName - } - default: - // Go on. - } - - h := mw.Wrap(handler) - - req := &dns.Msg{ - Question: []dns.Question{{ - Name: tc.host, - Qtype: tc.qtype, - Qclass: dns.ClassINET, - }}, - } - - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{ - TLSServerName: tlsServerName, - }) - - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) - - err := h.ServeDNS(ctx, rw, req) - require.NoError(t, err) - - resp := rw.Msg() - require.NotNil(t, resp) - - if tc.wantNum == 0 { - assert.Empty(t, resp.Answer) - - return - } - - assert.Len(t, resp.Answer, tc.wantNum) - for _, rr := range resp.Answer { - svcb := testutil.RequireTypeAssert[*dns.SVCB](t, rr) - - assert.Equal(t, tc.wantTarget, svcb.Target) - assert.Equal(t, tc.host, svcb.Hdr.Name) - } - }) - } -} - -func TestInitMw_ServeDNS_specialDomain(t *testing.T) { - testCases := []struct { - name string - host string - qtype dnsmsg.RRType - fltGrpBlocked bool - hasProf bool - profBlocked bool - wantRCode dnsmsg.RCode - }{{ - name: "private_relay_blocked_by_fltgrp", - host: applePrivateRelayMaskHost, - qtype: dns.TypeA, - fltGrpBlocked: true, - hasProf: false, - profBlocked: false, - wantRCode: dns.RcodeNameError, - }, { - name: "no_special_domain", - host: "www.example.com", - qtype: dns.TypeA, - fltGrpBlocked: true, - hasProf: false, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "no_private_relay_qtype", - host: applePrivateRelayMaskHost, - qtype: dns.TypeTXT, - fltGrpBlocked: true, - hasProf: false, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "private_relay_blocked_by_prof", - host: applePrivateRelayMaskHost, - qtype: dns.TypeA, - fltGrpBlocked: false, - hasProf: true, - profBlocked: true, - wantRCode: dns.RcodeNameError, - }, { - name: "private_relay_allowed_by_prof", - host: applePrivateRelayMaskHost, - qtype: dns.TypeA, - fltGrpBlocked: true, - hasProf: true, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "private_relay_allowed_by_both", - host: applePrivateRelayMaskHost, - qtype: dns.TypeA, - fltGrpBlocked: false, - hasProf: true, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "private_relay_blocked_by_both", - host: applePrivateRelayMaskHost, - qtype: dns.TypeA, - fltGrpBlocked: true, - hasProf: true, - profBlocked: true, - wantRCode: dns.RcodeNameError, - }, { - name: "firefox_canary_allowed_by_prof", - host: firefoxCanaryHost, - qtype: dns.TypeA, - fltGrpBlocked: false, - hasProf: true, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "firefox_canary_allowed_by_fltgrp", - host: firefoxCanaryHost, - qtype: dns.TypeA, - fltGrpBlocked: false, - hasProf: false, - profBlocked: false, - wantRCode: dns.RcodeSuccess, - }, { - name: "firefox_canary_blocked_by_prof", - host: firefoxCanaryHost, - qtype: dns.TypeA, - fltGrpBlocked: false, - hasProf: true, - profBlocked: true, - wantRCode: dns.RcodeRefused, - }, { - name: "firefox_canary_blocked_by_fltgrp", - host: firefoxCanaryHost, - qtype: dns.TypeA, - fltGrpBlocked: true, - hasProf: false, - profBlocked: false, - wantRCode: dns.RcodeRefused, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var handler dnsserver.Handler = dnsserver.HandlerFunc(func( - ctx context.Context, - rw dnsserver.ResponseWriter, - req *dns.Msg, - ) (err error) { - if tc.wantRCode != dns.RcodeSuccess { - return errors.Error("unexpectedly reached handler") - } - - resp := (&dns.Msg{}).SetReply(req) - - return rw.WriteMsg(ctx, req, resp) - }) - - onProfileByLinkedIP := func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - if !tc.hasProf { - return nil, nil, profiledb.ErrDeviceNotFound - } - - prof := &agd.Profile{ - BlockPrivateRelay: tc.profBlocked, - BlockFirefoxCanary: tc.profBlocked, - } - - return prof, &agd.Device{}, nil - } - db := &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - return nil, nil, profiledb.ErrDeviceNotFound - }, - OnProfileByLinkedIP: onProfileByLinkedIP, - } - - geoIP := &agdtest.GeoIP{ - OnSubnetByLocation: func( - _ agd.Country, - _ agd.ASN, - _ netutil.AddrFamily, - ) (n netip.Prefix, err error) { - panic("not implemented") - }, - OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { - return nil, nil - }, - } - - errColl := &agdtest.ErrorCollector{ - OnCollect: func(_ context.Context, _ error) { - panic("not implemented") - }, - } - - mw := &initMw{ - messages: agdtest.NewConstructor(), - fltGrp: &agd.FilteringGroup{ - BlockPrivateRelay: tc.fltGrpBlocked, - BlockFirefoxCanary: tc.fltGrpBlocked, - }, - srvGrp: &agd.ServerGroup{}, - srv: &agd.Server{ - Protocol: agd.ProtoDNS, - LinkedIPEnabled: true, - }, - db: db, - geoIP: geoIP, - errColl: errColl, - } - - h := mw.Wrap(handler) - - ctx := context.Background() - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) - req := &dns.Msg{ - Question: []dns.Question{{ - Name: dns.Fqdn(tc.host), - Qtype: tc.qtype, - Qclass: dns.ClassINET, - }}, - } - - err := h.ServeDNS(ctx, rw, req) - require.NoError(t, err) - - resp := rw.Msg() - require.NotNil(t, resp) - - assert.Equal(t, tc.wantRCode, dnsmsg.RCode(resp.Rcode)) - }) - } -} - -var errSink error - -func BenchmarkInitMw_Wrap(b *testing.B) { - const devIDTarget = "dns.example.com" - srvGrp := &agd.ServerGroup{ - TLS: &agd.TLS{ - DeviceIDWildcards: []string{"*." + devIDTarget}, - }, - DDR: &agd.DDR{ - DeviceTargets: stringutil.NewSet(), - PublicTargets: stringutil.NewSet(), - Enabled: true, - }, - Name: agd.ServerGroupName("test_server_group"), - Servers: []*agd.Server{{ - BindData: []*agd.ServerBindData{{ - AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"), - }, { - AddrPort: netip.MustParseAddrPort("4.3.2.1:12345"), - }}, - Protocol: agd.ProtoDoT, - }}, - } - - messages := agdtest.NewConstructor() - - ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindData[0].AddrPort.Addr()} - ipv6Hints := []netip.Addr{netip.MustParseAddr("2001::1234")} - - srvGrp.DDR.DeviceTargets.Add(devIDTarget) - srvGrp.DDR.DeviceRecordTemplates = []*dns.SVCB{ - messages.NewDDRTemplate(agd.ProtoDoH, devIDTarget, "/dns", ipv4Hints, ipv6Hints, 443, 1), - messages.NewDDRTemplate(agd.ProtoDoT, devIDTarget, "", ipv4Hints, ipv6Hints, 853, 1), - messages.NewDDRTemplate(agd.ProtoDoQ, devIDTarget, "", ipv4Hints, ipv6Hints, 853, 1), - } - - mw := &initMw{ - messages: messages, - fltGrp: &agd.FilteringGroup{}, - srvGrp: srvGrp, - srv: srvGrp.Servers[0], - geoIP: &agdtest.GeoIP{ - OnSubnetByLocation: func( - _ agd.Country, - _ agd.ASN, - _ netutil.AddrFamily, - ) (n netip.Prefix, err error) { - panic("not implemented") - }, - OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { - return nil, nil - }, - }, - errColl: &agdtest.ErrorCollector{ - OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, - }, - } - - prof := &agd.Profile{} - dev := &agd.Device{} - - ctx := context.Background() - ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{ - TLSServerName: testDeviceID + ".dns.example.com", - }) - - req := &dns.Msg{ - Question: []dns.Question{{ - Name: "example.net", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }}, - } - resp := new(dns.Msg).SetReply(req) - - var handler dnsserver.Handler = dnsserver.HandlerFunc(func( - ctx context.Context, - rw dnsserver.ResponseWriter, - req *dns.Msg, - ) (err error) { - return rw.WriteMsg(ctx, req, resp) - }) - - handler = mw.Wrap(handler) - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) - - mw.db = &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - return prof, dev, nil - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - OnProfileByLinkedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - } - b.Run("success", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - errSink = handler.ServeDNS(ctx, rw, req) - } - - assert.NoError(b, errSink) - }) - - mw.db = &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - return nil, nil, profiledb.ErrDeviceNotFound - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - OnProfileByLinkedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - } - b.Run("not_found", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - errSink = handler.ServeDNS(ctx, rw, req) - } - - assert.NoError(b, errSink) - }) - - ffReq := &dns.Msg{ - Question: []dns.Question{{ - Name: "use-application-dns.net.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }}, - } - mw.db = &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - return prof, dev, nil - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - OnProfileByLinkedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - } - b.Run("firefox_canary", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - errSink = handler.ServeDNS(ctx, rw, ffReq) - } - - assert.NoError(b, errSink) - }) - - ddrReq := &dns.Msg{ - Question: []dns.Question{{ - // Check the worst case when wildcards are checked. - Name: "_dns." + testDeviceID + ".dns.example.com.", - Qtype: dns.TypeSVCB, - Qclass: dns.ClassINET, - }}, - } - devWithID := &agd.Device{ - ID: testDeviceID, - } - mw.db = &agdtest.ProfileDB{ - OnProfileByDeviceID: func( - _ context.Context, - _ agd.DeviceID, - ) (p *agd.Profile, d *agd.Device, err error) { - return prof, devWithID, nil - }, - OnProfileByDedicatedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - OnProfileByLinkedIP: func( - _ context.Context, - _ netip.Addr, - ) (p *agd.Profile, d *agd.Device, err error) { - panic("not implemented") - }, - } - b.Run("ddr", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - errSink = handler.ServeDNS(ctx, rw, ddrReq) - } - - assert.NoError(b, errSink) - }) -} diff --git a/internal/dnssvc/internal/accessmw/access.go b/internal/dnssvc/internal/accessmw/access.go new file mode 100644 index 0000000..54fe0d0 --- /dev/null +++ b/internal/dnssvc/internal/accessmw/access.go @@ -0,0 +1,80 @@ +// Package accessmw contains the access middleware of the AdGuard DNS server. +// It filters out the domain scanners and other requests by specified AdBlock +// rules and IP subnets. +package accessmw + +import ( + "context" + + "github.com/AdguardTeam/AdGuardDNS/internal/access" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" + "github.com/miekg/dns" +) + +// type check +var _ dnsserver.Middleware = (*Middleware)(nil) + +// Middleware is the access middleware of the AdGuard DNS server. +type Middleware struct { + accessManager access.Interface +} + +// Config is the configuration structure for the access middleware. All fields +// must be non-nil. +type Config struct { + AccessManager access.Interface +} + +// New returns a new access middleware. c must not be nil. +func New(c *Config) (mw *Middleware) { + return &Middleware{ + accessManager: c.AccessManager, + } +} + +// type check +var _ dnsserver.Middleware = (*Middleware)(nil) + +// Wrap implements the [dnsserver.Middleware] interface for *Middleware +func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) { + f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) { + defer func() { err = errors.Annotate(err, "access mw: %w") }() + + rAddr := netutil.NetAddrToAddrPort(rw.RemoteAddr()).Addr() + if blocked, _ := mw.accessManager.IsBlockedIP(rAddr); blocked { + metrics.AccessBlockedForSubnetTotal.Inc() + + return nil + } + + // Assume that module dnsserver has already validated that the request + // always has exactly one question for us. + q := req.Question[0] + if blocked := mw.accessManager.IsBlockedHost(normalizeDomain(q.Name), q.Qtype); blocked { + metrics.AccessBlockedForHostTotal.Inc() + + return nil + } + + return next.ServeDNS(ctx, rw, req) + } + + return dnsserver.HandlerFunc(f) +} + +// normalizeDomain returns a lowercased version of the host without the final +// dot, unless the host is ".", in which case it returns the unchanged host. +// That is the special case to allow matching queries like: +// +// dig IN NS '.' +func normalizeDomain(host string) (norm string) { + if host == "." { + return host + } + + return agdnet.NormalizeDomain(host) +} diff --git a/internal/dnssvc/internal/accessmw/access_test.go b/internal/dnssvc/internal/accessmw/access_test.go new file mode 100644 index 0000000..db1712c --- /dev/null +++ b/internal/dnssvc/internal/accessmw/access_test.go @@ -0,0 +1,135 @@ +package accessmw_test + +import ( + "context" + "net" + "net/netip" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/access" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/accessmw" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiddleware_Wrap(t *testing.T) { + am, accessErr := access.New([]string{ + "block.test", + "UPPERCASE.test", + "||block_aaaa.test^$dnstype=AAAA", + }, []string{ + "1.1.1.1", + "2.2.2.0/8", + }) + require.NoError(t, accessErr) + + amw := accessmw.New(&accessmw.Config{ + AccessManager: am, + }) + + testCases := []struct { + wantResp assert.BoolAssertionFunc + name string + host string + ip net.IP + qtype uint16 + }{{ + ip: net.IP{1, 1, 1, 0}, + name: "pass_ip", + host: "pass.test", + qtype: dns.TypeA, + wantResp: assert.True, + }, { + name: "block_ip", + ip: net.IP{1, 1, 1, 1}, + host: "pass.test", + qtype: dns.TypeA, + wantResp: assert.False, + }, { + name: "pass_subnet", + ip: net.IP{1, 2, 2, 2}, + host: "pass.test", + qtype: dns.TypeA, + wantResp: assert.True, + }, { + name: "block_subnet", + ip: net.IP{2, 2, 2, 2}, + host: "pass.test", + qtype: dns.TypeA, + wantResp: assert.False, + }, { + wantResp: assert.True, + name: "pass_domain", + host: "pass.test", + qtype: dns.TypeA, + }, { + wantResp: assert.False, + name: "blocked_domain_A", + host: "block.test", + qtype: dns.TypeA, + }, { + wantResp: assert.False, + name: "blocked_domain_HTTPS", + host: "block.test", + qtype: dns.TypeHTTPS, + }, { + wantResp: assert.False, + name: "uppercase_domain", + host: "uppercase.test", + qtype: dns.TypeHTTPS, + }, { + wantResp: assert.True, + name: "pass_qt", + host: "block_aaaa.test", + qtype: dns.TypeA, + }, { + wantResp: assert.False, + name: "block_qt", + host: "block_aaaa.test", + qtype: dns.TypeAAAA, + }} + + var handler dnsserver.Handler = dnsserver.HandlerFunc(func( + ctx context.Context, + rw dnsserver.ResponseWriter, + q *dns.Msg, + ) (_ error) { + resp := dnsservertest.NewResp( + dns.RcodeSuccess, + q, + dnsservertest.SectionAnswer{ + dnsservertest.NewA("test.domain", 0, netip.MustParseAddr("5.5.5.5")), + }, + ) + + err := rw.WriteMsg(ctx, q, resp) + if err != nil { + return err + } + + return nil + }) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rw := dnsserver.NewNonWriterResponseWriter(nil, &net.TCPAddr{IP: tc.ip, Port: 5357}) + req := &dns.Msg{ + Question: []dns.Question{{ + Name: tc.host, + Qtype: tc.qtype, + Qclass: dns.ClassINET, + }}, + } + + h := amw.Wrap(handler) + err := h.ServeDNS(context.Background(), rw, req) + require.NoError(t, err) + + resp := rw.Msg() + tc.wantResp(t, resp != nil) + }) + } +} diff --git a/internal/dnssvc/internal/dnssvctest/dnssvctest.go b/internal/dnssvc/internal/dnssvctest/dnssvctest.go new file mode 100644 index 0000000..1fba36e --- /dev/null +++ b/internal/dnssvc/internal/dnssvctest/dnssvctest.go @@ -0,0 +1,44 @@ +// Package dnssvctest contains common constants and utilities for the internal +// DNS-service packages. +package dnssvctest + +import ( + "net" + "net/netip" + "time" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" +) + +// Timeout is the common timeout for tests. +const Timeout time.Duration = 1 * time.Second + +// String representations of the common IDs for tests. +const ( + DeviceIDStr = "dev1234" + ProfileIDStr = "prof1234" +) + +// DeviceID is the common device ID for tests. +const DeviceID agd.DeviceID = DeviceIDStr + +// ProfileID is the common profile ID for tests. +const ProfileID agd.ProfileID = ProfileIDStr + +// Common addresses for tests. +var ( + ClientIP = net.IP{1, 2, 3, 4} + RemoteAddr = &net.TCPAddr{ + IP: ClientIP, + Port: 12345, + } + + ClientAddrPort = RemoteAddr.AddrPort() + ClientAddr = ClientAddrPort.Addr() + + ServerAddr = netip.MustParseAddr("5.6.7.8") + LocalAddr = &net.TCPAddr{ + IP: ServerAddr.AsSlice(), + Port: 54321, + } +) diff --git a/internal/dnssvc/deviceid.go b/internal/dnssvc/internal/initial/deviceid.go similarity index 99% rename from internal/dnssvc/deviceid.go rename to internal/dnssvc/internal/initial/deviceid.go index 9710664..2e5f5f6 100644 --- a/internal/dnssvc/deviceid.go +++ b/internal/dnssvc/internal/initial/deviceid.go @@ -1,4 +1,4 @@ -package dnssvc +package initial import ( "context" @@ -16,8 +16,6 @@ import ( "github.com/miekg/dns" ) -// Device ID Extraction - // deviceIDFromClientServerName extracts and validates a device ID. cliSrvName // is the server name as sent by the client. wildcards are the domain wildcards // for device ID detection. diff --git a/internal/dnssvc/deviceid_internal_test.go b/internal/dnssvc/internal/initial/deviceid_internal_test.go similarity index 86% rename from internal/dnssvc/deviceid_internal_test.go rename to internal/dnssvc/internal/initial/deviceid_internal_test.go index ebb6559..2dab12f 100644 --- a/internal/dnssvc/deviceid_internal_test.go +++ b/internal/dnssvc/internal/initial/deviceid_internal_test.go @@ -1,4 +1,4 @@ -package dnssvc +package initial import ( "context" @@ -9,13 +9,14 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestService_Wrap_deviceID(t *testing.T) { +func TestDeviceIDFromContext(t *testing.T) { testCases := []struct { name string cliSrvName string @@ -46,8 +47,8 @@ func TestService_Wrap_deviceID(t *testing.T) { proto: agd.ProtoDoT, }, { name: "tls_device_id", - cliSrvName: testDeviceID + ".dns.example.com", - wantDeviceID: testDeviceID, + cliSrvName: dnssvctest.DeviceIDStr + ".dns.example.com", + wantDeviceID: dnssvctest.DeviceID, wantErrMsg: "", wildcards: []string{"*.dns.example.com"}, proto: agd.ProtoDoT, @@ -61,7 +62,7 @@ func TestService_Wrap_deviceID(t *testing.T) { proto: agd.ProtoDoT, }, { name: "tls_deep_subdomain", - cliSrvName: "abc." + testDeviceID + ".dns.example.com", + cliSrvName: "abc." + dnssvctest.DeviceIDStr + ".dns.example.com", wantDeviceID: "", wantErrMsg: "", wildcards: []string{"*.dns.example.com"}, @@ -79,8 +80,8 @@ func TestService_Wrap_deviceID(t *testing.T) { proto: agd.ProtoDoT, }, { name: "quic_device_id", - cliSrvName: testDeviceID + ".dns.example.com", - wantDeviceID: testDeviceID, + cliSrvName: dnssvctest.DeviceIDStr + ".dns.example.com", + wantDeviceID: dnssvctest.DeviceID, wantErrMsg: "", wildcards: []string{"*.dns.example.com"}, proto: agd.ProtoDoQ, @@ -93,8 +94,8 @@ func TestService_Wrap_deviceID(t *testing.T) { proto: agd.ProtoDoT, }, { name: "tls_device_id_subdomain_wildcard", - cliSrvName: testDeviceID + ".sub.dns.example.com", - wantDeviceID: testDeviceID, + cliSrvName: dnssvctest.DeviceIDStr + ".sub.dns.example.com", + wantDeviceID: dnssvctest.DeviceID, wantErrMsg: "", wildcards: []string{ "*.dns.example.com", @@ -117,7 +118,7 @@ func TestService_Wrap_deviceID(t *testing.T) { } } -func TestService_Wrap_deviceIDHTTPS(t *testing.T) { +func TestDeviceIDFromContext_https(t *testing.T) { testCases := []struct { name string path string @@ -135,13 +136,13 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) { wantErrMsg: "", }, { name: "device_id", - path: "/dns-query/" + testDeviceID, - wantDeviceID: testDeviceID, + path: "/dns-query/" + dnssvctest.DeviceIDStr, + wantDeviceID: dnssvctest.DeviceID, wantErrMsg: "", }, { name: "device_id_slash", - path: "/dns-query/" + testDeviceID + "/", - wantDeviceID: testDeviceID, + path: "/dns-query/" + dnssvctest.DeviceIDStr + "/", + wantDeviceID: dnssvctest.DeviceID, wantErrMsg: "", }, { name: "bad_url", @@ -150,10 +151,10 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) { wantErrMsg: `http url device id check: bad path "/foo"`, }, { name: "extra", - path: "/dns-query/" + testDeviceID + "/foo", + path: "/dns-query/" + dnssvctest.DeviceIDStr + "/foo", wantDeviceID: "", - wantErrMsg: `http url device id check: bad path "/dns-query/` + testDeviceID + `/foo": ` + - `extra parts`, + wantErrMsg: `http url device id check: bad path "/dns-query/` + dnssvctest.DeviceIDStr + + `/foo": extra parts`, }, { name: "bad_device_id", path: "/dns-query/!!!", @@ -186,7 +187,7 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) { t.Run("domain_name", func(t *testing.T) { u := &url.URL{ Scheme: "https", - Host: testDeviceID + ".dns.example.com", + Host: dnssvctest.DeviceIDStr + ".dns.example.com", Path: "/dns-query", } @@ -202,11 +203,11 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) { deviceID, err := deviceIDFromContext(ctx, proto, []string{"*.dns.example.com"}) require.NoError(t, err) - assert.Equal(t, agd.DeviceID(testDeviceID), deviceID) + assert.Equal(t, agd.DeviceID(dnssvctest.DeviceID), deviceID) }) } -func TestService_Wrap_deviceIDFromEDNS(t *testing.T) { +func TestDeviceIDFromEDNS(t *testing.T) { testCases := []struct { name string opt dns.EDNS0 diff --git a/internal/dnssvc/internal/initial/initial.go b/internal/dnssvc/internal/initial/initial.go new file mode 100644 index 0000000..9669a34 --- /dev/null +++ b/internal/dnssvc/internal/initial/initial.go @@ -0,0 +1,284 @@ +// Package initial contains the initial, outermost (except for ratelimit) +// middleware of the AdGuard DNS server. It filters out the Firefox canary +// domain logic, sets and resets the AD bit for further processing, as well as +// puts as much information as it can into the context and request info. +package initial + +import ( + "context" + "fmt" + "net" + "net/netip" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" + "github.com/AdguardTeam/AdGuardDNS/internal/agdsync" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/geoip" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" + "github.com/miekg/dns" +) + +// Middleware is the initial middleware of the AdGuard DNS server. This +// middleware must be the most outer middleware apart from the ratelimit one. +type Middleware struct { + // messages is used to build the responses specific for the request's + // context. + messages *dnsmsg.Constructor + + // fltGrp is the filtering group to which srv belongs. + fltGrp *agd.FilteringGroup + + // srvGrp is the server group to which srv belongs. + srvGrp *agd.ServerGroup + + // srv is the current server which serves the request. + srv *agd.Server + + // pool is the pool of [agd.RequestInfo] values. + pool *agdsync.TypedPool[agd.RequestInfo] + + // db is the database of user profiles and devices. + db profiledb.Interface + + // geoIP detects the location of the request source. + geoIP geoip.Interface + + // errColl collects and reports the errors considered non-critical. + errColl agd.ErrorCollector +} + +// Config is the configuration structure for the initial middleware. All fields +// must be non-nil. +type Config struct { + // messages is used to build the responses specific for a request's context. + Messages *dnsmsg.Constructor + + // FilteringGroup is the filtering group to which Server belongs. + FilteringGroup *agd.FilteringGroup + + // ServerGroup is the server group to which Server belongs. + ServerGroup *agd.ServerGroup + + // Server is the current server which serves the request. + Server *agd.Server + + // DB is the database of user profiles and devices. + ProfileDB profiledb.Interface + + // GeoIP detects the location of the request source. + GeoIP geoip.Interface + + // ErrColl collects and reports the errors considered non-critical. + ErrColl agd.ErrorCollector +} + +// New returns a new initial middleware. c must not be nil. +func New(c *Config) (mw *Middleware) { + return &Middleware{ + messages: c.Messages, + fltGrp: c.FilteringGroup, + srvGrp: c.ServerGroup, + srv: c.Server, + pool: agdsync.NewTypedPool(func() (v *agd.RequestInfo) { + return &agd.RequestInfo{} + }), + db: c.ProfileDB, + geoIP: c.GeoIP, + errColl: c.ErrColl, + } +} + +// type check +var _ dnsserver.Middleware = (*Middleware)(nil) + +// Wrap implements the [dnsserver.Middleware] interface for *Middleware +func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) { + f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) { + defer func() { err = errors.Annotate(err, "init mw: %w") }() + + // Save the actual value of the request AD and DO bits and set the AD + // bit in the request to true, so that the upstream validates the data + // and caches the actual value of the response AD bit. Restore it + // later, depending on the request and response data. + reqAD := req.AuthenticatedData + reqDO := dnsmsg.IsDO(req) + req.AuthenticatedData = true + + // Assume that module dnsserver has already validated that the request + // always has exactly one question for us. + q := req.Question[0] + qt := q.Qtype + cl := q.Qclass + + // Get the request's information, such as GeoIP data and user profiles. + ri, err := mw.newRequestInfo(ctx, req, rw.LocalAddr(), rw.RemoteAddr(), q.Name, qt, cl) + if err != nil { + // Don't wrap the error, because this is the main flow, and there is + // already [errors.Annotate] here. + return mw.processReqInfoErr(ctx, rw, req, err) + } + defer mw.pool.Put(ri) + + if specHdlr, name := mw.reqInfoSpecialHandler(ri, cl); specHdlr != nil { + optlog.Debug1("init mw: got req-info special handler %s", name) + + // Don't wrap the error, because it's informative enough as is, and + // because if handled is true, the main flow terminates here. + return specHdlr(ctx, rw, req, ri) + } + + ctx = agd.ContextWithRequestInfo(ctx, ri) + + // Record the response, restore the AD bit value in both the request and + // the response, and write the response. + nwrw := internal.MakeNonWriter(rw) + err = next.ServeDNS(ctx, nwrw, req) + if err != nil { + // Don't wrap the error, because this is the main flow, and there is + // already errors.Annotate here. + return err + } + + resp := nwrw.Msg() + + // Following RFC 6840, set the AD bit in the response only when the + // response is authenticated, and the request contained either a set DO + // bit or a set AD bit. + // + // See https://datatracker.ietf.org/doc/html/rfc6840#section-5.8. + resp.AuthenticatedData = resp.AuthenticatedData && (reqAD || reqDO) + + err = rw.WriteMsg(ctx, req, resp) + + return errors.Annotate(err, "writing resp: %w") + } + + return dnsserver.HandlerFunc(f) +} + +// newRequestInfo returns the new request information structure using the +// middleware's configuration and values from ctx. +func (mw *Middleware) newRequestInfo( + ctx context.Context, + req *dns.Msg, + laddr net.Addr, + raddr net.Addr, + fqdn string, + qt dnsmsg.RRType, + cl dnsmsg.Class, +) (ri *agd.RequestInfo, err error) { + ri = mw.pool.Get() + + // Use ri as an argument here to evaluate and save the non-nil value of ri + // and prevent returns with an error from overwriting ri with nil. + defer func(fromPool *agd.RequestInfo) { + if err != nil { + mw.pool.Put(fromPool) + } + }(ri) + + // Clear all fields that must be set later. + ri.Device = nil + ri.Profile = nil + ri.ECS = nil + ri.Location = nil + + // Put the host, server, and client IP data into the request information + // immediately. + ri.FilteringGroup = mw.fltGrp + ri.Messages = mw.messages + ri.RemoteIP = netutil.NetAddrToAddrPort(raddr).Addr() + ri.ServerGroup = mw.srvGrp.Name + ri.Server = mw.srv.Name + ri.Host = agdnet.NormalizeDomain(fqdn) + ri.QType = qt + ri.QClass = cl + + // As an optimization, put the request ID closer to the top of the context + // stack. + ri.ID, _ = agd.RequestIDFromContext(ctx) + + // Add the GeoIP information, if any. + err = mw.addLocation(ctx, ri, req) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, err + } + + // Add the profile information, if any. + localIP := netutil.NetAddrToAddrPort(laddr).Addr() + err = mw.addProfile(ctx, ri, req, localIP) + if err != nil { + // Don't wrap the error, because it's informative enough as is. + return nil, err + } + + return ri, nil +} + +// addLocation adds GeoIP location information about the client's remote address +// as well as the EDNS Client Subnet information, if there is one, to ri. err +// is not nil only if req contains a malformed EDNS Client Subnet option. +func (mw *Middleware) addLocation(ctx context.Context, ri *agd.RequestInfo, req *dns.Msg) (err error) { + ri.Location = mw.locationData(ctx, ri.RemoteIP, "client") + + ecs, scope, err := dnsmsg.ECSFromMsg(req) + if err != nil { + return fmt.Errorf("adding ecs info: %w", err) + } else if ecs != (netip.Prefix{}) { + ri.ECS = &agd.ECS{ + Location: mw.locationData(ctx, ecs.Addr(), "ecs"), + Subnet: ecs, + Scope: scope, + } + } + + return nil +} + +// locationData returns the GeoIP location information about the IP address. +// typ is the type of data being requested for error reporting and logging. +func (mw *Middleware) locationData(ctx context.Context, ip netip.Addr, typ string) (l *agd.Location) { + l, err := mw.geoIP.Data("", ip) + if err != nil { + // Consider GeoIP errors non-critical. Report and go on. + agd.Collectf(ctx, mw.errColl, "init mw: getting geoip for %s ip: %w", typ, err) + } + + if l == nil { + optlog.Debug2("init mw: no geoip for %s ip %s", typ, ip) + } else { + optlog.Debug4("init mw: found country/asn %q/%d for %s ip %s", l.Country, l.ASN, typ, ip) + } + + return l +} + +// processReqInfoErr processes the error returned by [Middleware.newRequestInfo] +// and returns the properly handled and/or wrapped error. +func (mw *Middleware) processReqInfoErr( + ctx context.Context, + rw dnsserver.ResponseWriter, + req *dns.Msg, + origErr error, +) (err error) { + var ecsErr dnsmsg.BadECSError + if errors.As(origErr, &ecsErr) { + // We've got a bad ECS option. Log and respond with a FORMERR + // immediately. + optlog.Debug1("init mw: %s", origErr) + + writeErr := rw.WriteMsg(ctx, req, mw.messages.NewMsgFORMERR(req)) + writeErr = errors.Annotate(writeErr, "writing formerr resp: %w") + + return errors.WithDeferred(origErr, writeErr) + } + + return origErr +} diff --git a/internal/dnssvc/internal/initial/initial_test.go b/internal/dnssvc/internal/initial/initial_test.go new file mode 100644 index 0000000..f666d23 --- /dev/null +++ b/internal/dnssvc/internal/initial/initial_test.go @@ -0,0 +1,660 @@ +package initial_test + +import ( + "context" + "crypto/tls" + "net/netip" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/stringutil" + "github.com/AdguardTeam/golibs/testutil" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" +) + +func TestMiddleware_Wrap(t *testing.T) { + const ( + resolverName = "dns.example.com" + resolverFQDN = resolverName + "." + + targetWithID = dnssvctest.DeviceIDStr + ".d." + resolverName + "." + + ddrFQDN = initial.DDRDomain + "." + + dohPath = "/dns-query" + ) + + testDevice := &agd.Device{ID: dnssvctest.DeviceID} + + srvs := map[agd.ServerName]*agd.Server{ + "dot": { + TLS: &tls.Config{}, + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"), + }}, + Protocol: agd.ProtoDoT, + }, + "doh": { + TLS: &tls.Config{}, + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("5.6.7.8:54321"), + }}, + Protocol: agd.ProtoDoH, + }, + "dns": { + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("2.4.6.8:53"), + }}, + Protocol: agd.ProtoDNS, + LinkedIPEnabled: true, + }, + "dns_nolink": { + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("2.4.6.8:53"), + }}, + Protocol: agd.ProtoDNS, + }, + } + + srvGrp := &agd.ServerGroup{ + TLS: &agd.TLS{ + DeviceIDWildcards: []string{"*.d." + resolverName}, + }, + DDR: &agd.DDR{ + DeviceTargets: stringutil.NewSet(), + PublicTargets: stringutil.NewSet(), + Enabled: true, + }, + Name: agd.ServerGroupName("test_server_group"), + Servers: maps.Values(srvs), + } + + srvGrp.DDR.DeviceTargets.Add("d." + resolverName) + srvGrp.DDR.PublicTargets.Add(resolverName) + + geoIP := &agdtest.GeoIP{ + OnSubnetByLocation: func( + _ agd.Country, + _ agd.ASN, + _ netutil.AddrFamily, + ) (_ netip.Prefix, _ error) { + panic("not implemented") + }, + OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { + return nil, nil + }, + } + + messages := agdtest.NewConstructor() + + pubSVCBTmpls := []*dns.SVCB{ + messages.NewDDRTemplate(agd.ProtoDoH, resolverName, dohPath, nil, nil, 443, 1), + messages.NewDDRTemplate(agd.ProtoDoT, resolverName, "", nil, nil, 853, 1), + messages.NewDDRTemplate(agd.ProtoDoQ, resolverName, "", nil, nil, 853, 1), + } + + devSVCBTmpls := []*dns.SVCB{ + messages.NewDDRTemplate(agd.ProtoDoH, "d."+resolverName, dohPath, nil, nil, 443, 1), + messages.NewDDRTemplate(agd.ProtoDoT, "d."+resolverName, "", nil, nil, 853, 1), + messages.NewDDRTemplate(agd.ProtoDoQ, "d."+resolverName, "", nil, nil, 853, 1), + } + + srvGrp.DDR.PublicRecordTemplates = pubSVCBTmpls + srvGrp.DDR.DeviceRecordTemplates = devSVCBTmpls + + var handler dnsserver.Handler = dnsserver.HandlerFunc(func( + _ context.Context, + _ dnsserver.ResponseWriter, + _ *dns.Msg, + ) (_ error) { + // Make sure we haven't reached the following middleware. + panic("not implemented") + }) + + testCases := []struct { + device *agd.Device + name string + srv *agd.Server + host string + wantTarget string + wantNum int + qtype uint16 + }{{ + device: testDevice, + name: "id", + srv: srvs["dot"], + host: ddrFQDN, + wantTarget: targetWithID, + wantNum: len(pubSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: testDevice, + name: "id_specific", + srv: srvs["dot"], + host: initial.DDRLabel + "." + targetWithID, + wantTarget: targetWithID, + wantNum: len(devSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: nil, + name: "no_id", + srv: srvs["dot"], + host: ddrFQDN, + wantTarget: resolverFQDN, + wantNum: len(pubSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: testDevice, + name: "linked_ip", + srv: srvs["dns"], + host: ddrFQDN, + wantTarget: targetWithID, + wantNum: len(pubSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: testDevice, + name: "no_linked_ip", + srv: srvs["dns_nolink"], + host: ddrFQDN, + wantTarget: resolverFQDN, + wantNum: len(pubSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: testDevice, + name: "public_resolver_name", + srv: srvs["dot"], + host: initial.DDRLabel + "." + resolverFQDN, + wantTarget: targetWithID, + wantNum: len(pubSVCBTmpls), + qtype: dns.TypeSVCB, + }, { + device: nil, + name: "arpa_not_ddr_svcb", + srv: srvs["dot"], + host: dns.Fqdn( + initial.DDRLabel + ".something.else." + initial.ResolverARPADomain, + ), + wantTarget: "", + wantNum: 0, + qtype: dns.TypeSVCB, + }, { + device: nil, + name: "arpa_ddr_not_svcb", + srv: srvs["dot"], + host: ddrFQDN, + wantTarget: "", + wantNum: 0, + qtype: dns.TypeA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + return &agd.Profile{}, tc.device, nil + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + return nil, nil, profiledb.ErrDeviceNotFound + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + return &agd.Profile{}, tc.device, nil + }, + } + + mw := initial.New(&initial.Config{ + Messages: agdtest.NewConstructor(), + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: tc.srv, + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + ctx := context.Background() + ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{ + TLSServerName: srvNameForProto(tc.device, resolverName, tc.srv.Protocol), + }) + + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) + req := &dns.Msg{ + Question: []dns.Question{{ + Name: tc.host, + Qtype: tc.qtype, + Qclass: dns.ClassINET, + }}, + } + + h := mw.Wrap(handler) + err := h.ServeDNS(ctx, rw, req) + require.NoError(t, err) + + resp := rw.Msg() + require.NotNil(t, resp) + + if tc.wantNum == 0 { + assert.Empty(t, resp.Answer) + + return + } + + assert.Len(t, resp.Answer, tc.wantNum) + for _, rr := range resp.Answer { + svcb := testutil.RequireTypeAssert[*dns.SVCB](t, rr) + + assert.Equal(t, tc.wantTarget, svcb.Target) + assert.Equal(t, tc.host, svcb.Hdr.Name) + } + }) + } +} + +// srvNameForProto returns a client's TLS server name based on the protocol and +// other data. +func srvNameForProto(dev *agd.Device, resolverName string, proto agd.Protocol) (srvName string) { + switch proto { + case agd.ProtoDoT, agd.ProtoDoQ: + srvName = resolverName + if dev != nil { + srvName = string(dev.ID) + ".d." + srvName + } + default: + // Go on. + } + + return srvName +} + +func TestMiddleware_Wrap_error(t *testing.T) { + var handler dnsserver.Handler = dnsserver.HandlerFunc(func( + _ context.Context, + _ dnsserver.ResponseWriter, + _ *dns.Msg, + ) (_ error) { + // Make sure we haven't reached the following middleware. + panic("not implemented") + }) + + srvGrp := &agd.ServerGroup{ + Name: agd.ServerGroupName("test_server_group"), + } + + srv := &agd.Server{ + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("1.2.3.4:53"), + }}, + Protocol: agd.ProtoDNS, + LinkedIPEnabled: true, + } + + geoIP := &agdtest.GeoIP{ + OnSubnetByLocation: func( + _ agd.Country, + _ agd.ASN, + _ netutil.AddrFamily, + ) (_ netip.Prefix, _ error) { + panic("not implemented") + }, + OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { + return nil, nil + }, + } + + const testError errors.Error = errors.Error("test error") + + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + return nil, nil, testError + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + } + + mw := initial.New(&initial.Config{ + Messages: agdtest.NewConstructor(), + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: srv, + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + ctx := context.Background() + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) + req := &dns.Msg{ + Question: []dns.Question{{ + Name: "www.example.com.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } + + h := mw.Wrap(handler) + err := h.ServeDNS(ctx, rw, req) + assert.ErrorIs(t, err, testError) +} + +var errSink error + +func BenchmarkMiddleware_Wrap(b *testing.B) { + const devIDTarget = "dns.example.com" + srvGrp := &agd.ServerGroup{ + TLS: &agd.TLS{ + DeviceIDWildcards: []string{"*." + devIDTarget}, + }, + DDR: &agd.DDR{ + DeviceTargets: stringutil.NewSet(), + PublicTargets: stringutil.NewSet(), + Enabled: true, + }, + Name: agd.ServerGroupName("test_server_group"), + Servers: []*agd.Server{{ + BindData: []*agd.ServerBindData{{ + AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"), + }, { + AddrPort: netip.MustParseAddrPort("4.3.2.1:12345"), + }}, + Protocol: agd.ProtoDoT, + }}, + } + + messages := agdtest.NewConstructor() + + geoIP := &agdtest.GeoIP{ + OnSubnetByLocation: func( + _ agd.Country, + _ agd.ASN, + _ netutil.AddrFamily, + ) (n netip.Prefix, err error) { + panic("not implemented") + }, + OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { + return nil, nil + }, + } + + ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindData[0].AddrPort.Addr()} + ipv6Hints := []netip.Addr{netip.MustParseAddr("2001::1234")} + + srvGrp.DDR.DeviceTargets.Add(devIDTarget) + srvGrp.DDR.DeviceRecordTemplates = []*dns.SVCB{ + messages.NewDDRTemplate(agd.ProtoDoH, devIDTarget, "/dns", ipv4Hints, ipv6Hints, 443, 1), + messages.NewDDRTemplate(agd.ProtoDoT, devIDTarget, "", ipv4Hints, ipv6Hints, 853, 1), + messages.NewDDRTemplate(agd.ProtoDoQ, devIDTarget, "", ipv4Hints, ipv6Hints, 853, 1), + } + + prof := &agd.Profile{} + dev := &agd.Device{} + + ctx := context.Background() + ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{ + TLSServerName: dnssvctest.DeviceIDStr + ".dns.example.com", + }) + + req := &dns.Msg{ + Question: []dns.Question{{ + Name: "example.net", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } + resp := new(dns.Msg).SetReply(req) + + var handler dnsserver.Handler = dnsserver.HandlerFunc(func( + ctx context.Context, + rw dnsserver.ResponseWriter, + req *dns.Msg, + ) (err error) { + return rw.WriteMsg(ctx, req, resp) + }) + + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) + + b.Run("success", func(b *testing.B) { + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + return prof, dev, nil + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + } + + mw := initial.New(&initial.Config{ + Messages: messages, + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: srvGrp.Servers[0], + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + h := mw.Wrap(handler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errSink = h.ServeDNS(ctx, rw, req) + } + + assert.NoError(b, errSink) + }) + + b.Run("not_found", func(b *testing.B) { + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + return nil, nil, profiledb.ErrDeviceNotFound + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + } + + mw := initial.New(&initial.Config{ + Messages: messages, + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: srvGrp.Servers[0], + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + h := mw.Wrap(handler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errSink = h.ServeDNS(ctx, rw, req) + } + + assert.NoError(b, errSink) + }) + + b.Run("firefox_canary", func(b *testing.B) { + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + return prof, dev, nil + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + } + + ffReq := &dns.Msg{ + Question: []dns.Question{{ + Name: "use-application-dns.net.", + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } + + mw := initial.New(&initial.Config{ + Messages: messages, + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: srvGrp.Servers[0], + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + h := mw.Wrap(handler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errSink = h.ServeDNS(ctx, rw, ffReq) + } + + assert.NoError(b, errSink) + }) + + b.Run("ddr", func(b *testing.B) { + devWithID := &agd.Device{ + ID: dnssvctest.DeviceID, + } + + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + return prof, devWithID, nil + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByLinkedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + } + + ddrReq := &dns.Msg{ + Question: []dns.Question{{ + // Check the worst case when wildcards are checked. + Name: "_dns." + dnssvctest.DeviceIDStr + ".dns.example.com.", + Qtype: dns.TypeSVCB, + Qclass: dns.ClassINET, + }}, + } + + mw := initial.New(&initial.Config{ + Messages: messages, + FilteringGroup: &agd.FilteringGroup{}, + ServerGroup: srvGrp, + Server: srvGrp.Servers[0], + ProfileDB: db, + GeoIP: geoIP, + ErrColl: &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { panic("not implemented") }, + }, + }) + + h := mw.Wrap(handler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + errSink = h.ServeDNS(ctx, rw, ddrReq) + } + + assert.NoError(b, errSink) + }) + + // Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU: + // goos: linux + // goarch: amd64 + // pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial + // cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics + // BenchmarkMiddleware_Wrap/success-16 1970464 735.8 ns/op 72 B/op 2 allocs/op + // BenchmarkMiddleware_Wrap/not_found-16 1469100 715.9 ns/op 48 B/op 1 allocs/op + // BenchmarkMiddleware_Wrap/firefox_canary-16 1644410 861.9 ns/op 72 B/op 2 allocs/op + // BenchmarkMiddleware_Wrap/ddr-16 252656 4810 ns/op 1408 B/op 45 allocs/op +} diff --git a/internal/dnssvc/internal/initial/profile.go b/internal/dnssvc/internal/initial/profile.go new file mode 100644 index 0000000..266a87d --- /dev/null +++ b/internal/dnssvc/internal/initial/profile.go @@ -0,0 +1,110 @@ +package initial + +import ( + "context" + "fmt" + "net/netip" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/errors" + "github.com/miekg/dns" +) + +// addProfile adds profile and device information, if any, to the request +// information. +func (mw *Middleware) addProfile( + ctx context.Context, + ri *agd.RequestInfo, + req *dns.Msg, + localIP netip.Addr, +) (err error) { + defer func() { err = errors.Annotate(err, "getting profile from req: %w") }() + + var id agd.DeviceID + if p := mw.srv.Protocol; p.IsStdEncrypted() { + // Assume that mw.srvGrp.TLS is non-nil if p.IsStdEncrypted() is true. + wildcards := mw.srvGrp.TLS.DeviceIDWildcards + id, err = deviceIDFromContext(ctx, mw.srv.Protocol, wildcards) + } else if p == agd.ProtoDNS { + id, err = deviceIDFromEDNS(req) + } else { + // No DeviceID for DNSCrypt yet. + return nil + } + + if err != nil { + return err + } + + 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 { + if !errors.Is(err, profiledb.ErrDeviceNotFound) { + // Very unlikely, since there is only one error type currently + // returned from the default profile DB. + return fmt.Errorf("unexpected profiledb error: %w", err) + } + + optlog.Debug1("init mw: profile or device not found: %s", err) + } else if prof.Deleted { + optlog.Debug1("init mw: profile %s is deleted", prof.ID) + } else { + optlog.Debug3("init mw: found profile %s and device %s by %s", prof.ID, dev.ID, byWhat) + + ri.Device, ri.Profile = dev, prof + ri.Messages = dnsmsg.NewConstructor(prof.BlockingMode.Mode, prof.FilteredResponseTTL) + } + + return nil +} + +// Constants for the parameter by which a device has been found. +const ( + byDeviceID = "device id" + byDedicatedIP = "dedicated ip" + byLinkedIP = "linked ip" +) + +// profile finds the profile by the client data. +func (mw *Middleware) profile( + ctx context.Context, + localIP netip.Addr, + remoteIP netip.Addr, + id agd.DeviceID, +) (prof *agd.Profile, dev *agd.Device, byWhat string, err error) { + if id != "" { + prof, dev, err = mw.db.ProfileByDeviceID(ctx, id) + if err != nil { + return nil, nil, "", err + } + + return prof, dev, byDeviceID, nil + } + + if !mw.srv.LinkedIPEnabled { + optlog.Debug1("init mw: not matching by linked or dedicated ip for server %s", mw.srv.Name) + + return nil, nil, "", profiledb.ErrDeviceNotFound + } else if p := mw.srv.Protocol; p != agd.ProtoDNS { + optlog.Debug1("init mw: not matching by linked or dedicated ip for proto %v", p) + + return nil, nil, "", profiledb.ErrDeviceNotFound + } + + byWhat = byDedicatedIP + prof, dev, err = mw.db.ProfileByDedicatedIP(ctx, localIP) + if errors.Is(err, profiledb.ErrDeviceNotFound) { + byWhat = byLinkedIP + prof, dev, err = mw.db.ProfileByLinkedIP(ctx, remoteIP) + } + + if err != nil { + return nil, nil, "", err + } + + return prof, dev, byWhat, nil +} diff --git a/internal/dnssvc/internal/initial/profile_internal_test.go b/internal/dnssvc/internal/initial/profile_internal_test.go new file mode 100644 index 0000000..3bd5644 --- /dev/null +++ b/internal/dnssvc/internal/initial/profile_internal_test.go @@ -0,0 +1,167 @@ +package initial + +import ( + "context" + "net/netip" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/testutil" + "github.com/stretchr/testify/assert" +) + +func TestMiddleware_profile(t *testing.T) { + prof := &agd.Profile{ + ID: dnssvctest.ProfileID, + DeviceIDs: []agd.DeviceID{ + dnssvctest.DeviceID, + }, + } + dev := &agd.Device{ + ID: dnssvctest.DeviceID, + LinkedIP: dnssvctest.ClientAddr, + DedicatedIPs: []netip.Addr{ + dnssvctest.ServerAddr, + }, + } + + testCases := []struct { + wantDev *agd.Device + wantProf *agd.Profile + wantByWhat string + wantErrMsg string + name string + id agd.DeviceID + proto agd.Protocol + linkedIPEnabled bool + }{{ + wantDev: nil, + wantProf: nil, + wantByWhat: "", + wantErrMsg: "device not found", + name: "no_device_id", + id: "", + proto: agd.ProtoDNS, + linkedIPEnabled: true, + }, { + wantDev: dev, + wantProf: prof, + wantByWhat: byDeviceID, + wantErrMsg: "", + name: "device_id", + id: dnssvctest.DeviceID, + proto: agd.ProtoDNS, + linkedIPEnabled: true, + }, { + wantDev: dev, + wantProf: prof, + wantByWhat: byLinkedIP, + wantErrMsg: "", + name: "linked_ip", + id: "", + proto: agd.ProtoDNS, + linkedIPEnabled: true, + }, { + wantDev: nil, + wantProf: nil, + wantByWhat: "", + wantErrMsg: "device not found", + name: "linked_ip_dot", + id: "", + proto: agd.ProtoDoT, + linkedIPEnabled: true, + }, { + wantDev: nil, + wantProf: nil, + wantByWhat: "", + wantErrMsg: "device not found", + name: "linked_ip_disabled", + id: "", + proto: agd.ProtoDoT, + linkedIPEnabled: false, + }, { + wantDev: dev, + wantProf: prof, + wantByWhat: byDedicatedIP, + wantErrMsg: "", + name: "dedicated_ip", + id: "", + proto: agd.ProtoDNS, + linkedIPEnabled: true, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mw := New(&Config{ + Server: &agd.Server{ + Protocol: tc.proto, + LinkedIPEnabled: tc.linkedIPEnabled, + }, + ProfileDB: newProfileDB(t, prof, dev, tc.wantByWhat), + }) + + ctx := context.Background() + gotProf, gotDev, gotByWhat, err := mw.profile( + ctx, + dnssvctest.ServerAddr, + dnssvctest.ClientAddr, + tc.id, + ) + testutil.AssertErrorMsg(t, tc.wantErrMsg, err) + assert.Equal(t, tc.wantProf, gotProf) + assert.Equal(t, tc.wantDev, gotDev) + assert.Equal(t, tc.wantByWhat, gotByWhat) + }) + } +} + +// newProfileDB is a helper that creates a database returning prof and dev +// depending on which parameter should be used to find them. +func newProfileDB( + t *testing.T, + prof *agd.Profile, + dev *agd.Device, + byWhat string, +) (db profiledb.Interface) { + return &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + gotID agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + assert.Equal(t, dnssvctest.DeviceID, gotID) + + if byWhat == byDeviceID { + return prof, dev, nil + } + + return nil, nil, profiledb.ErrDeviceNotFound + }, + OnProfileByDedicatedIP: func( + _ context.Context, + gotLocalIP netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + assert.Equal(t, dnssvctest.ServerAddr, gotLocalIP) + + if byWhat == byDedicatedIP { + return prof, dev, nil + } + + return nil, nil, profiledb.ErrDeviceNotFound + }, + OnProfileByLinkedIP: func( + _ context.Context, + gotRemoteIP netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + assert.Equal(t, dnssvctest.ClientAddr, gotRemoteIP) + + if byWhat == byLinkedIP { + return prof, dev, nil + } + + return nil, nil, profiledb.ErrDeviceNotFound + }, + } +} diff --git a/internal/dnssvc/specialdomain.go b/internal/dnssvc/internal/initial/specialdomain.go similarity index 75% rename from internal/dnssvc/specialdomain.go rename to internal/dnssvc/internal/initial/specialdomain.go index 2803626..1269c5a 100644 --- a/internal/dnssvc/specialdomain.go +++ b/internal/dnssvc/internal/initial/specialdomain.go @@ -1,4 +1,4 @@ -package dnssvc +package initial import ( "context" @@ -13,30 +13,28 @@ import ( "github.com/miekg/dns" ) -// Handling For Special Purpose Requests -// // TODO(a.garipov): Consider creating a new prefiltering package for this kind // of filtering-before-filtering. const ( - // resolverArpaDomain is the non-FQDN version of the DNS Resolver + // ResolverARPADomain is the non-FQDN version of the DNS Resolver // Special-Use domain pointing to itself. // // See https://www.ietf.org/archive/id/draft-ietf-add-ddr-07.html#section-8. - resolverArpaDomain = "resolver.arpa" + ResolverARPADomain = "resolver.arpa" - // ddrLabel is the leading label of the special domain name for DDR. - ddrLabel = "_dns" + // DDRLabel is the leading label of the special domain name for DDR. + DDRLabel = "_dns" - // ddrDomain is the non-FQDN version of the Discovery of Designated + // DDRDomain is the non-FQDN version of the Discovery of Designated // Resolvers for querying the resolver with unknown or absent name. - ddrDomain = ddrLabel + "." + resolverArpaDomain + DDRDomain = DDRLabel + "." + ResolverARPADomain - // firefoxCanaryHost is the hostname that Firefox uses to check if it + // FirefoxCanaryHost is the hostname that Firefox uses to check if it // should use its own DNS-over-HTTPS settings. // // See https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https. - firefoxCanaryHost = "use-application-dns.net" + FirefoxCanaryHost = "use-application-dns.net" ) // Hostnames that Apple devices use to check if Apple Private Relay can be @@ -45,14 +43,14 @@ const ( // // See https://developer.apple.com/support/prepare-your-network-for-icloud-private-relay. const ( - applePrivateRelayMaskHost = "mask.icloud.com" - applePrivateRelayMaskH2Host = "mask-h2.icloud.com" - applePrivateRelayMaskCanaryHost = "mask-canary.icloud.com" + ApplePrivateRelayMaskHost = "mask.icloud.com" + ApplePrivateRelayMaskH2Host = "mask-h2.icloud.com" + ApplePrivateRelayMaskCanaryHost = "mask-canary.icloud.com" ) // reqInfoSpecialHandler returns a handler that can handle a special-domain // query based on the request info, as well as the handler's name for debugging. -func (mw *initMw) reqInfoSpecialHandler( +func (mw *Middleware) reqInfoSpecialHandler( ri *agd.RequestInfo, cl dnsmsg.Class, ) (f reqInfoHandlerFunc, name string) { @@ -62,7 +60,7 @@ func (mw *initMw) reqInfoSpecialHandler( if mw.isDDRRequest(ri) { return mw.handleDDR, "ddr" - } else if netutil.IsSubdomain(ri.Host, resolverArpaDomain) { + } else if netutil.IsSubdomain(ri.Host, ResolverARPADomain) { // A badly formed resolver.arpa subdomain query. return mw.handleBadResolverARPA, "bad_resolver_arpa" } @@ -86,7 +84,7 @@ type reqInfoHandlerFunc func( // ARPA if the requested host is a subdomain of resolver.arpa SUDN. // // See https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-07. -func (mw *initMw) isDDRRequest(ri *agd.RequestInfo) (ok bool) { +func (mw *Middleware) isDDRRequest(ri *agd.RequestInfo) (ok bool) { if ri.QType != dns.TypeSVCB { // Resolvers should respond to queries of any type other than SVCB for // _dns.resolver.arpa with NODATA and queries of any type for any domain @@ -97,27 +95,34 @@ func (mw *initMw) isDDRRequest(ri *agd.RequestInfo) (ok bool) { } host := ri.Host - if host == ddrDomain { + if host == DDRDomain { // A simple resolver.arpa request. return true } - if firstLabel, resolverDomain, cut := strings.Cut(host, "."); cut && firstLabel == ddrLabel { - ddr := mw.srvGrp.DDR - if ddr.PublicTargets.Has(resolverDomain) { - // The client may simply send a DNS SVCB query using the known name - // of the resolver. This query can be issued to the named Encrypted - // Resolver itself or to any other resolver. Unlike the case of - // bootstrapping from an Unencrypted Resolver, these records should - // be available in the public DNS. - return true - } + return isDDRDomain(mw.srvGrp.DDR, ri.Device, host) +} - firstLabel, resolverDomain, cut = strings.Cut(resolverDomain, ".") - if cut && ri.Device != nil && firstLabel == string(ri.Device.ID) { - // A request for the device ID resolver domain. - return ddr.DeviceTargets.Has(resolverDomain) - } +// isDDRDomain returns true if host is a DDR domain. +func isDDRDomain(ddr *agd.DDR, dev *agd.Device, host string) (ok bool) { + firstLabel, resolverDomain, cut := strings.Cut(host, ".") + if !cut || firstLabel != DDRLabel { + return false + } + + if ddr.PublicTargets.Has(resolverDomain) { + // The client may simply send a DNS SVCB query using the known name of + // the resolver. This query can be issued to the named Encrypted + // Resolver itself or to any other resolver. Unlike the case of + // bootstrapping from an Unencrypted Resolver, these records should be + // available in the public DNS. + return true + } + + firstLabel, resolverDomain, cut = strings.Cut(resolverDomain, ".") + if cut && dev != nil && firstLabel == string(dev.ID) { + // A request for the device ID resolver domain. + return ddr.DeviceTargets.Has(resolverDomain) } return false @@ -125,7 +130,7 @@ func (mw *initMw) isDDRRequest(ri *agd.RequestInfo) (ok bool) { // handleDDR checks if the request is for the Discovery of Designated Resolvers // and writes a response if needed. -func (mw *initMw) handleDDR( +func (mw *Middleware) handleDDR( ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg, @@ -145,7 +150,7 @@ func (mw *initMw) handleDDR( // newRespDDR returns a new Discovery of Designated Resolvers response copying // it from the prebuilt templates in srvGrp and modifying it in accordance with // the request data. req must not be nil. -func (mw *initMw) newRespDDR(req *dns.Msg, dev *agd.Device) (resp *dns.Msg) { +func (mw *Middleware) newRespDDR(req *dns.Msg, dev *agd.Device) (resp *dns.Msg) { resp = mw.messages.NewRespMsg(req) name := req.Question[0].Name ddr := mw.srvGrp.DDR @@ -173,7 +178,7 @@ func (mw *initMw) newRespDDR(req *dns.Msg, dev *agd.Device) (resp *dns.Msg) { } // handleBadResolverARPA writes a NODATA response. -func (mw *initMw) handleBadResolverARPA( +func (mw *Middleware) handleBadResolverARPA( ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg, @@ -189,7 +194,7 @@ func (mw *initMw) handleBadResolverARPA( // specialDomainHandler returns a handler that can handle a special-domain // query for Apple Private Relay or Firefox canary domain based on the request // or profile information, as well as the handler's name for debugging. -func (mw *initMw) specialDomainHandler( +func (mw *Middleware) specialDomainHandler( ri *agd.RequestInfo, ) (f reqInfoHandlerFunc, name string) { qt := ri.QType @@ -202,13 +207,13 @@ func (mw *initMw) specialDomainHandler( switch host { case - applePrivateRelayMaskHost, - applePrivateRelayMaskH2Host, - applePrivateRelayMaskCanaryHost: + ApplePrivateRelayMaskHost, + ApplePrivateRelayMaskH2Host, + ApplePrivateRelayMaskCanaryHost: if shouldBlockPrivateRelay(ri, prof) { return mw.handlePrivateRelay, "apple_private_relay" } - case firefoxCanaryHost: + case FirefoxCanaryHost: if shouldBlockFirefoxCanary(ri, prof) { return mw.handleFirefoxCanary, "firefox" } @@ -234,7 +239,7 @@ func shouldBlockPrivateRelay(ri *agd.RequestInfo, prof *agd.Profile) (ok bool) { // handlePrivateRelay responds to Apple Private Relay queries with an NXDOMAIN // response. -func (mw *initMw) handlePrivateRelay( +func (mw *Middleware) handlePrivateRelay( ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg, @@ -262,7 +267,7 @@ func shouldBlockFirefoxCanary(ri *agd.RequestInfo, prof *agd.Profile) (ok bool) // handleFirefoxCanary checks if the request is for the fully-qualified domain // name that Firefox uses to check DoH settings and writes a response if needed. -func (mw *initMw) handleFirefoxCanary( +func (mw *Middleware) handleFirefoxCanary( ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg, diff --git a/internal/dnssvc/internal/initial/specialdomain_test.go b/internal/dnssvc/internal/initial/specialdomain_test.go new file mode 100644 index 0000000..2ca8cad --- /dev/null +++ b/internal/dnssvc/internal/initial/specialdomain_test.go @@ -0,0 +1,224 @@ +package initial_test + +import ( + "context" + "net/netip" + "testing" + + "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" + "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/netutil" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMiddleware_ServeDNS_specialDomain(t *testing.T) { + testCases := []struct { + name string + host string + qtype dnsmsg.RRType + fltGrpBlocked bool + hasProf bool + profBlocked bool + wantRCode dnsmsg.RCode + }{{ + name: "private_relay_blocked_by_fltgrp", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeA, + fltGrpBlocked: true, + hasProf: false, + profBlocked: false, + wantRCode: dns.RcodeNameError, + }, { + name: "no_special_domain", + host: "www.example.com", + qtype: dns.TypeA, + fltGrpBlocked: true, + hasProf: false, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "no_private_relay_qtype", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeTXT, + fltGrpBlocked: true, + hasProf: false, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "private_relay_blocked_by_prof", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeA, + fltGrpBlocked: false, + hasProf: true, + profBlocked: true, + wantRCode: dns.RcodeNameError, + }, { + name: "private_relay_allowed_by_prof", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeA, + fltGrpBlocked: true, + hasProf: true, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "private_relay_allowed_by_both", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeA, + fltGrpBlocked: false, + hasProf: true, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "private_relay_blocked_by_both", + host: initial.ApplePrivateRelayMaskHost, + qtype: dns.TypeA, + fltGrpBlocked: true, + hasProf: true, + profBlocked: true, + wantRCode: dns.RcodeNameError, + }, { + name: "firefox_canary_allowed_by_prof", + host: initial.FirefoxCanaryHost, + qtype: dns.TypeA, + fltGrpBlocked: false, + hasProf: true, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "firefox_canary_allowed_by_fltgrp", + host: initial.FirefoxCanaryHost, + qtype: dns.TypeA, + fltGrpBlocked: false, + hasProf: false, + profBlocked: false, + wantRCode: dns.RcodeSuccess, + }, { + name: "firefox_canary_blocked_by_prof", + host: initial.FirefoxCanaryHost, + qtype: dns.TypeA, + fltGrpBlocked: false, + hasProf: true, + profBlocked: true, + wantRCode: dns.RcodeRefused, + }, { + name: "firefox_canary_blocked_by_fltgrp", + host: initial.FirefoxCanaryHost, + qtype: dns.TypeA, + fltGrpBlocked: true, + hasProf: false, + profBlocked: false, + wantRCode: dns.RcodeRefused, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var handler dnsserver.Handler = dnsserver.HandlerFunc(func( + ctx context.Context, + rw dnsserver.ResponseWriter, + req *dns.Msg, + ) (err error) { + if tc.wantRCode != dns.RcodeSuccess { + return errors.Error("unexpectedly reached handler") + } + + resp := (&dns.Msg{}).SetReply(req) + + return rw.WriteMsg(ctx, req, resp) + }) + + onProfileByLinkedIP := func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + if !tc.hasProf { + return nil, nil, profiledb.ErrDeviceNotFound + } + + prof := &agd.Profile{ + BlockPrivateRelay: tc.profBlocked, + BlockFirefoxCanary: tc.profBlocked, + } + + return prof, &agd.Device{}, nil + } + db := &agdtest.ProfileDB{ + OnProfileByDeviceID: func( + _ context.Context, + _ agd.DeviceID, + ) (p *agd.Profile, d *agd.Device, err error) { + panic("not implemented") + }, + OnProfileByDedicatedIP: func( + _ context.Context, + _ netip.Addr, + ) (p *agd.Profile, d *agd.Device, err error) { + return nil, nil, profiledb.ErrDeviceNotFound + }, + OnProfileByLinkedIP: onProfileByLinkedIP, + } + + geoIP := &agdtest.GeoIP{ + OnSubnetByLocation: func( + _ agd.Country, + _ agd.ASN, + _ netutil.AddrFamily, + ) (n netip.Prefix, err error) { + panic("not implemented") + }, + OnData: func(_ string, _ netip.Addr) (l *agd.Location, err error) { + return nil, nil + }, + } + + errColl := &agdtest.ErrorCollector{ + OnCollect: func(_ context.Context, _ error) { + panic("not implemented") + }, + } + + mw := initial.New(&initial.Config{ + Messages: agdtest.NewConstructor(), + FilteringGroup: &agd.FilteringGroup{ + BlockPrivateRelay: tc.fltGrpBlocked, + BlockFirefoxCanary: tc.fltGrpBlocked, + }, + ServerGroup: &agd.ServerGroup{}, + Server: &agd.Server{ + Protocol: agd.ProtoDNS, + LinkedIPEnabled: true, + }, + ProfileDB: db, + GeoIP: geoIP, + ErrColl: errColl, + }) + + h := mw.Wrap(handler) + + ctx := context.Background() + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) + req := &dns.Msg{ + Question: []dns.Question{{ + Name: dns.Fqdn(tc.host), + Qtype: tc.qtype, + Qclass: dns.ClassINET, + }}, + } + + err := h.ServeDNS(ctx, rw, req) + require.NoError(t, err) + + resp := rw.Msg() + require.NotNil(t, resp) + + assert.Equal(t, tc.wantRCode, dnsmsg.RCode(resp.Rcode)) + }) + } +} diff --git a/internal/dnssvc/internal/internal.go b/internal/dnssvc/internal/internal.go new file mode 100644 index 0000000..353adae --- /dev/null +++ b/internal/dnssvc/internal/internal.go @@ -0,0 +1,15 @@ +// Package internal contains common utilities for DNS middlewares. +package internal + +import "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + +// MakeNonWriter makes rw a *dnsserver.NonWriterResponseWriter unless it already +// is one, in which case it just returns it. +func MakeNonWriter(rw dnsserver.ResponseWriter) (nwrw *dnsserver.NonWriterResponseWriter) { + nwrw, ok := rw.(*dnsserver.NonWriterResponseWriter) + if ok { + return nwrw + } + + return dnsserver.NewNonWriterResponseWriter(rw.LocalAddr(), rw.RemoteAddr()) +} diff --git a/internal/dnssvc/middleware.go b/internal/dnssvc/middleware.go index b32805e..09fbfaa 100644 --- a/internal/dnssvc/middleware.go +++ b/internal/dnssvc/middleware.go @@ -3,11 +3,12 @@ package dnssvc import ( "context" "strconv" - "strings" "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/optlog" @@ -59,20 +60,20 @@ func (mh *svcHandler) ServeDNS( modReq, reqRes, elapsedReq := mh.svc.filterRequest(ctx, req, flt, reqInfo) - nwrw := makeNonWriter(rw) + nwrw := internal.MakeNonWriter(rw) if modReq != nil { // Modified request is set only if the request was modified by a CNAME // rewrite rule, so resolve the request as if it was for the rewritten // name. - // Clone the request informaton and replace the host name with the + // Clone the request information and replace the host name with the // rewritten one, since the request information from current context // must only be accessed for reading, see [agd.RequestInfo]. Shallow // copy is enough, because we only change the [agd.RequestInfo.Host] // field, which is a string. modReqInfo := &agd.RequestInfo{} *modReqInfo = *reqInfo - modReqInfo.Host = strings.ToLower(strings.TrimSuffix(modReq.Question[0].Name, ".")) + modReqInfo.Host = agdnet.NormalizeDomain(modReq.Question[0].Name) modReqCtx := agd.ContextWithRequestInfo(ctx, modReqInfo) @@ -110,17 +111,6 @@ func (mh *svcHandler) ServeDNS( return nil } -// makeNonWriter makes rw a *dnsserver.NonWriterResponseWriter unless it already -// is one, in which case it just returns it. -func makeNonWriter(rw dnsserver.ResponseWriter) (nwrw *dnsserver.NonWriterResponseWriter) { - nwrw, ok := rw.(*dnsserver.NonWriterResponseWriter) - if ok { - return nwrw - } - - return dnsserver.NewNonWriterResponseWriter(rw.LocalAddr(), rw.RemoteAddr()) -} - // rewrittenRequest returns a request from res in case it's a CNAME rewrite, and // returns nil otherwise. Note that the returned message is always a request // since any other rewrite rule type turns into response. diff --git a/internal/dnssvc/middleware_test.go b/internal/dnssvc/middleware_test.go index b7ea00e..ca51b57 100644 --- a/internal/dnssvc/middleware_test.go +++ b/internal/dnssvc/middleware_test.go @@ -15,6 +15,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/querylog" "github.com/AdguardTeam/golibs/netutil" @@ -25,12 +26,6 @@ import ( ) const ( - // testProfID is the [agd.ProfileID] for tests. - testProfID agd.ProfileID = "prof1234" - - // testDevID is the [agd.DeviceID] for tests. - testDevID agd.DeviceID = "dev1234" - // testFltListID is the [agd.FilterListID] for tests. testFltListID agd.FilterListID = "flt1234" @@ -46,16 +41,13 @@ const ( testDevIDWildcard string = "*.dns.example.com" ) -// testTimeout is the common timeout for tests. -const testTimeout time.Duration = 1 * time.Second - // newTestService creates a new [dnssvc.Service] for tests. The service built // of stubs, that use the following data: // // - A filtering group containing a filter with [testFltListID] and enabled // rule lists. -// - A device with [testDevID] and enabled filtering. -// - A profile with [testProfID] with enabled filtering and query +// - A device with [dnssvctest.DeviceID] and enabled filtering. +// - A profile with [dnssvctest.ProfileID] with enabled filtering and query // logging, containing the device. // - GeoIP database always returning [agd.CountryAD], [agd.ContinentEU], and // ASN of 42. @@ -63,8 +55,8 @@ const testTimeout time.Duration = 1 * time.Second // the DeviceID with [testDevIDWildcard]. // // Each stub also uses the corresponding channels to send the data it receives -// from the service. If the channel is [nil], the stub ignores it. Each -// sending to a channel wrapped with [testutil.RequireSend] using [testTimeout]. +// from the service. The channels must not be nil. Each sending to a channel +// wrapped with [testutil.RequireSend] using [dnssvctest.Timeout]. // // It also uses the [dnsservertest.DefaultHandler] to create the DNS handler. func newTestService( @@ -82,13 +74,13 @@ func newTestService( pt := testutil.PanicT{} dev := &agd.Device{ - ID: testDevID, + ID: dnssvctest.DeviceID, FilteringEnabled: true, } prof := &agd.Profile{ - ID: testProfID, - DeviceIDs: []agd.DeviceID{testDevID}, + ID: dnssvctest.ProfileID, + DeviceIDs: []agd.DeviceID{dnssvctest.DeviceID}, RuleListIDs: []agd.FilterListID{testFltListID}, FilteredResponseTTL: agdtest.FilteredResponseTTL, FilteringEnabled: true, @@ -100,9 +92,7 @@ func newTestService( _ context.Context, id agd.DeviceID, ) (p *agd.Profile, d *agd.Device, err error) { - if profileDBCh != nil { - testutil.RequireSend(pt, profileDBCh, id, testTimeout) - } + testutil.RequireSend(pt, profileDBCh, id, dnssvctest.Timeout) return prof, dev, nil }, @@ -120,13 +110,20 @@ func newTestService( }, } + accessManager := &agdtest.AccessManager{ + OnIsBlockedHost: func(host string, qt uint16) (blocked bool) { + return false + }, + OnIsBlockedIP: func(ip netip.Addr) (blocked bool, rule string) { + return false, "" + }, + } + // Make sure that any panics and errors within handlers are caught and // that they fail the test by panicking. errColl := &agdtest.ErrorCollector{ OnCollect: func(_ context.Context, err error) { - if errCollCh != nil { - testutil.RequireSend(pt, errCollCh, err, testTimeout) - } + testutil.RequireSend(pt, errCollCh, err, dnssvctest.Timeout) }, } @@ -144,9 +141,7 @@ func newTestService( panic("not implemented") }, OnData: func(host string, _ netip.Addr) (l *agd.Location, err error) { - if geoIPCh != nil { - testutil.RequireSend(pt, geoIPCh, host, testTimeout) - } + testutil.RequireSend(pt, geoIPCh, host, dnssvctest.Timeout) return loc, nil }, @@ -161,9 +156,7 @@ func newTestService( var ql querylog.Interface = &agdtest.QueryLog{ OnWrite: func(_ context.Context, e *querylog.Entry) (err error) { - if querylogCh != nil { - testutil.RequireSend(pt, querylogCh, e, testTimeout) - } + testutil.RequireSend(pt, querylogCh, e, dnssvctest.Timeout) return nil }, @@ -196,17 +189,13 @@ func newTestService( dnsDB := &agdtest.DNSDB{ OnRecord: func(_ context.Context, _ *dns.Msg, ri *agd.RequestInfo) { - if dnsDBCh != nil { - testutil.RequireSend(pt, dnsDBCh, ri, testTimeout) - } + testutil.RequireSend(pt, dnsDBCh, ri, dnssvctest.Timeout) }, } ruleStat := &agdtest.RuleStat{ OnCollect: func(_ context.Context, _ agd.FilterListID, text agd.FilterRuleText) { - if ruleStatCh != nil { - testutil.RequireSend(pt, ruleStatCh, text, testTimeout) - } + testutil.RequireSend(pt, ruleStatCh, text, dnssvctest.Timeout) }, } @@ -226,7 +215,8 @@ func newTestService( testFltGrpID := agd.FilteringGroupID("1234") c := &dnssvc.Config{ - Messages: agdtest.NewConstructor(), + AccessManager: accessManager, + Messages: agdtest.NewConstructor(), BillStat: &agdtest.BillStatRecorder{ OnRecord: func( _ context.Context, @@ -308,7 +298,7 @@ func TestService_Wrap(t *testing.T) { ctx := context.Background() ctx = dnsserver.ContextWithClientInfo(ctx, dnsserver.ClientInfo{ - TLSServerName: strings.ReplaceAll(testDevIDWildcard, "*", string(testDevID)), + TLSServerName: strings.ReplaceAll(testDevIDWildcard, "*", string(dnssvctest.DeviceID)), }) ctx = dnsserver.ContextWithServerInfo(ctx, dnsserver.ServerInfo{ Proto: agd.ProtoDoT, @@ -356,7 +346,7 @@ func TestService_Wrap(t *testing.T) { resp := rw.Msg() dnsservertest.RequireResponse(t, req, resp, 1, dns.RcodeSuccess, false) - assert.Equal(t, testDevID, <-profileDBCh) + assert.Equal(t, dnssvctest.DeviceID, <-profileDBCh) logEntry := <-querylogCh assert.Equal(t, domainFQDN, logEntry.DomainFQDN) @@ -446,7 +436,7 @@ func TestService_Wrap(t *testing.T) { A: netutil.IPv4Localhost().AsSlice(), }}, resp.Answer) - assert.Equal(t, testDevID, <-profileDBCh) + assert.Equal(t, dnssvctest.DeviceID, <-profileDBCh) logEntry := <-querylogCh assert.Equal(t, domainFQDN, logEntry.DomainFQDN) diff --git a/internal/dnssvc/presvcmw_internal_test.go b/internal/dnssvc/presvcmw_internal_test.go index f6db9da..7b59e57 100644 --- a/internal/dnssvc/presvcmw_internal_test.go +++ b/internal/dnssvc/presvcmw_internal_test.go @@ -4,7 +4,6 @@ import ( "context" "crypto/sha256" "encoding/hex" - "net" "testing" "time" @@ -13,8 +12,10 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix" + "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,7 +25,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) { const safeBrowsingHost = "scam.example.net." var ( - ip = net.IP{127, 0, 0, 1} + ip = netutil.IPv4Localhost() name = "example.com" ) @@ -98,7 +99,7 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) tctx := agd.ContextWithRequestInfo(ctx, tc.ri) dnsCk := &agdtest.DNSCheck{ diff --git a/internal/dnssvc/preupstreammw.go b/internal/dnssvc/preupstreammw.go index c21deac..a31814b 100644 --- a/internal/dnssvc/preupstreammw.go +++ b/internal/dnssvc/preupstreammw.go @@ -12,6 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal" "github.com/AdguardTeam/AdGuardDNS/internal/ecscache" "github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/golibs/errors" @@ -95,7 +96,7 @@ func (mh *preUpstreamMwHandler) ServeDNS( return mh.serveAndroidMetric(ctx, mh.next, rw, req, rn) } - nwrw := makeNonWriter(rw) + nwrw := internal.MakeNonWriter(rw) err = mh.next.ServeDNS(ctx, nwrw, req) if err != nil { // Don't wrap the error, because this is the main flow, and there is @@ -130,7 +131,7 @@ func (mh *preUpstreamMwHandler) serveAndroidMetric( req := dnsmsg.Clone(origReq) req.Question[0].Name = replName - nwrw := makeNonWriter(rw) + nwrw := internal.MakeNonWriter(rw) err = h.ServeDNS(ctx, nwrw, req) if err != nil { // Don't wrap the error, because this is the main flow, and there is diff --git a/internal/dnssvc/preupstreammw_internal_test.go b/internal/dnssvc/preupstreammw_internal_test.go index 09a5d6a..26fc3c3 100644 --- a/internal/dnssvc/preupstreammw_internal_test.go +++ b/internal/dnssvc/preupstreammw_internal_test.go @@ -12,6 +12,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" + "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest" "github.com/AdguardTeam/golibs/netutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -26,10 +27,9 @@ const ( func TestPreUpstreamMwHandler_ServeDNS_withCache(t *testing.T) { remoteIP := netip.MustParseAddr("1.2.3.4") aReq := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET) - respIP := remoteIP.AsSlice() resp := dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, defaultTTL, respIP), + dnsservertest.NewA(reqHostname, defaultTTL, remoteIP), }) ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{ Host: aReq.Question[0].Name, @@ -91,7 +91,7 @@ func TestPreUpstreamMwHandler_ServeDNS_withECSCache(t *testing.T) { const ctry = agd.CountryAD resp := dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, defaultTTL, remoteIP), }) numReq := 0 @@ -166,8 +166,8 @@ func TestPreUpstreamMwHandler_ServeDNS_androidMetric(t *testing.T) { ctx = dnsserver.ContextWithStartTime(ctx, time.Now()) ctx = agd.ContextWithRequestInfo(ctx, &agd.RequestInfo{}) - ipA := net.IP{1, 2, 3, 4} - ipB := net.IP{1, 2, 3, 5} + ipA := netip.MustParseAddr("1.2.3.4") + ipB := netip.MustParseAddr("1.2.3.5") const ttl = 100 @@ -228,7 +228,7 @@ func TestPreUpstreamMwHandler_ServeDNS_androidMetric(t *testing.T) { h := mw.Wrap(handler) - rw := dnsserver.NewNonWriterResponseWriter(nil, testRAddr) + rw := dnsserver.NewNonWriterResponseWriter(nil, dnssvctest.RemoteAddr) err := h.ServeDNS(ctx, rw, tc.req) require.NoError(t, err) diff --git a/internal/dnssvc/resp_internal_test.go b/internal/dnssvc/resp_internal_test.go index a1bfe7a..f987be7 100644 --- a/internal/dnssvc/resp_internal_test.go +++ b/internal/dnssvc/resp_internal_test.go @@ -3,6 +3,7 @@ package dnssvc import ( "context" "net" + "net/netip" "testing" "github.com/AdguardTeam/AdGuardDNS/internal/agd" @@ -10,7 +11,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/filter" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/testutil" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -23,9 +23,9 @@ func TestWriteFilteredResp(t *testing.T) { ) const fltRespTTL = agdtest.FilteredResponseTTLSec - respIP := net.IP{1, 2, 3, 4} - rewrIP := net.IP{5, 6, 7, 8} - blockIP := netutil.IPv4Zero() + respIP := netip.MustParseAddr("1.2.3.4") + rewrIP := netip.MustParseAddr("5.6.7.8") + blockIP := netip.IPv4Unspecified() const domain = "example.com" req := dnsservertest.NewReq(domain, dns.TypeA, dns.ClassINET) @@ -35,50 +35,50 @@ func TestWriteFilteredResp(t *testing.T) { testCases := []struct { reqRes filter.Result respRes filter.Result + wantIP netip.Addr name string - wantIP net.IP wantTTL uint32 }{{ reqRes: nil, respRes: nil, - name: "not_filtered", wantIP: respIP, + name: "not_filtered", wantTTL: respTTL, }, { reqRes: &filter.ResultAllowed{}, respRes: nil, - name: "allowed_req", wantIP: respIP, + name: "allowed_req", wantTTL: respTTL, }, { reqRes: &filter.ResultBlocked{}, respRes: nil, - name: "blocked_req", wantIP: blockIP, + name: "blocked_req", wantTTL: fltRespTTL, }, { reqRes: &filter.ResultModified{Msg: rewrResp}, respRes: nil, - name: "modified_req", wantIP: rewrIP, + name: "modified_req", wantTTL: fltRespTTL, }, { reqRes: nil, respRes: &filter.ResultAllowed{}, - name: "allowed_resp", wantIP: respIP, + name: "allowed_resp", wantTTL: respTTL, }, { reqRes: nil, respRes: &filter.ResultBlocked{}, - name: "blocked_resp", wantIP: blockIP, + name: "blocked_resp", wantTTL: fltRespTTL, }, { reqRes: nil, respRes: &filter.ResultModified{Msg: rewrResp}, - name: "modified_resp", wantIP: rewrIP, + name: "modified_resp", wantTTL: fltRespTTL, }} @@ -107,7 +107,7 @@ func TestWriteFilteredResp(t *testing.T) { a := testutil.RequireTypeAssert[*dns.A](t, ans) - assert.Equal(t, tc.wantIP, a.A) + assert.Equal(t, net.IP(tc.wantIP.AsSlice()), a.A) }) } } diff --git a/internal/ecscache/ecscache_test.go b/internal/ecscache/ecscache_test.go index 3b8a281..5bae3d4 100644 --- a/internal/ecscache/ecscache_test.go +++ b/internal/ecscache/ecscache_test.go @@ -49,6 +49,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { dnsservertest.NewSOA(reqHostname, defaultTTL, reqNS1, reqNS2), } + knownIP := netip.MustParseAddr("1.2.3.4") testTTL := 60 * time.Second const N = 5 @@ -62,7 +63,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }{{ req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, defaultTTL, knownIP), }), name: "simple_a", wantNumReq: 1, @@ -132,7 +133,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 0, knownIP), }), name: "expired_one", wantNumReq: N, @@ -141,7 +142,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 10, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 10, knownIP), }), name: "override_ttl_ok", wantNumReq: 1, @@ -150,7 +151,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 1000, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 1000, knownIP), }), name: "override_ttl_max", wantNumReq: 1, @@ -159,7 +160,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeSuccess, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, 0, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, 0, knownIP), }), name: "override_ttl_zero", wantNumReq: N, @@ -168,7 +169,7 @@ func TestMiddleware_Wrap_noECS(t *testing.T) { }, { req: aReq, resp: dnsservertest.NewResp(dns.RcodeServerFailure, aReq, dnsservertest.SectionAnswer{ - dnsservertest.NewA(reqHostname, dnsmsg.ServFailMaxCacheTTL, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(reqHostname, dnsmsg.ServFailMaxCacheTTL, knownIP), }), name: "override_ttl_servfail", wantNumReq: 1, @@ -327,7 +328,7 @@ func TestMiddleware_Wrap_ecs(t *testing.T) { dnsservertest.SectionAnswer{dnsservertest.NewA( reqHostname, defaultTTL, - net.IP{1, 2, 3, 4}, + netip.MustParseAddr("1.2.3.4"), )}, dnsservertest.SectionExtra{tc.respECS}, ) @@ -444,8 +445,8 @@ func TestMiddleware_Wrap_ecsOrder(t *testing.T) { }, ) - answerA := dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4}) - answerB := dnsservertest.NewA(reqHostname, defaultTTL, net.IP{5, 6, 7, 8}) + answerA := dnsservertest.NewA(reqHostname, defaultTTL, netip.MustParseAddr("1.2.3.4")) + answerB := dnsservertest.NewA(reqHostname, defaultTTL, netip.MustParseAddr("5.6.7.8")) // Tests diff --git a/internal/errcoll/sentry.go b/internal/errcoll/sentry.go index cc9b603..abf5edb 100644 --- a/internal/errcoll/sentry.go +++ b/internal/errcoll/sentry.go @@ -7,6 +7,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" @@ -54,6 +55,28 @@ func (c *SentryErrorCollector) Collect(ctx context.Context, err error) { }, scope) } +// ErrorFlushCollector collects information about errors, possibly sending them +// to a remote location. The collected errors should be flushed with the Flush. +type ErrorFlushCollector interface { + agd.ErrorCollector + + // Flush waits until the underlying transport sends any buffered events to + // the sentry server, blocking for at most the predefined timeout. + Flush() +} + +// type check +var _ ErrorFlushCollector = (*SentryErrorCollector)(nil) + +// flushTimeout is the timeout for flushing sentry errors. +const flushTimeout = 1 * time.Second + +// Flush implements the [ErrorFlushCollector] interface for +// *SentryErrorCollector. +func (c *SentryErrorCollector) Flush() { + _ = c.sentry.Flush(flushTimeout) +} + // SentryReportableError is the interface for errors and wrapper that can tell // whether they should be reported or not. type SentryReportableError interface { diff --git a/internal/filter/filter_test.go b/internal/filter/filter_test.go index af54a68..e198952 100644 --- a/internal/filter/filter_test.go +++ b/internal/filter/filter_test.go @@ -83,15 +83,10 @@ var ( blockedIP4 = net.IP{6, 6, 6, 13} allowedIP4 = net.IP{7, 7, 7, 42} - safeBrowsingSafeIP4 = net.IP{94, 140, 14, 14} + safeBrowsingSafeIP4 = netip.MustParseAddr("94.140.14.14") - safeSearchIPRespIP4 = net.IP{213, 180, 193, 56} - safeSearchIPRespIP6 = net.IP{ - 0x00, 0x01, 0x02, 0x03, - 0x00, 0x01, 0x02, 0x03, - 0x00, 0x01, 0x02, 0x03, - 0x00, 0x01, 0x02, 0x03, - } + safeSearchIPRespIP4 = netip.MustParseAddr("213.180.193.56") + safeSearchIPRespIP6 = netip.MustParseAddr("1:203:1:203:1:203:1:203") ) // Common clients. Keep in sync with ./testdata/filter. diff --git a/internal/filter/hashprefix/filter.go b/internal/filter/hashprefix/filter.go index b2ce57d..2378fab 100644 --- a/internal/filter/hashprefix/filter.go +++ b/internal/filter/hashprefix/filter.go @@ -175,6 +175,11 @@ func (f *Filter) FilterRequest( return rm, nil } +// ID implements the [internal.RequestFilter] interface for *Filter. +func (f *Filter) ID() (id agd.FilterListID) { + return f.id +} + // 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]. @@ -212,7 +217,7 @@ func (f *Filter) filteredResponse( ctx, cancel := context.WithTimeout(ctx, internal.DefaultResolveTimeout) defer cancel() - ips, err := f.resolver.LookupIP(ctx, fam, f.repHost) + ips, err := f.resolver.LookupNetIP(ctx, fam, f.repHost) if err != nil { agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err) diff --git a/internal/filter/hashprefix/filter_test.go b/internal/filter/hashprefix/filter_test.go index 0111b31..0ecf68a 100644 --- a/internal/filter/hashprefix/filter_test.go +++ b/internal/filter/hashprefix/filter_test.go @@ -2,8 +2,8 @@ package hashprefix_test import ( "context" - "net" "net/http" + "net/netip" "os" "testing" "time" @@ -28,7 +28,7 @@ func TestFilter_FilterRequest(t *testing.T) { strg, err := hashprefix.NewStorage("") require.NoError(t, err) - replIP := net.IP{1, 2, 3, 4} + replIP := netip.MustParseAddr("1.2.3.4") f, err := hashprefix.NewFilter(&hashprefix.FilterConfig{ Hashes: strg, URL: srvURL, @@ -38,12 +38,12 @@ func TestFilter_FilterRequest(t *testing.T) { }, }, Resolver: &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{replIP}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{replIP}, nil }, }, ID: agd.FilterListIDAdultBlocking, @@ -189,7 +189,7 @@ func newModifiedResult( tb testing.TB, req *dns.Msg, messages *dnsmsg.Constructor, - replIP net.IP, + replIP netip.Addr, ) (r *internal.ResultModified) { resp, err := messages.NewIPRespMsg(req, replIP) require.NoError(tb, err) @@ -217,11 +217,11 @@ func TestFilter_Refresh(t *testing.T) { }, }, Resolver: &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { + ) (ips []netip.Addr, err error) { panic("not implemented") }, }, @@ -262,7 +262,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { strg, err := hashprefix.NewStorage("") require.NoError(t, err) - replIP := net.IP{1, 2, 3, 4} + replIP := netip.MustParseAddr("1.2.3.4") fconf := &hashprefix.FilterConfig{ Hashes: strg, URL: srvURL, @@ -272,12 +272,12 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) { }, }, Resolver: &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{replIP}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{replIP}, nil }, }, ID: agd.FilterListIDAdultBlocking, diff --git a/internal/filter/hashprefix/matcher.go b/internal/filter/hashprefix/matcher.go index 5d8f752..7f83826 100644 --- a/internal/filter/hashprefix/matcher.go +++ b/internal/filter/hashprefix/matcher.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/stringutil" ) @@ -54,7 +54,7 @@ func (m *Matcher) MatchByPrefix( return nil, false, nil } - log.Debug("hashprefix matcher: got prefixes string %q", prefixesStr) + optlog.Debug1("hashprefix matcher: got prefixes string %q", prefixesStr) hashPrefixes, err := prefixesFromStr(prefixesStr) if err != nil { diff --git a/internal/filter/internal/composite/composite.go b/internal/filter/internal/composite/composite.go index 6974db8..f9d0bef 100644 --- a/internal/filter/internal/composite/composite.go +++ b/internal/filter/internal/composite/composite.go @@ -13,7 +13,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch" - "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" ) @@ -24,13 +24,6 @@ import ( // An empty composite filter is a filter that always returns a nil filtering // result. type Filter struct { - safeBrowsing *hashprefix.Filter - newRegisteredDomains *hashprefix.Filter - adultBlocking *hashprefix.Filter - - genSafeSearch *safesearch.Filter - ytSafeSearch *safesearch.Filter - // custom is the custom rule-list filter of the profile, if any. custom *rulelist.Immutable @@ -41,6 +34,10 @@ type Filter struct { // svcLists are the rule-list filters of the profile's enabled blocked // services, if any. svcLists []*rulelist.Immutable + + // reqFilters are the safe-browsing and safe-search request filters in the + // composite filter. + reqFilters []internal.RequestFilter } // Config is the configuration structure for the composite filter. @@ -80,16 +77,32 @@ func New(c *Config) (f *Filter) { return &Filter{} } - return &Filter{ - safeBrowsing: c.SafeBrowsing, - adultBlocking: c.AdultBlocking, - genSafeSearch: c.GeneralSafeSearch, - ytSafeSearch: c.YouTubeSafeSearch, - custom: c.Custom, - ruleLists: c.RuleLists, - svcLists: c.ServiceLists, - newRegisteredDomains: c.NewRegisteredDomains, + f = &Filter{ + custom: c.Custom, + ruleLists: c.RuleLists, + svcLists: c.ServiceLists, } + + // DO NOT change the order of request filters without necessity. + f.reqFilters = appendReqFilter(f.reqFilters, c.SafeBrowsing) + f.reqFilters = appendReqFilter(f.reqFilters, c.AdultBlocking) + f.reqFilters = appendReqFilter(f.reqFilters, c.GeneralSafeSearch) + f.reqFilters = appendReqFilter(f.reqFilters, c.YouTubeSafeSearch) + f.reqFilters = appendReqFilter(f.reqFilters, c.NewRegisteredDomains) + + return f +} + +// appendReqFilter appends flt to flts if flt is not nil. +func appendReqFilter[T *hashprefix.Filter | *safesearch.Filter]( + flts []internal.RequestFilter, + flt T, +) (res []internal.RequestFilter) { + if flt != nil { + flts = append(flts, internal.RequestFilter(flt)) + } + + return flts } // type check @@ -110,7 +123,7 @@ func (f *Filter) FilterRequest( // Prepare common data for filters. reqID := ri.ID - log.Debug("filters: filtering req %s: %d rule lists", reqID, len(f.ruleLists)) + optlog.Debug2("filters: filtering req %s: %d rule lists", reqID, len(f.ruleLists)) // Firstly, check the profile's rule-list filtering, the custom rules, and // the rules from blocked services settings. @@ -130,38 +143,11 @@ func (f *Filter) FilterRequest( // Go on. } - // Secondly, apply the safe browsing and safe search request filters in the - // following order. - // - // DO NOT change the order of reqFilters without necessity. - reqFilters := []struct { - filter internal.RequestFilter - id agd.FilterListID - }{{ - filter: nullify(f.safeBrowsing), - id: agd.FilterListIDSafeBrowsing, - }, { - filter: nullify(f.adultBlocking), - id: agd.FilterListIDAdultBlocking, - }, { - filter: nullify(f.genSafeSearch), - id: agd.FilterListIDGeneralSafeSearch, - }, { - filter: nullify(f.ytSafeSearch), - id: agd.FilterListIDYoutubeSafeSearch, - }, { - filter: nullify(f.newRegisteredDomains), - id: agd.FilterListIDNewRegDomains, - }} - - for _, rf := range reqFilters { - if rf.filter == nil { - continue - } - - log.Debug("filter %s: filtering req %s", rf.id, reqID) - r, err = rf.filter.FilterRequest(ctx, req, ri) - log.Debug("filter %s: finished filtering req %s, errors: %v", rf.id, reqID, err) + for _, rf := range f.reqFilters { + id := rf.ID() + optlog.Debug2("filter %s: filtering req %s", id, reqID) + r, err = rf.FilterRequest(ctx, req, ri) + optlog.Debug3("filter %s: finished filtering req %s, errors: %v", id, reqID, err) if err != nil { return nil, err } else if r != nil { @@ -173,17 +159,6 @@ func (f *Filter) FilterRequest( return rlRes, nil } -// nullify returns a nil interface value if flt is a nil pointer. Otherwise, it -// returns flt converted to the interface type. It is used to avoid situations -// where an interface value doesn't have any data but does have a type. -func nullify[T *safesearch.Filter | *hashprefix.Filter](flt T) (fr internal.RequestFilter) { - if flt == nil { - return nil - } - - return internal.RequestFilter(flt) -} - // FilterResponse implements the [internal.Interface] interface for *Filter. It // returns the action created from the filter list network rule with the highest // priority. If f is empty, it returns nil with no error. @@ -276,14 +251,10 @@ func parseRespAnswer(ans dns.RR) (hostname string, rrType dnsmsg.RRType, ok bool // isEmpty returns true if this composite filter is an empty filter. func (f *Filter) isEmpty() (ok bool) { return f == nil || - (f.safeBrowsing == nil && - f.adultBlocking == nil && - f.genSafeSearch == nil && - f.ytSafeSearch == nil && - f.custom == nil && - f.newRegisteredDomains == nil && + (f.custom == nil && len(f.ruleLists) == 0 && - len(f.svcLists) == 0) + len(f.svcLists) == 0 && + len(f.reqFilters) == 0) } // filterWithRuleLists filters one question's or answer's information through @@ -408,7 +379,7 @@ func (f *Filter) ruleDataToResult( } if allowlist { - log.Debug("rule list %s: allowed by rule %s", fltID, rule) + optlog.Debug2("rule list %s: allowed by rule %s", fltID, rule) return &internal.ResultAllowed{ List: fltID, @@ -416,7 +387,7 @@ func (f *Filter) ruleDataToResult( } } - log.Debug("rule list %s: blocked by rule %s", fltID, rule) + optlog.Debug2("rule list %s: blocked by rule %s", fltID, rule) return &internal.ResultBlocked{ List: fltID, diff --git a/internal/filter/internal/composite/composite_test.go b/internal/filter/internal/composite/composite_test.go index 20a751a..73cbcda 100644 --- a/internal/filter/internal/composite/composite_test.go +++ b/internal/filter/internal/composite/composite_test.go @@ -2,7 +2,6 @@ package composite_test import ( "context" - "net" "net/http" "net/netip" "testing" @@ -180,6 +179,9 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { dnsRewrite2Rules = filtertest.BlockRule + "$dnsrewrite=1.2.3.4\n" + filtertest.BlockRule + "$dnsrewrite=1.2.3.5" dnsRewriteRuleTXT = filtertest.BlockRule + "$dnsrewrite=NOERROR;TXT;abcdefg" + dnsRewriteRuleSOA = filtertest.BlockRule + "$dnsrewrite=NOERROR;SOA;ns1." + + filtertest.ReqFQDN + " hostmaster." + filtertest.ReqFQDN + " 1 3600 1800 604800 86400" + dnsRewriteTypedRules = dnsRewriteRuleTXT + "\n" + dnsRewriteRuleSOA ) var ( @@ -188,6 +190,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { rlCustomRefused = newImmutable(t, dnsRewriteRuleRefused, agd.FilterListIDCustom) rlCustomCname = newImmutable(t, dnsRewriteRuleCname, agd.FilterListIDCustom) rlCustom2Rules = newImmutable(t, dnsRewrite2Rules, agd.FilterListIDCustom) + rlCustomTyped = newImmutable(t, dnsRewriteTypedRules, agd.FilterListIDCustom) ) req := dnsservertest.NewReq(filtertest.ReqFQDN, dns.TypeA, dns.ClassINET) @@ -196,23 +199,33 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { modifiedReq := dnsmsg.Clone(req) modifiedReq.Question[0].Name = "new-cname.example." + txtReq := dnsmsg.Clone(req) + txtReq.Question[0].Qtype = dns.TypeTXT + + soaReq := dnsmsg.Clone(req) + soaReq.Question[0].Qtype = dns.TypeSOA + testCases := []struct { custom *rulelist.Immutable + req *dns.Msg wantRes internal.Result name string ruleLists []*rulelist.Refreshable }{{ custom: nil, + req: req, wantRes: &internal.ResultBlocked{List: testFltListID1, Rule: blockRule}, name: "block", ruleLists: []*rulelist.Refreshable{rlNonRewrite}, }, { custom: nil, + req: req, wantRes: &internal.ResultBlocked{List: testFltListID1, Rule: blockRule}, name: "dnsrewrite_no_effect", ruleLists: []*rulelist.Refreshable{rlNonRewrite, rlRewriteIgnored}, }, { custom: rlCustomRefused, + req: req, wantRes: &internal.ResultModified{ Msg: dnsservertest.NewResp(dns.RcodeRefused, req), List: agd.FilterListIDCustom, @@ -222,6 +235,7 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { ruleLists: []*rulelist.Refreshable{rlNonRewrite, rlRewriteIgnored}, }, { custom: rlCustomCname, + req: req, wantRes: &internal.ResultModified{ Msg: modifiedReq, List: agd.FilterListIDCustom, @@ -231,17 +245,18 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { ruleLists: []*rulelist.Refreshable{rlNonRewrite, rlRewriteIgnored}, }, { custom: rlCustom2Rules, + req: req, wantRes: &internal.ResultModified{ Msg: dnsservertest.NewResp(dns.RcodeSuccess, req, dnsservertest.SectionAnswer{ dnsservertest.NewA( filtertest.ReqFQDN, agdtest.FilteredResponseTTLSec, - net.IP{1, 2, 3, 4}, + netip.MustParseAddr("1.2.3.4"), ), dnsservertest.NewA( filtertest.ReqFQDN, agdtest.FilteredResponseTTLSec, - net.IP{1, 2, 3, 5}, + netip.MustParseAddr("1.2.3.5"), ), }), List: agd.FilterListIDCustom, @@ -249,6 +264,31 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { }, name: "dnsrewrite_answers", ruleLists: []*rulelist.Refreshable{rlNonRewrite, rlRewriteIgnored}, + }, { + custom: rlCustomTyped, + req: txtReq, + wantRes: &internal.ResultModified{ + Msg: dnsservertest.NewResp(dns.RcodeSuccess, txtReq, dnsservertest.SectionAnswer{ + dnsservertest.NewTXT( + filtertest.ReqFQDN, + agdtest.FilteredResponseTTLSec, + "abcdefg", + ), + }), + List: agd.FilterListIDCustom, + Rule: "", + }, + name: "dnsrewrite_txt", + ruleLists: []*rulelist.Refreshable{}, + }, { + custom: rlCustomTyped, + req: soaReq, + wantRes: &internal.ResultModified{ + Msg: dnsservertest.NewResp(dns.RcodeSuccess, soaReq), + List: agd.FilterListIDCustom, + }, + name: "dnsrewrite_soa", + ruleLists: []*rulelist.Refreshable{}, }} for _, tc := range testCases { @@ -262,10 +302,10 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) { ri := &agd.RequestInfo{ Messages: agdtest.NewConstructor(), Host: filtertest.ReqHost, - QType: dns.TypeA, + QType: tc.req.Question[0].Qtype, } - res, fltErr := f.FilterRequest(ctx, req, ri) + res, fltErr := f.FilterRequest(ctx, tc.req, ri) require.NoError(t, fltErr) assert.Equal(t, tc.wantRes, res) @@ -364,7 +404,7 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { const rewriteRule = filtertest.BlockRule + "$dnsrewrite=NOERROR;A;" + safeSearchIPStr - var safeSearchIP net.IP = netip.MustParseAddr(safeSearchIPStr).AsSlice() + safeSearchIP := netip.MustParseAddr(safeSearchIPStr) cachePath, srvURL := filtertest.PrepareRefreshable(t, nil, rewriteRule, http.StatusOK) const fltListID = agd.FilterListIDGeneralSafeSearch @@ -379,12 +419,12 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) { MaxSize: filtertest.FilterMaxSize, }, Resolver: &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{safeSearchIP}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{safeSearchIP}, nil }, }, ErrColl: &agdtest.ErrorCollector{ @@ -460,9 +500,9 @@ func TestFilter_FilterResponse(t *testing.T) { ) var ( - passedIPv4 net.IP = netip.MustParseAddr(passedIPv4Str).AsSlice() - blockedIPv4 net.IP = netip.MustParseAddr(blockedIPv4Str).AsSlice() - blockedIPv6 net.IP = netip.MustParseAddr(blockedIPv6Str).AsSlice() + passedIPv4 = netip.MustParseAddr(passedIPv4Str) + blockedIPv4 = netip.MustParseAddr(blockedIPv4Str) + blockedIPv6 = netip.MustParseAddr(blockedIPv6Str) ) blockingRL := newFromStr(t, blockRules, testFltListID1) @@ -492,7 +532,7 @@ func TestFilter_FilterResponse(t *testing.T) { wantRule: filtertest.ReqHost, respAns: dnsservertest.SectionAnswer{ dnsservertest.NewCNAME(cnameReqFQDN, ttl, filtertest.ReqFQDN), - dnsservertest.NewA(filtertest.ReqFQDN, ttl, net.IP{1, 2, 3, 4}), + dnsservertest.NewA(filtertest.ReqFQDN, ttl, netip.MustParseAddr("1.2.3.4")), }, qType: dns.TypeA, }, { @@ -515,38 +555,45 @@ func TestFilter_FilterResponse(t *testing.T) { name: "ipv4hint", reqFQDN: filtertest.ReqFQDN, wantRule: blockedIPv4Str, - respAns: dnsservertest.SectionAnswer{ - dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{blockedIPv4}, []net.IP{}), - }, + respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS( + filtertest.ReqFQDN, + ttl, + []netip.Addr{blockedIPv4}, + []netip.Addr{}, + )}, qType: dns.TypeHTTPS, }, { name: "ipv6hint", reqFQDN: filtertest.ReqFQDN, wantRule: blockedIPv6Str, - respAns: dnsservertest.SectionAnswer{ - dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{}, []net.IP{blockedIPv6}), - }, + respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS( + filtertest.ReqFQDN, + ttl, + []netip.Addr{}, + []netip.Addr{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}, - ), - }, + respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS( + filtertest.ReqFQDN, + ttl, + []netip.Addr{blockedIPv4}, + []netip.Addr{blockedIPv6}, + )}, qType: dns.TypeHTTPS, }, { name: "pass_hints", reqFQDN: filtertest.ReqFQDN, wantRule: "", - respAns: dnsservertest.SectionAnswer{ - dnsservertest.NewHTTPS(filtertest.ReqFQDN, ttl, []net.IP{passedIPv4}, []net.IP{}), - }, + respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS( + filtertest.ReqFQDN, + ttl, + []netip.Addr{passedIPv4}, + []netip.Addr{}, + )}, qType: dns.TypeHTTPS, }} diff --git a/internal/filter/internal/composite/dnsrewrite.go b/internal/filter/internal/composite/dnsrewrite.go index 8b30f45..73fcfa4 100644 --- a/internal/filter/internal/composite/dnsrewrite.go +++ b/internal/filter/internal/composite/dnsrewrite.go @@ -2,14 +2,14 @@ package composite import ( "fmt" - "net" + "net/netip" "strings" "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/urlfilter/rules" "github.com/miekg/dns" ) @@ -116,16 +116,13 @@ func processDNSRewriteRules(dnsr []*rules.NetworkRule) (res *dnsRewriteResult) { } // filterDNSRewrite handles dnsrewrite filters. It constructs a DNS -// response and returns it. +// response and returns it. dnsrr.RCode should be dns.RcodeSuccess and contain +// a non-empty dnsrr.Response. func filterDNSRewrite( messages *dnsmsg.Constructor, req *dns.Msg, dnsrr *dnsRewriteResult, ) (resp *dns.Msg, err error) { - if dnsrr.RCode != dns.RcodeSuccess { - return nil, errors.Error("non-success answer") - } - if dnsrr.Response == nil { return nil, errors.Error("no dns rewrite rule responses") } @@ -139,6 +136,10 @@ func filterDNSRewrite( ans, err = filterDNSRewriteResponse(messages, req, rr, v) if err != nil { return nil, fmt.Errorf("dns rewrite response for %d[%d]: %w", rr, i, err) + } else if ans == nil { + // TODO(e.burkov): Currently, urlfilter returns all the matched + // $dnsrewrite rules including invalid ones. Fix this behavior. + continue } resp.Answer = append(resp.Answer, ans) @@ -171,7 +172,7 @@ func filterDNSRewriteResponse( case dns.TypeSRV: return newAnswerSRV(messages, v, rr, req) default: - log.Debug("don't know how to handle dns rr type %d, skipping", rr) + optlog.Debug1("filters: don't know how to handle dns rr type %d, skipping", rr) return nil, nil } @@ -238,13 +239,13 @@ func newAnsFromIP( rr rules.RRType, req *dns.Msg, ) (ans dns.RR, err error) { - ip, ok := v.(net.IP) + ip, ok := v.(netip.Addr) if !ok { return nil, fmt.Errorf("value for rr type %d has type %T, not net.IP", rr, v) } if rr == dns.TypeA { - return messages.NewAnsA(req, ip.To4()) + return messages.NewAnsA(req, ip) } return messages.NewAnsAAAA(req, ip) diff --git a/internal/filter/internal/internal.go b/internal/filter/internal/internal.go index bceed64..1faddec 100644 --- a/internal/filter/internal/internal.go +++ b/internal/filter/internal/internal.go @@ -12,9 +12,6 @@ import ( "github.com/miekg/dns" ) -// Make sure that the signatures for FilterRequest match. -var _ RequestFilter = (Interface)(nil) - // Interface is the DNS request and response filter interface. type Interface interface { // FilterRequest filters the DNS request for the provided client. All @@ -41,4 +38,5 @@ const DefaultResolveTimeout = 1 * time.Second // RequestFilter can filter a request based on the request info. type RequestFilter interface { FilterRequest(ctx context.Context, req *dns.Msg, ri *agd.RequestInfo) (r Result, err error) + ID() (id agd.FilterListID) } diff --git a/internal/filter/internal/rulelist/rulelist.go b/internal/filter/internal/rulelist/rulelist.go index c2c0913..0fdf18a 100644 --- a/internal/filter/internal/rulelist/rulelist.go +++ b/internal/filter/internal/rulelist/rulelist.go @@ -117,9 +117,8 @@ func (f *filter) DNSResult( } dnsReq := &urlfilter.DNSRequest{ - Hostname: host, - // TODO(a.garipov): Make this a net.IP in module urlfilter. - ClientIP: clientIP.String(), + Hostname: host, + ClientIP: clientIP, ClientName: clientName, DNSType: rrType, Answer: isAns, diff --git a/internal/filter/internal/safesearch/safesearch.go b/internal/filter/internal/safesearch/safesearch.go index 3acac2d..63e0ddc 100644 --- a/internal/filter/internal/safesearch/safesearch.go +++ b/internal/filter/internal/safesearch/safesearch.go @@ -5,7 +5,6 @@ package safesearch import ( "context" "fmt" - "net" "net/netip" "time" @@ -108,7 +107,7 @@ func (f *Filter) FilterRequest( defer cancel() var result *dns.Msg - ips, err := f.resolver.LookupIP(ctx, fam, repHost) + ips, err := f.resolver.LookupNetIP(ctx, fam, repHost) if err != nil { agd.Collectf(ctx, f.errColl, "filter %s: resolving: %w", f.id, err) @@ -135,6 +134,11 @@ func (f *Filter) FilterRequest( return rm, nil } +// ID implements the [internal.RequestFilter] interface for *Filter. +func (f *Filter) ID() (id agd.FilterListID) { + return f.id +} + // safeSearchHost returns the replacement host for the given host and question // type, if any. qt should be either [dns.TypeA] or [dns.TypeAAAA]. func (f *Filter) safeSearchHost(host string, qt dnsmsg.RRType) (ssHost string, ok bool) { @@ -157,7 +161,7 @@ func (f *Filter) safeSearchHost(host string, qt dnsmsg.RRType) (ssHost string, o // A/AAAA or CNAME type. switch drw.RRType { case dns.TypeA, dns.TypeAAAA: - return drw.Value.(net.IP).String(), true + return drw.Value.(netip.Addr).String(), true default: continue } diff --git a/internal/filter/internal/safesearch/safesearch_test.go b/internal/filter/internal/safesearch/safesearch_test.go index fcaf66e..b1f4421 100644 --- a/internal/filter/internal/safesearch/safesearch_test.go +++ b/internal/filter/internal/safesearch/safesearch_test.go @@ -34,11 +34,11 @@ const testSafeIPStr = "1.2.3.4" // testIPOfEngineWithIP is the IP address of the safe version of // search-engine-ip.example. -var testIPOfEngineWithIP net.IP = netip.MustParseAddr(testSafeIPStr).AsSlice() +var testIPOfEngineWithIP = netip.MustParseAddr(testSafeIPStr) // testIPOfEngineWithDomain is the IP address of the safe version of // search-engine-domain.example. -var testIPOfEngineWithDomain = net.IP{1, 2, 3, 5} +var testIPOfEngineWithDomain = netip.MustParseAddr("1.2.3.5") // Common domain names for tests. const ( @@ -69,16 +69,16 @@ func TestFilter(t *testing.T) { MaxSize: filtertest.FilterMaxSize, }, Resolver: &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, host string, - ) (ips []net.IP, err error) { + ) (ips []netip.Addr, err error) { switch host { case testSafeIPStr: - return []net.IP{testIPOfEngineWithIP}, nil + return []netip.Addr{testIPOfEngineWithIP}, nil case testSafeDomain: - return []net.IP{testIPOfEngineWithDomain}, nil + return []netip.Addr{testIPOfEngineWithDomain}, nil default: return nil, errors.Error("test resolver error") } @@ -145,7 +145,7 @@ func TestFilter(t *testing.T) { assert.Equal(t, rm.Rule, agd.FilterRuleText(testEngineWithIP)) a := testutil.RequireTypeAssert[*dns.A](t, rm.Msg.Answer[0]) - assert.Equal(t, testIPOfEngineWithIP, a.A) + assert.Equal(t, net.IP(testIPOfEngineWithIP.AsSlice()), a.A) t.Run("cached", func(t *testing.T) { newReq, newRI := newReq(t, testEngineWithIP, dns.TypeA) @@ -180,7 +180,7 @@ func TestFilter(t *testing.T) { assert.Equal(t, rm.Rule, agd.FilterRuleText(testEngineWithDomain)) a := testutil.RequireTypeAssert[*dns.A](t, rm.Msg.Answer[0]) - assert.Equal(t, testIPOfEngineWithDomain, a.A) + assert.Equal(t, net.IP(testIPOfEngineWithDomain.AsSlice()), a.A) }) } diff --git a/internal/filter/internal/serviceblock/serviceblock.go b/internal/filter/internal/serviceblock/serviceblock.go index d812fee..0045f3a 100644 --- a/internal/filter/internal/serviceblock/serviceblock.go +++ b/internal/filter/internal/serviceblock/serviceblock.go @@ -14,6 +14,7 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal" "github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" + "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/log" ) @@ -128,7 +129,7 @@ func (f *Filter) loadIndex(ctx context.Context, acceptStale bool) (resp *indexRe return nil, fmt.Errorf("decoding index: %w", err) } - log.Debug("service filter: loaded index with %d blocked services", len(resp.BlockedServices)) + optlog.Debug1("service filter: loaded index with %d blocked services", len(resp.BlockedServices)) return resp, nil } diff --git a/internal/filter/storage_test.go b/internal/filter/storage_test.go index a72a728..03680ff 100644 --- a/internal/filter/storage_test.go +++ b/internal/filter/storage_test.go @@ -4,6 +4,7 @@ import ( "context" "io" "net" + "net/netip" "os" "path/filepath" "testing" @@ -131,12 +132,12 @@ func TestStorage_FilterFromContext_customAllow(t *testing.T) { } resolver := &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{safeBrowsingSafeIP4}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{safeBrowsingSafeIP4}, nil }, } @@ -227,12 +228,12 @@ func TestStorage_FilterFromContext_schedule(t *testing.T) { } resolver := &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{safeBrowsingSafeIP4}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{safeBrowsingSafeIP4}, nil }, } @@ -741,12 +742,12 @@ func TestStorage_FilterFromContext_safeBrowsing(t *testing.T) { } resolver := &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, _ netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { - return []net.IP{safeBrowsingSafeIP4}, nil + ) (ips []netip.Addr, err error) { + return []netip.Addr{safeBrowsingSafeIP4}, nil }, } @@ -809,18 +810,18 @@ func TestStorage_FilterFromContext_safeBrowsing(t *testing.T) { func TestStorage_FilterFromContext_safeSearch(t *testing.T) { numLookupIP := 0 resolver := &agdtest.Resolver{ - OnLookupIP: func( + OnLookupNetIP: func( _ context.Context, fam netutil.AddrFamily, _ string, - ) (ips []net.IP, err error) { + ) (ips []netip.Addr, err error) { numLookupIP++ if fam == netutil.AddrFamilyIPv4 { - return []net.IP{safeSearchIPRespIP4}, nil + return []netip.Addr{safeSearchIPRespIP4}, nil } - return []net.IP{safeSearchIPRespIP6}, nil + return []netip.Addr{safeSearchIPRespIP6}, nil }, } @@ -842,33 +843,33 @@ func TestStorage_FilterFromContext_safeSearch(t *testing.T) { } testCases := []struct { + wantIP netip.Addr name string host string - wantIP net.IP rrtype uint16 wantLookups int }{{ + wantIP: safeSearchIPRespIP4, name: "ip4", host: safeSearchIPHost, - wantIP: safeSearchIPRespIP4, rrtype: dns.TypeA, wantLookups: 1, }, { + wantIP: safeSearchIPRespIP6, name: "ip6", host: safeSearchIPHost, - wantIP: safeSearchIPRespIP6, rrtype: dns.TypeAAAA, wantLookups: 1, }, { + wantIP: safeSearchIPRespIP4, name: "host_ip4", host: safeSearchHost, - wantIP: safeSearchIPRespIP4, rrtype: dns.TypeA, wantLookups: 1, }, { + wantIP: safeSearchIPRespIP6, name: "host_ip6", host: safeSearchHost, - wantIP: safeSearchIPRespIP6, rrtype: dns.TypeAAAA, wantLookups: 1, }} @@ -896,22 +897,15 @@ func TestStorage_FilterFromContext_safeSearch(t *testing.T) { res := rm.Msg require.NotNil(t, res) - - if tc.wantIP == nil { - assert.Nil(t, res.Answer) - - return - } - require.Len(t, res.Answer, 1) switch ans := res.Answer[0]; ans := ans.(type) { case *dns.A: assert.Equal(t, tc.rrtype, ans.Hdr.Rrtype) - assert.Equal(t, tc.wantIP, ans.A) + assert.Equal(t, net.IP(tc.wantIP.AsSlice()), ans.A) case *dns.AAAA: assert.Equal(t, tc.rrtype, ans.Hdr.Rrtype) - assert.Equal(t, tc.wantIP, ans.AAAA) + assert.Equal(t, net.IP(tc.wantIP.AsSlice()), ans.AAAA) default: t.Fatalf("unexpected answer type %T(%[1]v)", ans) } diff --git a/internal/geoip/asntops.go b/internal/geoip/asntops.go index 43d4305..d638df4 100644 --- a/internal/geoip/asntops.go +++ b/internal/geoip/asntops.go @@ -7,238 +7,481 @@ import "github.com/AdguardTeam/AdGuardDNS/internal/agd" // DefaultTopASNs contains all specially handled ASNs. var DefaultTopASNs = map[agd.ASN]struct{}{ 2: {}, + 174: {}, + 209: {}, + 224: {}, + 559: {}, 577: {}, + 680: {}, 701: {}, + 719: {}, + 786: {}, + 803: {}, 812: {}, 852: {}, + 855: {}, + 1103: {}, 1136: {}, + 1213: {}, 1221: {}, 1241: {}, 1257: {}, 1267: {}, + 1299: {}, + 1403: {}, 1547: {}, + 1653: {}, + 1659: {}, 1680: {}, + 1759: {}, + 1764: {}, + 1835: {}, + 1836: {}, + 1853: {}, 1901: {}, + 1955: {}, + 2018: {}, + 2107: {}, + 2108: {}, 2110: {}, 2116: {}, 2119: {}, + 2497: {}, + 2510: {}, + 2514: {}, 2516: {}, 2518: {}, 2519: {}, 2527: {}, 2586: {}, + 2588: {}, + 2602: {}, + 2607: {}, 2609: {}, + 2611: {}, 2740: {}, + 2764: {}, + 2847: {}, + 2852: {}, 2856: {}, 2860: {}, + 3132: {}, + 3194: {}, 3209: {}, 3212: {}, 3215: {}, 3216: {}, + 3221: {}, + 3225: {}, 3238: {}, 3243: {}, 3249: {}, + 3255: {}, 3269: {}, 3292: {}, 3301: {}, 3303: {}, 3308: {}, 3320: {}, + 3326: {}, 3329: {}, 3352: {}, + 3356: {}, 3462: {}, + 3549: {}, 3605: {}, 3695: {}, + 3741: {}, + 3758: {}, + 3786: {}, 3816: {}, 3855: {}, 4007: {}, 4134: {}, + 4230: {}, + 4515: {}, + 4538: {}, 4609: {}, 4638: {}, + 4648: {}, 4657: {}, + 4685: {}, 4713: {}, + 4721: {}, + 4725: {}, + 4739: {}, + 4750: {}, 4760: {}, 4761: {}, 4764: {}, 4766: {}, + 4768: {}, 4771: {}, 4773: {}, 4775: {}, + 4780: {}, + 4787: {}, 4788: {}, 4804: {}, 4808: {}, + 4809: {}, 4812: {}, 4817: {}, 4818: {}, 4837: {}, + 4847: {}, + 5080: {}, 5089: {}, 5378: {}, 5384: {}, + 5385: {}, + 5390: {}, 5391: {}, + 5408: {}, 5410: {}, + 5413: {}, 5416: {}, 5432: {}, 5466: {}, 5483: {}, + 5518: {}, + 5532: {}, 5578: {}, 5603: {}, 5607: {}, 5610: {}, 5617: {}, 5639: {}, + 5645: {}, + 5650: {}, + 5713: {}, 5769: {}, 6057: {}, + 6079: {}, + 6128: {}, 6147: {}, 6167: {}, 6306: {}, 6327: {}, 6400: {}, + 6407: {}, + 6535: {}, 6568: {}, 6639: {}, 6661: {}, 6677: {}, 6697: {}, + 6700: {}, + 6703: {}, 6713: {}, 6730: {}, 6739: {}, 6752: {}, 6758: {}, + 6772: {}, 6799: {}, 6805: {}, 6810: {}, 6821: {}, 6830: {}, + 6843: {}, 6848: {}, + 6849: {}, 6855: {}, 6866: {}, + 6871: {}, + 6876: {}, + 6939: {}, 7018: {}, + 7029: {}, + 7122: {}, 7131: {}, + 7155: {}, 7303: {}, + 7311: {}, 7418: {}, + 7438: {}, + 7470: {}, + 7472: {}, + 7482: {}, + 7522: {}, + 7524: {}, 7545: {}, 7552: {}, + 7575: {}, 7642: {}, + 7679: {}, 7713: {}, + 7727: {}, 7738: {}, + 7794: {}, 7922: {}, 7992: {}, 8014: {}, 8048: {}, + 8053: {}, + 8075: {}, + 8094: {}, 8151: {}, 8167: {}, 8193: {}, + 8200: {}, + 8251: {}, + 8257: {}, + 8273: {}, + 8290: {}, 8301: {}, + 8339: {}, 8346: {}, 8359: {}, + 8368: {}, + 8369: {}, 8374: {}, 8376: {}, + 8386: {}, 8400: {}, 8402: {}, 8412: {}, + 8422: {}, + 8437: {}, + 8445: {}, 8447: {}, + 8449: {}, 8452: {}, + 8462: {}, + 8468: {}, 8473: {}, + 8511: {}, + 8542: {}, 8544: {}, 8551: {}, + 8560: {}, + 8562: {}, 8585: {}, + 8612: {}, + 8632: {}, + 8661: {}, 8680: {}, 8681: {}, 8697: {}, 8708: {}, + 8717: {}, + 8728: {}, + 8758: {}, 8764: {}, + 8767: {}, + 8772: {}, 8781: {}, 8818: {}, + 8821: {}, + 8847: {}, + 8849: {}, 8866: {}, 8881: {}, 8926: {}, + 8948: {}, 8953: {}, 8966: {}, + 8990: {}, + 9002: {}, + 9008: {}, 9009: {}, + 9031: {}, 9038: {}, + 9050: {}, 9051: {}, + 9063: {}, + 9070: {}, + 9105: {}, + 9119: {}, 9121: {}, + 9125: {}, + 9129: {}, + 9145: {}, 9146: {}, + 9155: {}, 9158: {}, + 9186: {}, 9198: {}, 9231: {}, + 9245: {}, 9246: {}, 9249: {}, + 9260: {}, 9269: {}, 9299: {}, + 9303: {}, 9304: {}, + 9316: {}, 9318: {}, 9329: {}, + 9335: {}, + 9341: {}, + 9351: {}, + 9354: {}, + 9365: {}, + 9381: {}, + 9416: {}, + 9419: {}, + 9431: {}, 9443: {}, 9471: {}, + 9484: {}, 9500: {}, 9506: {}, 9534: {}, 9541: {}, + 9595: {}, 9605: {}, + 9617: {}, 9644: {}, + 9658: {}, 9674: {}, + 9694: {}, 9751: {}, 9790: {}, 9808: {}, 9824: {}, + 9829: {}, 9873: {}, + 9876: {}, + 9902: {}, + 9908: {}, + 9919: {}, + 9924: {}, + 9930: {}, + 9931: {}, + 9934: {}, 9988: {}, + 10010: {}, + 10013: {}, 10030: {}, + 10036: {}, + 10075: {}, 10094: {}, + 10099: {}, 10101: {}, + 10103: {}, 10118: {}, 10131: {}, 10139: {}, 10143: {}, + 10214: {}, 10219: {}, 10226: {}, 10269: {}, + 10292: {}, + 10299: {}, 10396: {}, + 10429: {}, + 10474: {}, 10620: {}, + 10796: {}, + 10834: {}, + 11014: {}, 11081: {}, 11139: {}, + 11172: {}, + 11232: {}, + 11259: {}, + 11260: {}, 11269: {}, + 11290: {}, 11315: {}, + 11351: {}, + 11367: {}, + 11426: {}, 11427: {}, + 11492: {}, 11556: {}, 11562: {}, 11594: {}, 11664: {}, + 11776: {}, + 11814: {}, 11816: {}, 11830: {}, 11845: {}, + 11888: {}, + 11992: {}, + 12066: {}, + 12083: {}, 12252: {}, + 12271: {}, 12297: {}, + 12301: {}, 12302: {}, 12322: {}, + 12334: {}, + 12338: {}, + 12350: {}, 12353: {}, + 12361: {}, + 12365: {}, 12389: {}, + 12390: {}, 12392: {}, 12400: {}, + 12406: {}, + 12414: {}, 12430: {}, + 12436: {}, + 12455: {}, 12479: {}, + 12491: {}, + 12552: {}, + 12570: {}, + 12576: {}, 12578: {}, + 12590: {}, + 12591: {}, + 12605: {}, + 12668: {}, 12709: {}, + 12714: {}, 12716: {}, 12735: {}, 12741: {}, + 12754: {}, 12764: {}, + 12767: {}, + 12768: {}, 12793: {}, 12810: {}, + 12829: {}, 12849: {}, 12874: {}, 12876: {}, + 12880: {}, 12912: {}, 12929: {}, + 12946: {}, 12958: {}, 12969: {}, 12975: {}, + 12978: {}, 12997: {}, + 13000: {}, + 13030: {}, 13036: {}, + 13037: {}, + 13044: {}, + 13045: {}, + 13046: {}, + 13092: {}, + 13099: {}, + 13110: {}, 13122: {}, + 13124: {}, + 13127: {}, + 13156: {}, + 13188: {}, + 13189: {}, 13194: {}, + 13213: {}, 13280: {}, 13285: {}, + 13306: {}, 13335: {}, 13489: {}, 13771: {}, @@ -246,61 +489,138 @@ var DefaultTopASNs = map[agd.ASN]struct{}{ 14061: {}, 14080: {}, 14117: {}, + 14259: {}, 14434: {}, 14522: {}, 14593: {}, + 14618: {}, 14638: {}, 14709: {}, 14754: {}, 14813: {}, + 14868: {}, + 14956: {}, + 14979: {}, 14988: {}, + 15128: {}, 15146: {}, + 15169: {}, + 15311: {}, 15344: {}, + 15377: {}, 15378: {}, + 15383: {}, 15389: {}, 15397: {}, 15399: {}, + 15404: {}, 15433: {}, + 15435: {}, + 15440: {}, + 15457: {}, + 15474: {}, 15480: {}, 15502: {}, + 15511: {}, + 15516: {}, + 15525: {}, + 15542: {}, + 15547: {}, 15557: {}, + 15600: {}, + 15614: {}, + 15623: {}, + 15659: {}, 15704: {}, 15706: {}, 15735: {}, 15751: {}, + 15765: {}, + 15774: {}, 15796: {}, 15802: {}, 15805: {}, + 15808: {}, + 15836: {}, 15895: {}, + 15897: {}, + 15924: {}, + 15935: {}, + 15943: {}, + 15955: {}, 15958: {}, 15962: {}, 15964: {}, + 15965: {}, 15994: {}, + 16006: {}, 16010: {}, 16019: {}, 16028: {}, 16058: {}, + 16086: {}, 16116: {}, + 16117: {}, + 16125: {}, 16135: {}, + 16178: {}, + 16190: {}, + 16202: {}, + 16223: {}, + 16229: {}, 16232: {}, + 16245: {}, + 16246: {}, 16276: {}, + 16322: {}, + 16333: {}, + 16342: {}, 16345: {}, + 16413: {}, + 16437: {}, 16509: {}, + 16591: {}, + 16592: {}, + 16629: {}, 16637: {}, 16705: {}, + 16735: {}, + 16814: {}, + 16906: {}, + 16960: {}, 17072: {}, 17079: {}, 17400: {}, + 17411: {}, 17421: {}, + 17451: {}, 17458: {}, 17470: {}, 17480: {}, + 17488: {}, 17501: {}, + 17506: {}, 17511: {}, + 17539: {}, + 17547: {}, 17552: {}, 17557: {}, + 17621: {}, + 17622: {}, + 17623: {}, + 17638: {}, 17639: {}, + 17665: {}, + 17670: {}, 17676: {}, + 17698: {}, + 17705: {}, + 17716: {}, + 17726: {}, + 17747: {}, + 17809: {}, + 17816: {}, + 17828: {}, 17853: {}, 17858: {}, 17882: {}, @@ -309,611 +629,2626 @@ var DefaultTopASNs = map[agd.ASN]struct{}{ 17976: {}, 17993: {}, 18001: {}, + 18004: {}, + 18013: {}, 18024: {}, + 18049: {}, + 18053: {}, + 18081: {}, + 18106: {}, + 18126: {}, + 18144: {}, + 18182: {}, 18200: {}, + 18209: {}, + 18371: {}, + 18390: {}, + 18399: {}, 18403: {}, + 18419: {}, + 18429: {}, + 18734: {}, + 18747: {}, 18809: {}, + 18822: {}, + 18840: {}, 18881: {}, + 19016: {}, + 19037: {}, + 19108: {}, 19114: {}, + 19180: {}, + 19182: {}, 19246: {}, + 19397: {}, + 19422: {}, 19429: {}, + 19624: {}, 19711: {}, 19863: {}, + 19889: {}, 20001: {}, + 20055: {}, + 20057: {}, 20115: {}, + 20207: {}, + 20255: {}, 20294: {}, + 20299: {}, + 20365: {}, 20473: {}, + 20485: {}, + 20598: {}, 20634: {}, + 20661: {}, + 20676: {}, + 20719: {}, + 20771: {}, 20776: {}, + 20804: {}, 20845: {}, + 20846: {}, + 20860: {}, 20875: {}, + 20880: {}, 20910: {}, + 20911: {}, + 20928: {}, + 20960: {}, + 20963: {}, 20978: {}, + 21001: {}, 21003: {}, + 21021: {}, + 21040: {}, + 21050: {}, + 21069: {}, + 21107: {}, 21183: {}, + 21196: {}, + 21211: {}, + 21217: {}, + 21230: {}, + 21232: {}, 21246: {}, 21271: {}, 21277: {}, 21283: {}, 21299: {}, + 21305: {}, 21334: {}, 21351: {}, 21412: {}, + 21430: {}, 21450: {}, + 21491: {}, 21497: {}, + 21559: {}, 21575: {}, + 21599: {}, 21744: {}, + 21804: {}, 21826: {}, 21859: {}, 21928: {}, + 21949: {}, 21996: {}, 22047: {}, 22069: {}, 22085: {}, + 22313: {}, 22363: {}, + 22394: {}, + 22411: {}, + 22423: {}, + 22581: {}, 22724: {}, + 22735: {}, 22773: {}, + 22869: {}, + 22884: {}, 22927: {}, 22933: {}, + 22995: {}, + 23114: {}, 23201: {}, 23243: {}, 23487: {}, + 23520: {}, 23655: {}, 23657: {}, + 23673: {}, 23674: {}, + 23688: {}, 23693: {}, + 23700: {}, + 23750: {}, 23752: {}, + 23764: {}, + 23856: {}, + 23860: {}, + 23888: {}, 23889: {}, + 23900: {}, + 23917: {}, + 23923: {}, + 23944: {}, 23955: {}, + 23956: {}, 23969: {}, + 24016: {}, + 24033: {}, + 24086: {}, + 24157: {}, 24158: {}, + 24163: {}, + 24164: {}, + 24165: {}, + 24186: {}, 24203: {}, + 24309: {}, + 24323: {}, + 24324: {}, + 24337: {}, 24378: {}, 24389: {}, + 24390: {}, + 24400: {}, 24432: {}, 24439: {}, + 24441: {}, + 24444: {}, + 24445: {}, + 24492: {}, 24499: {}, + 24547: {}, + 24550: {}, + 24559: {}, + 24560: {}, + 24589: {}, + 24608: {}, 24651: {}, 24691: {}, 24722: {}, + 24743: {}, 24757: {}, + 24800: {}, + 24812: {}, + 24822: {}, 24835: {}, + 24852: {}, + 24863: {}, + 24875: {}, + 24889: {}, 24921: {}, 24940: {}, + 24955: {}, + 24961: {}, 25019: {}, 25106: {}, + 25117: {}, + 25124: {}, + 25129: {}, + 25133: {}, 25135: {}, 25139: {}, 25144: {}, 25159: {}, + 25180: {}, + 25184: {}, + 25190: {}, + 25229: {}, + 25248: {}, 25250: {}, 25255: {}, + 25274: {}, + 25310: {}, + 25369: {}, + 25374: {}, + 25375: {}, + 25400: {}, + 25406: {}, + 25424: {}, + 25429: {}, + 25441: {}, + 25447: {}, 25454: {}, + 25467: {}, + 25471: {}, 25472: {}, + 25491: {}, + 25509: {}, + 25512: {}, 25513: {}, + 25528: {}, 25543: {}, + 25596: {}, + 25607: {}, + 25620: {}, + 25668: {}, + 25695: {}, + 26130: {}, + 26210: {}, + 26383: {}, 26599: {}, 26611: {}, 26615: {}, + 26617: {}, 27651: {}, 27653: {}, 27660: {}, 27665: {}, + 27668: {}, + 27672: {}, 27694: {}, + 27695: {}, + 27696: {}, 27699: {}, + 27708: {}, + 27717: {}, 27725: {}, + 27729: {}, 27734: {}, 27738: {}, + 27742: {}, 27745: {}, 27747: {}, + 27757: {}, + 27759: {}, 27773: {}, + 27774: {}, 27775: {}, 27781: {}, + 27789: {}, + 27796: {}, 27800: {}, + 27813: {}, 27831: {}, + 27833: {}, + 27837: {}, + 27839: {}, + 27843: {}, + 27866: {}, + 27876: {}, 27882: {}, 27884: {}, + 27887: {}, + 27889: {}, 27895: {}, 27901: {}, 27903: {}, + 27923: {}, 27924: {}, + 27927: {}, + 27928: {}, + 27932: {}, 27947: {}, + 27951: {}, + 27953: {}, + 27955: {}, + 27983: {}, + 27984: {}, 27995: {}, 28005: {}, 28006: {}, + 28007: {}, + 28015: {}, + 28024: {}, + 28032: {}, 28036: {}, + 28048: {}, + 28075: {}, + 28080: {}, + 28088: {}, 28094: {}, 28104: {}, + 28110: {}, 28118: {}, 28126: {}, + 28146: {}, + 28171: {}, + 28186: {}, + 28198: {}, + 28201: {}, + 28202: {}, + 28210: {}, + 28220: {}, + 28258: {}, + 28294: {}, + 28343: {}, + 28398: {}, 28403: {}, + 28432: {}, + 28438: {}, + 28458: {}, + 28469: {}, + 28481: {}, + 28509: {}, + 28512: {}, + 28530: {}, + 28531: {}, + 28532: {}, + 28534: {}, + 28536: {}, + 28537: {}, + 28541: {}, + 28545: {}, 28548: {}, + 28554: {}, + 28555: {}, 28573: {}, + 28580: {}, + 28598: {}, + 28649: {}, + 28656: {}, + 28668: {}, + 28669: {}, 28683: {}, + 28685: {}, 28698: {}, + 28725: {}, + 28753: {}, + 28760: {}, 28787: {}, + 28812: {}, + 28840: {}, + 28849: {}, + 28851: {}, + 28878: {}, 28884: {}, 28885: {}, + 28919: {}, 28952: {}, + 28964: {}, + 28972: {}, + 29027: {}, + 29030: {}, + 29039: {}, 29049: {}, 29061: {}, + 29070: {}, + 29084: {}, + 29091: {}, + 29119: {}, + 29124: {}, 29170: {}, + 29194: {}, + 29208: {}, 29238: {}, 29244: {}, 29247: {}, 29256: {}, + 29286: {}, + 29310: {}, 29314: {}, + 29348: {}, 29355: {}, 29357: {}, + 29384: {}, + 29405: {}, 29447: {}, 29465: {}, + 29467: {}, + 29485: {}, + 29492: {}, + 29497: {}, 29518: {}, 29544: {}, 29555: {}, 29571: {}, + 29580: {}, + 29582: {}, + 29584: {}, 29614: {}, + 29687: {}, 29695: {}, 29975: {}, + 30036: {}, + 30058: {}, + 30526: {}, + 30600: {}, + 30619: {}, 30689: {}, 30722: {}, + 30764: {}, 30844: {}, + 30848: {}, + 30860: {}, 30873: {}, + 30896: {}, + 30929: {}, 30969: {}, + 30982: {}, 30985: {}, 30986: {}, 30990: {}, 30992: {}, 30999: {}, 31012: {}, + 31027: {}, 31037: {}, 31042: {}, + 31115: {}, + 31117: {}, + 31122: {}, + 31126: {}, + 31127: {}, 31133: {}, + 31143: {}, + 31148: {}, 31163: {}, + 31169: {}, + 31200: {}, + 31204: {}, 31205: {}, + 31208: {}, 31213: {}, + 31214: {}, 31224: {}, + 31242: {}, + 31245: {}, + 31246: {}, 31252: {}, + 31263: {}, + 31272: {}, + 31287: {}, + 31404: {}, + 31423: {}, 31452: {}, + 31499: {}, + 31543: {}, + 31549: {}, 31615: {}, + 31655: {}, + 31679: {}, + 31689: {}, 31721: {}, + 31725: {}, + 31726: {}, + 31856: {}, + 31898: {}, + 31960: {}, 32020: {}, + 32098: {}, + 32398: {}, + 32437: {}, + 32860: {}, + 33363: {}, 33392: {}, 33567: {}, 33576: {}, 33582: {}, + 33588: {}, 33763: {}, 33765: {}, 33771: {}, 33779: {}, + 33781: {}, + 33788: {}, + 33796: {}, + 33852: {}, 33874: {}, + 33885: {}, 33915: {}, + 33922: {}, 33983: {}, + 34001: {}, 34058: {}, + 34087: {}, + 34113: {}, + 34120: {}, 34170: {}, + 34187: {}, + 34224: {}, + 34244: {}, + 34245: {}, + 34295: {}, + 34296: {}, + 34306: {}, + 34362: {}, + 34458: {}, + 34525: {}, + 34533: {}, + 34547: {}, 34557: {}, + 34569: {}, + 34577: {}, + 34594: {}, + 34606: {}, + 34661: {}, + 34666: {}, + 34683: {}, + 34700: {}, + 34702: {}, + 34705: {}, + 34718: {}, + 34724: {}, + 34743: {}, + 34754: {}, + 34772: {}, 34779: {}, + 34797: {}, 34803: {}, + 34820: {}, + 34841: {}, + 34857: {}, + 34876: {}, + 34918: {}, + 34977: {}, 34984: {}, 35047: {}, + 35063: {}, + 35091: {}, + 35104: {}, + 35132: {}, + 35141: {}, + 35179: {}, + 35191: {}, 35197: {}, 35223: {}, 35228: {}, + 35244: {}, + 35277: {}, + 35297: {}, + 35311: {}, + 35320: {}, + 35328: {}, + 35346: {}, + 35362: {}, + 35370: {}, + 35394: {}, 35432: {}, 35444: {}, + 35457: {}, + 35493: {}, + 35518: {}, + 35549: {}, + 35566: {}, + 35567: {}, + 35568: {}, + 35612: {}, + 35613: {}, + 35656: {}, + 35699: {}, + 35706: {}, + 35725: {}, + 35729: {}, + 35753: {}, + 35790: {}, 35805: {}, + 35807: {}, + 35816: {}, 35819: {}, 35900: {}, + 35913: {}, + 36040: {}, 36290: {}, + 36384: {}, + 36445: {}, + 36492: {}, + 36511: {}, 36549: {}, + 36864: {}, + 36865: {}, 36866: {}, 36873: {}, + 36874: {}, + 36877: {}, 36884: {}, 36890: {}, + 36892: {}, + 36902: {}, 36903: {}, + 36905: {}, 36907: {}, 36908: {}, + 36909: {}, 36912: {}, + 36913: {}, + 36914: {}, + 36916: {}, + 36920: {}, + 36923: {}, 36924: {}, 36925: {}, 36926: {}, + 36930: {}, 36935: {}, + 36937: {}, 36939: {}, 36947: {}, + 36955: {}, 36958: {}, + 36959: {}, 36962: {}, 36963: {}, + 36965: {}, + 36969: {}, + 36972: {}, 36974: {}, + 36977: {}, + 36986: {}, 36988: {}, 36992: {}, + 36994: {}, 36996: {}, 36998: {}, 36999: {}, 37002: {}, 37006: {}, + 37008: {}, 37009: {}, + 37014: {}, 37020: {}, + 37027: {}, + 37028: {}, + 37030: {}, 37035: {}, 37037: {}, + 37049: {}, + 37053: {}, 37054: {}, 37057: {}, + 37061: {}, + 37063: {}, 37069: {}, + 37073: {}, + 37074: {}, 37075: {}, + 37076: {}, + 37081: {}, + 37084: {}, + 37090: {}, 37094: {}, + 37098: {}, + 37100: {}, 37105: {}, + 37110: {}, 37113: {}, 37119: {}, + 37123: {}, + 37124: {}, + 37126: {}, + 37129: {}, 37133: {}, 37136: {}, + 37141: {}, + 37143: {}, + 37148: {}, 37154: {}, + 37163: {}, 37164: {}, 37168: {}, 37173: {}, + 37183: {}, + 37184: {}, + 37187: {}, + 37189: {}, 37190: {}, + 37196: {}, + 37203: {}, 37204: {}, 37205: {}, + 37208: {}, + 37211: {}, + 37215: {}, + 37219: {}, 37223: {}, 37228: {}, 37229: {}, 37233: {}, + 37254: {}, + 37257: {}, + 37272: {}, + 37282: {}, 37284: {}, 37287: {}, + 37292: {}, 37294: {}, + 37303: {}, + 37305: {}, 37309: {}, + 37313: {}, + 37315: {}, 37323: {}, + 37326: {}, + 37329: {}, + 37333: {}, + 37334: {}, 37336: {}, 37337: {}, + 37340: {}, 37342: {}, 37343: {}, + 37347: {}, + 37349: {}, + 37350: {}, + 37353: {}, + 37358: {}, 37371: {}, 37376: {}, 37385: {}, + 37395: {}, 37406: {}, 37410: {}, + 37414: {}, 37424: {}, + 37425: {}, + 37427: {}, + 37429: {}, + 37430: {}, 37440: {}, 37447: {}, + 37449: {}, + 37451: {}, 37453: {}, 37457: {}, 37460: {}, 37461: {}, + 37462: {}, 37473: {}, + 37480: {}, + 37489: {}, 37492: {}, + 37503: {}, 37508: {}, 37517: {}, 37524: {}, 37526: {}, 37529: {}, 37531: {}, + 37532: {}, 37541: {}, + 37542: {}, + 37545: {}, 37550: {}, 37552: {}, + 37558: {}, 37559: {}, 37563: {}, + 37564: {}, + 37571: {}, 37575: {}, 37577: {}, + 37580: {}, + 37582: {}, + 37584: {}, + 37586: {}, + 37593: {}, 37594: {}, + 37604: {}, 37611: {}, + 37612: {}, 37614: {}, 37616: {}, + 37619: {}, + 37621: {}, + 37622: {}, + 37637: {}, + 37638: {}, + 37642: {}, 37645: {}, + 37649: {}, + 37654: {}, + 37665: {}, 37671: {}, 37678: {}, + 37680: {}, + 37682: {}, 37693: {}, + 37697: {}, 37705: {}, + 37711: {}, + 37721: {}, + 37917: {}, + 37963: {}, 38008: {}, 38009: {}, + 38067: {}, 38077: {}, + 38136: {}, 38195: {}, 38198: {}, 38201: {}, + 38203: {}, + 38209: {}, + 38229: {}, 38235: {}, + 38247: {}, + 38264: {}, + 38266: {}, + 38322: {}, 38442: {}, 38466: {}, + 38511: {}, + 38553: {}, 38565: {}, + 38584: {}, + 38592: {}, + 38600: {}, 38623: {}, 38742: {}, + 38794: {}, + 38805: {}, 38819: {}, + 38841: {}, + 38851: {}, 38901: {}, 38999: {}, + 39007: {}, 39010: {}, + 39032: {}, + 39067: {}, + 39074: {}, + 39093: {}, + 39122: {}, + 39184: {}, + 39216: {}, 39232: {}, + 39273: {}, + 39279: {}, + 39280: {}, + 39308: {}, + 39344: {}, + 39351: {}, + 39354: {}, + 39375: {}, + 39386: {}, + 39397: {}, + 39401: {}, 39402: {}, + 39501: {}, + 39507: {}, + 39544: {}, + 39572: {}, 39603: {}, + 39608: {}, 39611: {}, 39642: {}, + 39647: {}, + 39650: {}, + 39686: {}, + 39766: {}, + 39824: {}, + 39826: {}, + 39878: {}, 39891: {}, + 39927: {}, + 40021: {}, + 40029: {}, + 40191: {}, + 40676: {}, + 40788: {}, 40945: {}, + 40980: {}, + 41032: {}, + 41046: {}, + 41088: {}, + 41124: {}, 41164: {}, 41202: {}, + 41230: {}, + 41275: {}, + 41313: {}, 41329: {}, 41330: {}, + 41354: {}, + 41371: {}, + 41378: {}, + 41436: {}, + 41496: {}, + 41549: {}, 41557: {}, + 41563: {}, + 41564: {}, + 41627: {}, + 41676: {}, 41697: {}, + 41704: {}, + 41733: {}, 41738: {}, + 41745: {}, 41750: {}, + 41798: {}, + 41820: {}, + 41833: {}, + 41881: {}, 41897: {}, 41937: {}, + 41956: {}, + 41997: {}, 42003: {}, + 42013: {}, + 42020: {}, 42082: {}, + 42083: {}, + 42099: {}, + 42109: {}, + 42162: {}, + 42183: {}, + 42232: {}, + 42235: {}, + 42248: {}, 42298: {}, + 42306: {}, 42313: {}, + 42314: {}, + 42334: {}, + 42337: {}, + 42410: {}, 42437: {}, + 42525: {}, 42532: {}, + 42541: {}, 42560: {}, + 42571: {}, + 42580: {}, + 42581: {}, 42610: {}, + 42652: {}, + 42682: {}, + 42689: {}, + 42708: {}, + 42713: {}, 42772: {}, 42779: {}, + 42781: {}, + 42828: {}, + 42837: {}, + 42841: {}, + 42852: {}, 42863: {}, + 42908: {}, + 42912: {}, + 42925: {}, + 42960: {}, 42961: {}, + 42991: {}, 43019: {}, + 43060: {}, + 43139: {}, 43197: {}, + 43205: {}, 43242: {}, + 43248: {}, + 43256: {}, + 43350: {}, + 43406: {}, 43447: {}, + 43451: {}, + 43452: {}, + 43513: {}, + 43529: {}, + 43533: {}, 43557: {}, + 43568: {}, 43612: {}, + 43627: {}, + 43633: {}, + 43653: {}, + 43700: {}, + 43708: {}, 43733: {}, + 43754: {}, 43766: {}, + 43768: {}, 43824: {}, + 43870: {}, + 43922: {}, 43925: {}, + 43939: {}, 43940: {}, + 44027: {}, 44034: {}, + 44066: {}, + 44075: {}, 44087: {}, + 44134: {}, 44143: {}, + 44212: {}, + 44213: {}, + 44217: {}, + 44234: {}, 44244: {}, + 44285: {}, + 44313: {}, + 44327: {}, + 44377: {}, + 44391: {}, 44395: {}, 44477: {}, + 44483: {}, 44489: {}, + 44515: {}, + 44546: {}, 44558: {}, + 44566: {}, 44575: {}, + 44631: {}, + 44702: {}, + 44709: {}, + 44725: {}, 44735: {}, 44869: {}, + 44894: {}, + 44901: {}, 44925: {}, + 45090: {}, + 45102: {}, 45143: {}, 45177: {}, 45178: {}, + 45224: {}, 45245: {}, + 45267: {}, + 45271: {}, + 45305: {}, 45345: {}, 45355: {}, 45356: {}, + 45410: {}, + 45458: {}, + 45461: {}, + 45475: {}, 45498: {}, + 45543: {}, + 45558: {}, + 45588: {}, 45609: {}, 45629: {}, 45650: {}, 45669: {}, 45727: {}, + 45754: {}, 45758: {}, + 45763: {}, + 45766: {}, + 45773: {}, + 45845: {}, 45879: {}, 45891: {}, 45899: {}, + 45903: {}, + 45905: {}, + 45916: {}, + 45918: {}, + 45925: {}, + 45935: {}, 45960: {}, + 46198: {}, 46408: {}, + 46562: {}, 46650: {}, + 46868: {}, + 46941: {}, 47139: {}, + 47159: {}, + 47169: {}, + 47232: {}, 47237: {}, + 47253: {}, 47331: {}, 47377: {}, 47394: {}, + 47485: {}, + 47524: {}, + 47588: {}, 47589: {}, + 47702: {}, + 47782: {}, + 47798: {}, + 47881: {}, 47883: {}, + 47887: {}, + 47898: {}, + 47942: {}, + 47956: {}, + 47959: {}, + 47962: {}, 47975: {}, 48092: {}, + 48133: {}, + 48146: {}, + 48147: {}, + 48161: {}, 48190: {}, 48206: {}, + 48233: {}, 48252: {}, + 48253: {}, + 48260: {}, + 48271: {}, + 48418: {}, + 48437: {}, + 48480: {}, + 48492: {}, 48503: {}, + 48506: {}, + 48629: {}, + 48642: {}, 48675: {}, + 48685: {}, + 48695: {}, + 48716: {}, 48728: {}, + 48803: {}, + 48830: {}, 48832: {}, 48847: {}, + 48861: {}, 48887: {}, + 48917: {}, + 48926: {}, + 48945: {}, + 48953: {}, + 48966: {}, + 49020: {}, + 49040: {}, + 49056: {}, + 49070: {}, + 49100: {}, + 49115: {}, + 49117: {}, + 49129: {}, + 49223: {}, 49273: {}, + 49455: {}, + 49472: {}, + 49528: {}, + 49561: {}, + 49586: {}, + 49602: {}, 49628: {}, + 49666: {}, + 49724: {}, + 49725: {}, 49800: {}, + 49808: {}, + 49889: {}, 49902: {}, + 49914: {}, 49981: {}, 50010: {}, + 50025: {}, + 50181: {}, 50223: {}, + 50231: {}, 50251: {}, + 50261: {}, 50266: {}, - 50360: {}, + 50274: {}, + 50304: {}, + 50334: {}, + 50349: {}, + 50411: {}, + 50463: {}, + 50467: {}, + 50500: {}, + 50581: {}, + 50597: {}, + 50613: {}, 50616: {}, + 50635: {}, + 50648: {}, + 50670: {}, + 50673: {}, + 50685: {}, + 50698: {}, 50710: {}, + 50767: {}, + 50770: {}, + 50810: {}, + 50821: {}, + 50825: {}, + 50925: {}, + 50953: {}, + 50959: {}, 50973: {}, + 50979: {}, + 51018: {}, + 51110: {}, + 51167: {}, + 51175: {}, + 51184: {}, 51207: {}, + 51265: {}, + 51319: {}, + 51336: {}, + 51346: {}, + 51371: {}, 51375: {}, + 51395: {}, + 51399: {}, 51407: {}, + 51430: {}, + 51469: {}, 51495: {}, + 51504: {}, + 51561: {}, + 51582: {}, + 51604: {}, + 51615: {}, + 51653: {}, + 51684: {}, 51765: {}, + 51784: {}, + 51825: {}, + 51852: {}, 51896: {}, + 51947: {}, + 52000: {}, + 52075: {}, + 52116: {}, + 52157: {}, + 52173: {}, 52228: {}, + 52232: {}, 52233: {}, + 52238: {}, + 52242: {}, + 52251: {}, 52253: {}, + 52257: {}, 52260: {}, 52262: {}, 52263: {}, + 52286: {}, + 52312: {}, + 52323: {}, 52341: {}, + 52361: {}, 52362: {}, + 52363: {}, + 52381: {}, 52398: {}, + 52405: {}, + 52412: {}, + 52423: {}, + 52433: {}, + 52436: {}, + 52444: {}, + 52455: {}, + 52465: {}, 52468: {}, + 52471: {}, + 52613: {}, + 52783: {}, + 52974: {}, + 53006: {}, 53667: {}, + 53764: {}, + 53926: {}, + 54115: {}, + 54198: {}, + 54614: {}, + 55081: {}, 55330: {}, + 55387: {}, + 55391: {}, + 55392: {}, + 55424: {}, + 55427: {}, 55430: {}, + 55501: {}, + 55577: {}, + 55685: {}, + 55699: {}, + 55769: {}, + 55784: {}, + 55792: {}, 55805: {}, + 55821: {}, 55836: {}, 55850: {}, + 55853: {}, + 55872: {}, + 55900: {}, + 55915: {}, 55943: {}, 55944: {}, + 55990: {}, + 56017: {}, + 56030: {}, + 56040: {}, + 56041: {}, + 56042: {}, + 56044: {}, + 56046: {}, + 56047: {}, + 56048: {}, 56055: {}, 56089: {}, + 56099: {}, + 56120: {}, 56167: {}, + 56204: {}, + 56207: {}, + 56231: {}, + 56262: {}, + 56293: {}, 56300: {}, + 56329: {}, + 56349: {}, + 56388: {}, + 56400: {}, + 56410: {}, + 56478: {}, + 56484: {}, + 56491: {}, + 56566: {}, + 56568: {}, + 56630: {}, + 56653: {}, + 56655: {}, + 56656: {}, 56665: {}, + 56694: {}, 56696: {}, + 56704: {}, + 56709: {}, + 56803: {}, 56902: {}, + 56933: {}, + 56995: {}, + 57013: {}, + 57016: {}, + 57043: {}, + 57070: {}, + 57101: {}, + 57133: {}, + 57134: {}, 57218: {}, + 57248: {}, + 57256: {}, 57269: {}, 57293: {}, + 57344: {}, + 57370: {}, + 57374: {}, 57388: {}, + 57389: {}, 57513: {}, + 57564: {}, + 57566: {}, + 57588: {}, + 57608: {}, + 57630: {}, + 57634: {}, + 57704: {}, + 57722: {}, + 57728: {}, + 57743: {}, + 57760: {}, + 57761: {}, + 57764: {}, + 57778: {}, + 57794: {}, + 57852: {}, + 57858: {}, + 57869: {}, + 57888: {}, + 58056: {}, + 58061: {}, + 58065: {}, + 58118: {}, 58224: {}, + 58254: {}, + 58322: {}, + 58328: {}, + 58424: {}, + 58453: {}, 58460: {}, + 58461: {}, + 58485: {}, + 58504: {}, + 58507: {}, + 58524: {}, + 58593: {}, + 58610: {}, + 58656: {}, + 58666: {}, + 58682: {}, + 58689: {}, + 58715: {}, + 58717: {}, 58731: {}, + 58821: {}, + 58895: {}, + 58923: {}, + 58945: {}, 58952: {}, + 59078: {}, + 59129: {}, + 59253: {}, 59257: {}, - 59974: {}, + 59317: {}, + 59355: {}, + 59362: {}, + 59443: {}, + 59463: {}, + 59497: {}, + 59588: {}, + 59625: {}, + 59668: {}, + 59702: {}, + 59729: {}, + 59847: {}, + 59861: {}, + 59890: {}, + 59931: {}, 59989: {}, 60068: {}, + 60111: {}, + 60138: {}, 60258: {}, + 60268: {}, + 60294: {}, + 60304: {}, + 60325: {}, + 60352: {}, + 60353: {}, + 60367: {}, + 60372: {}, 60471: {}, + 60485: {}, + 60517: {}, + 60636: {}, + 60656: {}, + 60723: {}, + 60725: {}, + 60757: {}, 60781: {}, + 60806: {}, + 60999: {}, + 61071: {}, + 61098: {}, + 61135: {}, 61143: {}, + 61154: {}, + 61189: {}, 61272: {}, + 61275: {}, + 61287: {}, + 61307: {}, + 61345: {}, + 61367: {}, 61461: {}, + 61466: {}, + 61478: {}, + 61512: {}, + 62005: {}, + 62013: {}, + 62027: {}, + 62161: {}, + 62179: {}, + 62240: {}, + 62250: {}, + 62282: {}, + 62336: {}, + 62337: {}, + 62386: {}, + 62563: {}, + 62627: {}, + 63023: {}, + 63199: {}, 63473: {}, + 63526: {}, + 63852: {}, + 63859: {}, 63949: {}, + 63969: {}, + 63991: {}, + 63996: {}, + 64037: {}, + 64043: {}, + 64105: {}, + 64126: {}, + 64134: {}, + 64270: {}, + 64300: {}, 64466: {}, + 131090: {}, + 131111: {}, 131178: {}, + 131207: {}, 131267: {}, 131284: {}, + 131322: {}, 131429: {}, 131445: {}, + 131464: {}, + 131471: {}, + 131584: {}, + 131591: {}, + 131596: {}, + 131607: {}, + 131627: {}, + 132021: {}, 132045: {}, 132061: {}, + 132080: {}, + 132104: {}, + 132116: {}, + 132148: {}, + 132160: {}, 132165: {}, 132167: {}, + 132173: {}, 132199: {}, + 132203: {}, + 132222: {}, + 132280: {}, + 132298: {}, + 132447: {}, + 132449: {}, + 132462: {}, + 132468: {}, 132471: {}, + 132480: {}, + 132513: {}, + 132525: {}, 132618: {}, + 132652: {}, + 132686: {}, + 132730: {}, + 132825: {}, + 132831: {}, + 133012: {}, + 133192: {}, + 133287: {}, + 133334: {}, + 133380: {}, + 133384: {}, 133385: {}, + 133414: {}, + 133440: {}, + 133453: {}, + 133480: {}, 133481: {}, + 133524: {}, 133579: {}, 133606: {}, 133612: {}, + 133613: {}, + 133623: {}, + 133661: {}, + 133875: {}, + 133894: {}, + 133957: {}, + 133982: {}, + 134090: {}, + 134134: {}, + 134204: {}, + 134356: {}, + 134359: {}, + 134413: {}, + 134489: {}, + 134562: {}, + 134599: {}, + 134651: {}, + 134674: {}, + 134697: {}, + 134707: {}, + 134714: {}, + 134715: {}, + 134732: {}, + 134739: {}, 134783: {}, + 134806: {}, 134840: {}, + 134995: {}, + 135043: {}, + 135059: {}, + 135069: {}, + 135126: {}, + 135298: {}, + 135300: {}, + 135333: {}, + 135371: {}, + 135375: {}, + 135376: {}, + 135377: {}, + 135405: {}, + 135407: {}, 135409: {}, + 135427: {}, + 135478: {}, + 135589: {}, + 135600: {}, + 135607: {}, + 135887: {}, + 136030: {}, + 136039: {}, + 136093: {}, + 136119: {}, + 136167: {}, + 136210: {}, + 136224: {}, + 136238: {}, 136255: {}, + 136258: {}, + 136379: {}, + 136380: {}, + 136384: {}, + 136442: {}, + 136454: {}, + 136474: {}, + 136479: {}, + 136480: {}, + 136515: {}, + 136525: {}, + 136530: {}, + 136538: {}, 136557: {}, + 136763: {}, + 136780: {}, + 136787: {}, + 136873: {}, + 136897: {}, + 136907: {}, + 136919: {}, + 136950: {}, + 136969: {}, + 136972: {}, + 136975: {}, + 136994: {}, + 137047: {}, + 137056: {}, + 137080: {}, + 137226: {}, + 137236: {}, + 137409: {}, 137412: {}, + 137424: {}, + 137449: {}, + 137453: {}, + 137477: {}, + 137526: {}, + 137561: {}, + 137577: {}, + 137580: {}, 137824: {}, + 137872: {}, + 137891: {}, + 137905: {}, + 137952: {}, + 137959: {}, + 137967: {}, + 137989: {}, + 138089: {}, + 138096: {}, + 138167: {}, + 138168: {}, 138179: {}, + 138197: {}, + 138346: {}, + 138368: {}, + 138384: {}, + 138388: {}, + 138423: {}, + 138500: {}, + 138506: {}, + 138529: {}, + 138590: {}, + 138629: {}, + 138634: {}, + 138655: {}, + 138684: {}, + 138754: {}, + 138886: {}, + 138915: {}, + 138934: {}, + 138997: {}, + 139009: {}, + 139029: {}, + 139043: {}, + 139218: {}, + 139224: {}, + 139325: {}, + 139609: {}, + 139628: {}, + 139651: {}, + 139719: {}, 139759: {}, + 139766: {}, 139831: {}, + 139879: {}, 139898: {}, 139922: {}, + 139994: {}, + 140045: {}, + 140072: {}, + 140220: {}, + 140265: {}, + 140292: {}, + 140330: {}, + 140401: {}, + 140499: {}, 140504: {}, + 140594: {}, + 140608: {}, + 140900: {}, + 140903: {}, + 140966: {}, + 141024: {}, + 141031: {}, + 141047: {}, + 141145: {}, + 141216: {}, + 141342: {}, + 141432: {}, + 141680: {}, + 141681: {}, + 141691: {}, + 141711: {}, + 141767: {}, + 141778: {}, + 141983: {}, + 141995: {}, + 142065: {}, + 142300: {}, + 142352: {}, + 142580: {}, + 142647: {}, + 147045: {}, + 147049: {}, + 149024: {}, + 149173: {}, + 149419: {}, + 149994: {}, + 150107: {}, + 150153: {}, + 150683: {}, + 150692: {}, + 150750: {}, + 151080: {}, + 151396: {}, + 196735: {}, + 196838: {}, + 196874: {}, + 196925: {}, + 196961: {}, 197207: {}, + 197225: {}, + 197248: {}, + 197296: {}, + 197301: {}, + 197350: {}, + 197398: {}, + 197423: {}, + 197540: {}, + 197556: {}, + 197663: {}, + 197674: {}, + 197706: {}, + 197798: {}, 197830: {}, + 197882: {}, + 197889: {}, + 197897: {}, + 198068: {}, + 198161: {}, + 198252: {}, + 198265: {}, 198279: {}, - 198288: {}, + 198394: {}, + 198433: {}, + 198440: {}, + 198504: {}, + 198589: {}, 198605: {}, + 198631: {}, + 198668: {}, + 198735: {}, + 198820: {}, + 198961: {}, + 198966: {}, + 199046: {}, + 199081: {}, + 199128: {}, 199140: {}, 199276: {}, + 199469: {}, + 199490: {}, + 199493: {}, + 199524: {}, + 199565: {}, + 199620: {}, + 199636: {}, 199731: {}, + 199739: {}, + 199995: {}, + 200019: {}, + 200088: {}, 200134: {}, + 200154: {}, + 200446: {}, + 200590: {}, + 200612: {}, + 200640: {}, + 200683: {}, + 200697: {}, + 200724: {}, + 200736: {}, + 200740: {}, + 200845: {}, + 200865: {}, + 200899: {}, + 200923: {}, + 201000: {}, + 201011: {}, 201019: {}, + 201031: {}, + 201150: {}, 201167: {}, + 201205: {}, + 201241: {}, + 201249: {}, + 201322: {}, + 201411: {}, + 201540: {}, + 201596: {}, + 201603: {}, + 201746: {}, + 201749: {}, 201767: {}, + 201776: {}, + 201814: {}, + 201838: {}, 201884: {}, + 201890: {}, + 201967: {}, 201986: {}, + 201997: {}, + 202065: {}, + 202085: {}, 202087: {}, + 202098: {}, + 202103: {}, + 202204: {}, 202254: {}, + 202282: {}, + 202293: {}, + 202422: {}, + 202433: {}, + 202441: {}, + 202468: {}, + 202498: {}, + 202561: {}, + 202613: {}, + 202632: {}, + 202635: {}, + 202699: {}, + 202870: {}, + 202877: {}, + 202921: {}, + 202931: {}, + 202940: {}, + 202960: {}, + 202987: {}, 203020: {}, + 203136: {}, + 203206: {}, 203214: {}, + 203217: {}, + 203409: {}, + 203424: {}, + 203448: {}, + 203459: {}, + 203561: {}, + 203622: {}, + 203653: {}, + 203675: {}, + 203680: {}, + 203735: {}, + 203744: {}, + 203905: {}, + 203912: {}, + 203917: {}, 203953: {}, + 203971: {}, 203995: {}, 204106: {}, + 204108: {}, + 204151: {}, 204170: {}, + 204274: {}, + 204279: {}, 204317: {}, 204342: {}, + 204356: {}, + 204403: {}, + 204457: {}, + 204467: {}, + 204548: {}, + 204565: {}, + 204566: {}, + 204592: {}, + 204595: {}, + 204601: {}, 204649: {}, + 204666: {}, + 204716: {}, + 204793: {}, + 204802: {}, + 204804: {}, + 204873: {}, + 204918: {}, + 204957: {}, 205110: {}, + 205119: {}, + 205168: {}, + 205244: {}, + 205254: {}, + 205278: {}, + 205362: {}, + 205367: {}, 205368: {}, + 205371: {}, + 205400: {}, + 205473: {}, + 205544: {}, + 205547: {}, + 205638: {}, + 205647: {}, 205714: {}, + 205800: {}, + 205832: {}, + 205889: {}, 206026: {}, + 206065: {}, 206067: {}, + 206092: {}, + 206119: {}, 206206: {}, 206262: {}, + 206283: {}, + 206296: {}, + 206358: {}, + 206375: {}, + 206406: {}, + 206471: {}, + 206561: {}, + 206610: {}, + 206611: {}, + 206641: {}, + 206663: {}, + 206666: {}, + 206774: {}, + 206783: {}, + 206804: {}, + 206841: {}, + 206977: {}, + 207044: {}, + 207097: {}, + 207137: {}, + 207143: {}, + 207159: {}, + 207192: {}, + 207231: {}, + 207251: {}, + 207348: {}, + 207369: {}, + 207375: {}, + 207408: {}, + 207502: {}, + 207568: {}, + 207569: {}, + 207589: {}, + 207604: {}, 207651: {}, + 207713: {}, + 207728: {}, + 207782: {}, + 207790: {}, 207810: {}, 207876: {}, + 207980: {}, + 207990: {}, + 207991: {}, + 208115: {}, + 208126: {}, + 208149: {}, + 208286: {}, + 208320: {}, + 208339: {}, + 208448: {}, + 208555: {}, + 208592: {}, + 208668: {}, + 208708: {}, + 208730: {}, + 208734: {}, + 208785: {}, + 208847: {}, + 208859: {}, + 208905: {}, + 208909: {}, + 208972: {}, + 208997: {}, + 209046: {}, + 209049: {}, + 209193: {}, + 209196: {}, + 209240: {}, + 209262: {}, + 209277: {}, + 209360: {}, + 209375: {}, + 209424: {}, 209442: {}, + 209531: {}, + 209743: {}, + 209835: {}, + 209839: {}, 209854: {}, + 210003: {}, + 210021: {}, + 210079: {}, + 210080: {}, + 210095: {}, + 210116: {}, + 210125: {}, + 210218: {}, + 210278: {}, 210315: {}, + 210402: {}, 210542: {}, + 210625: {}, + 210644: {}, + 210808: {}, + 210974: {}, + 211028: {}, + 211057: {}, 211144: {}, + 211145: {}, + 211147: {}, + 211211: {}, + 211356: {}, + 211458: {}, + 211468: {}, + 211504: {}, + 211555: {}, + 211559: {}, + 211689: {}, + 211790: {}, + 211995: {}, + 212046: {}, + 212183: {}, 212238: {}, + 212330: {}, + 212370: {}, + 212449: {}, + 212531: {}, + 212572: {}, + 212637: {}, + 212645: {}, + 212655: {}, + 212661: {}, + 212865: {}, + 212898: {}, + 212910: {}, + 212974: {}, + 212986: {}, + 212999: {}, 213155: {}, + 213207: {}, + 213363: {}, + 213398: {}, + 213402: {}, 262145: {}, + 262146: {}, + 262159: {}, + 262179: {}, 262181: {}, 262186: {}, + 262187: {}, + 262191: {}, 262197: {}, 262202: {}, 262210: {}, + 262215: {}, + 262220: {}, + 262221: {}, + 262223: {}, + 262234: {}, 262239: {}, + 262241: {}, + 262253: {}, + 262262: {}, + 262354: {}, + 262468: {}, + 262481: {}, + 262493: {}, 262589: {}, + 262659: {}, + 262673: {}, + 262753: {}, + 262773: {}, + 262916: {}, + 262928: {}, + 262932: {}, + 263073: {}, + 263170: {}, + 263175: {}, + 263187: {}, + 263209: {}, + 263210: {}, + 263216: {}, + 263222: {}, + 263223: {}, + 263224: {}, 263238: {}, + 263242: {}, + 263245: {}, + 263686: {}, + 263689: {}, + 263694: {}, + 263698: {}, + 263699: {}, + 263702: {}, 263703: {}, + 263717: {}, 263725: {}, + 263732: {}, + 263749: {}, + 263750: {}, + 263751: {}, + 263759: {}, + 263761: {}, + 263762: {}, + 263763: {}, + 263765: {}, + 263767: {}, + 263781: {}, 263783: {}, + 263792: {}, + 263793: {}, + 263805: {}, + 263809: {}, 263824: {}, + 263980: {}, + 264605: {}, + 264609: {}, 264628: {}, + 264635: {}, + 264637: {}, + 264640: {}, + 264642: {}, 264645: {}, + 264646: {}, 264663: {}, 264668: {}, + 264685: {}, + 264694: {}, + 264696: {}, 264731: {}, + 264733: {}, + 264738: {}, + 264744: {}, + 264750: {}, + 264756: {}, + 264758: {}, + 264764: {}, + 264770: {}, + 264778: {}, + 264779: {}, + 264780: {}, + 264783: {}, + 264793: {}, + 264796: {}, + 264814: {}, + 264819: {}, + 264821: {}, + 264825: {}, + 264837: {}, + 264838: {}, + 264844: {}, + 264847: {}, + 265540: {}, + 265594: {}, + 265631: {}, + 265632: {}, + 265636: {}, + 265663: {}, + 265675: {}, + 265684: {}, + 265688: {}, + 265691: {}, + 265698: {}, + 265705: {}, + 265711: {}, + 265721: {}, + 265727: {}, + 265767: {}, + 265780: {}, + 265798: {}, + 265799: {}, + 265818: {}, + 265822: {}, + 265826: {}, + 265855: {}, + 265867: {}, + 265880: {}, + 266668: {}, + 266673: {}, + 266677: {}, + 266725: {}, + 266730: {}, + 266734: {}, + 266742: {}, + 266755: {}, + 266766: {}, + 266779: {}, + 266783: {}, + 266792: {}, + 266802: {}, + 266809: {}, + 266814: {}, + 266815: {}, + 266841: {}, + 266853: {}, + 266858: {}, + 266860: {}, + 266870: {}, + 266880: {}, + 266893: {}, + 266894: {}, + 266904: {}, + 267684: {}, + 267685: {}, + 267699: {}, + 267700: {}, + 267702: {}, + 267705: {}, + 267708: {}, + 267713: {}, + 267749: {}, + 267761: {}, + 267765: {}, + 267795: {}, + 267797: {}, + 267803: {}, + 267809: {}, + 267828: {}, + 267832: {}, + 267837: {}, + 267845: {}, + 267846: {}, + 267882: {}, + 267883: {}, + 268323: {}, + 268976: {}, + 269729: {}, + 269730: {}, + 269733: {}, + 269734: {}, + 269738: {}, + 269749: {}, + 269750: {}, + 269763: {}, + 269780: {}, + 269782: {}, + 269783: {}, + 269788: {}, + 269797: {}, + 269806: {}, + 269816: {}, + 269820: {}, + 269822: {}, + 269831: {}, + 269832: {}, + 269838: {}, + 269840: {}, + 269843: {}, + 269853: {}, + 269857: {}, + 269862: {}, + 269894: {}, + 269901: {}, + 269905: {}, + 269908: {}, + 269919: {}, + 269921: {}, + 269926: {}, + 269928: {}, + 269931: {}, + 269934: {}, + 269936: {}, + 269940: {}, + 269946: {}, + 269950: {}, + 269953: {}, + 269955: {}, + 269964: {}, + 269965: {}, + 269974: {}, + 269976: {}, + 269981: {}, + 269989: {}, + 270007: {}, + 270023: {}, + 270026: {}, + 270029: {}, + 270035: {}, + 270036: {}, + 270045: {}, + 270049: {}, + 270052: {}, + 270058: {}, + 270068: {}, + 270071: {}, + 270096: {}, + 270098: {}, + 270108: {}, + 270161: {}, + 270814: {}, + 271689: {}, 271773: {}, + 271791: {}, + 271795: {}, + 271806: {}, + 271808: {}, + 271812: {}, + 271814: {}, + 271819: {}, + 271822: {}, + 271835: {}, + 271837: {}, + 271855: {}, + 271868: {}, + 271874: {}, + 271880: {}, + 271898: {}, + 271899: {}, + 271907: {}, + 271909: {}, + 271911: {}, + 271929: {}, + 271930: {}, + 271932: {}, + 271935: {}, + 271965: {}, + 271971: {}, + 271988: {}, + 271996: {}, + 272006: {}, + 272011: {}, + 272015: {}, + 272018: {}, + 272019: {}, + 272026: {}, + 272055: {}, + 272062: {}, + 272073: {}, + 272075: {}, + 272083: {}, + 272102: {}, + 272106: {}, + 272110: {}, + 272112: {}, + 272122: {}, + 272134: {}, + 272809: {}, + 272818: {}, + 272836: {}, + 272848: {}, + 272851: {}, + 272882: {}, + 272883: {}, + 272954: {}, + 327687: {}, + 327693: {}, 327697: {}, + 327700: {}, 327707: {}, 327712: {}, + 327714: {}, + 327716: {}, + 327724: {}, + 327725: {}, 327738: {}, + 327742: {}, + 327747: {}, + 327750: {}, + 327752: {}, 327756: {}, + 327760: {}, 327765: {}, + 327768: {}, 327769: {}, + 327770: {}, 327776: {}, + 327782: {}, + 327786: {}, + 327794: {}, + 327795: {}, + 327798: {}, 327799: {}, 327802: {}, + 327804: {}, + 327809: {}, + 327814: {}, + 327819: {}, + 327820: {}, + 327828: {}, + 327829: {}, + 327830: {}, + 327849: {}, + 327864: {}, + 327871: {}, + 327872: {}, 327885: {}, + 327900: {}, + 327901: {}, 327903: {}, 327931: {}, + 327932: {}, 327934: {}, + 327947: {}, + 327972: {}, + 327975: {}, + 327990: {}, + 327991: {}, + 327996: {}, + 328015: {}, 328061: {}, + 328073: {}, + 328075: {}, 328079: {}, 328088: {}, + 328111: {}, + 328114: {}, + 328118: {}, + 328136: {}, 328140: {}, + 328144: {}, + 328154: {}, 328169: {}, + 328182: {}, 328191: {}, + 328196: {}, + 328198: {}, 328200: {}, + 328207: {}, + 328223: {}, 328228: {}, + 328244: {}, 328250: {}, + 328253: {}, + 328258: {}, + 328271: {}, + 328284: {}, 328286: {}, 328297: {}, + 328304: {}, 328309: {}, + 328319: {}, + 328331: {}, + 328341: {}, + 328344: {}, + 328411: {}, + 328429: {}, + 328442: {}, 328453: {}, 328469: {}, + 328471: {}, + 328475: {}, + 328479: {}, + 328480: {}, 328488: {}, + 328490: {}, + 328491: {}, + 328494: {}, + 328509: {}, + 328510: {}, + 328535: {}, 328539: {}, + 328546: {}, + 328549: {}, + 328566: {}, + 328567: {}, + 328570: {}, + 328578: {}, + 328581: {}, + 328586: {}, + 328590: {}, + 328594: {}, + 328600: {}, 328605: {}, + 328610: {}, + 328611: {}, + 328619: {}, + 328638: {}, + 328652: {}, + 328676: {}, + 328697: {}, 328708: {}, + 328717: {}, 328727: {}, + 328734: {}, + 328753: {}, 328755: {}, + 328770: {}, + 328785: {}, + 328817: {}, + 328844: {}, + 328856: {}, + 328857: {}, + 328858: {}, + 328880: {}, + 328887: {}, + 328892: {}, + 328899: {}, + 328919: {}, + 328939: {}, 328943: {}, + 328954: {}, + 328959: {}, + 328961: {}, + 328965: {}, + 328975: {}, + 328977: {}, + 328983: {}, + 328987: {}, + 328988: {}, + 328993: {}, + 328997: {}, + 329014: {}, + 329021: {}, + 329027: {}, + 329029: {}, + 329044: {}, + 329048: {}, + 329074: {}, + 329078: {}, + 329082: {}, + 329094: {}, + 329101: {}, + 329103: {}, + 329129: {}, + 329135: {}, + 329155: {}, + 329167: {}, + 329183: {}, + 329211: {}, + 329255: {}, + 329288: {}, + 393629: {}, + 393678: {}, + 393682: {}, 394311: {}, + 394381: {}, 395561: {}, + 395570: {}, + 395965: {}, + 396082: {}, 396304: {}, + 396356: {}, 396357: {}, + 396982: {}, + 397399: {}, + 397545: {}, + 397961: {}, 398228: {}, + 398721: {}, + 398901: {}, + 399382: {}, + 399686: {}, 399724: {}, + 400266: {}, + 400354: {}, + 400618: {}, } // DefaultCountryTopASNs is a mapping of a country to their top ASNs. var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryAD: 6752, agd.CountryAE: 5384, - agd.CountryAF: 132471, + agd.CountryAF: 55330, agd.CountryAG: 11594, agd.CountryAI: 11139, - agd.CountryAL: 21183, + agd.CountryAL: 50616, agd.CountryAM: 12297, agd.CountryAO: 37119, agd.CountryAR: 7303, @@ -929,32 +3264,32 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryBE: 5432, agd.CountryBF: 37577, agd.CountryBG: 8866, - agd.CountryBH: 5416, + agd.CountryBH: 51375, agd.CountryBI: 327799, - agd.CountryBJ: 37424, - agd.CountryBL: 14593, - agd.CountryBM: 3855, + agd.CountryBJ: 37136, + agd.CountryBM: 32020, agd.CountryBN: 10094, agd.CountryBO: 6568, - agd.CountryBQ: 27745, - agd.CountryBR: 26599, + agd.CountryBQ: 27694, + agd.CountryBR: 28573, agd.CountryBS: 15146, agd.CountryBT: 18024, agd.CountryBW: 14988, agd.CountryBY: 25106, agd.CountryBZ: 10269, agd.CountryCA: 812, + agd.CountryCC: 198605, agd.CountryCD: 37020, - agd.CountryCF: 37460, + agd.CountryCF: 328079, agd.CountryCG: 36924, agd.CountryCH: 3303, agd.CountryCI: 29571, agd.CountryCK: 10131, - agd.CountryCL: 27651, + agd.CountryCL: 7418, agd.CountryCM: 30992, agd.CountryCN: 4134, - agd.CountryCO: 27831, - agd.CountryCR: 11830, + agd.CountryCO: 10620, + agd.CountryCR: 52263, agd.CountryCU: 27725, agd.CountryCV: 37517, agd.CountryCW: 52233, @@ -962,14 +3297,13 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryCZ: 5610, agd.CountryDE: 3320, agd.CountryDJ: 30990, - agd.CountryDK: 9009, + agd.CountryDK: 3292, agd.CountryDM: 40945, agd.CountryDO: 6400, agd.CountryDZ: 36947, agd.CountryEC: 27947, agd.CountryEE: 44477, agd.CountryEG: 8452, - agd.CountryEH: 36947, agd.CountryER: 24757, agd.CountryES: 3352, agd.CountryET: 24757, @@ -979,7 +3313,7 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryFM: 139759, agd.CountryFO: 15389, agd.CountryFR: 3215, - agd.CountryGA: 36924, + agd.CountryGA: 16058, agd.CountryGB: 2856, agd.CountryGD: 46650, agd.CountryGE: 16010, @@ -991,37 +3325,37 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryGM: 37309, agd.CountryGN: 37461, agd.CountryGP: 3215, - agd.CountryGQ: 37529, + agd.CountryGQ: 37337, agd.CountryGR: 6799, agd.CountryGT: 14754, - agd.CountryGU: 9246, + agd.CountryGU: 7131, agd.CountryGW: 37559, agd.CountryGY: 19863, - agd.CountryHK: 4760, + agd.CountryHK: 45102, agd.CountryHN: 14754, agd.CountryHR: 5391, agd.CountryHT: 27653, agd.CountryHU: 5483, agd.CountryID: 7713, - agd.CountryIE: 16509, + agd.CountryIE: 15502, agd.CountryIL: 1680, agd.CountryIM: 13122, agd.CountryIN: 55836, agd.CountryIO: 17458, agd.CountryIQ: 203214, - agd.CountryIR: 44244, - agd.CountryIS: 44735, + agd.CountryIR: 197207, + agd.CountryIS: 44925, agd.CountryIT: 1267, agd.CountryJE: 8680, agd.CountryJM: 30689, - agd.CountryJO: 48832, + agd.CountryJO: 8376, agd.CountryJP: 2516, agd.CountryKE: 33771, - agd.CountryKG: 50223, + agd.CountryKG: 47237, agd.CountryKH: 38623, - agd.CountryKI: 135409, + agd.CountryKI: 134783, agd.CountryKM: 36939, - agd.CountryKN: 11139, + agd.CountryKN: 36290, agd.CountryKR: 4766, agd.CountryKW: 29357, agd.CountryKY: 6639, @@ -1029,7 +3363,7 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryLA: 9873, agd.CountryLB: 42003, agd.CountryLC: 15344, - agd.CountryLI: 20634, + agd.CountryLI: 9009, agd.CountryLK: 18001, agd.CountryLR: 37094, agd.CountryLS: 33567, @@ -1063,16 +3397,17 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryNA: 36996, agd.CountryNC: 18200, agd.CountryNE: 37531, + agd.CountryNF: 198605, agd.CountryNG: 29465, agd.CountryNI: 14754, agd.CountryNL: 1136, agd.CountryNO: 2119, agd.CountryNP: 17501, - agd.CountryNR: 198605, + agd.CountryNR: 140504, agd.CountryNU: 198605, agd.CountryNZ: 9790, agd.CountryOM: 28885, - agd.CountryPA: 11556, + agd.CountryPA: 18809, agd.CountryPE: 12252, agd.CountryPF: 9471, agd.CountryPG: 139898, @@ -1099,36 +3434,37 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountrySG: 4773, agd.CountrySH: 33763, agd.CountrySI: 5603, - agd.CountrySJ: 29695, agd.CountrySK: 6855, agd.CountrySL: 37164, agd.CountrySM: 15433, agd.CountrySN: 8346, agd.CountrySO: 37371, agd.CountrySR: 27775, - agd.CountrySS: 37594, + agd.CountrySS: 328755, agd.CountryST: 328191, agd.CountrySV: 14754, - agd.CountrySX: 27781, + agd.CountrySX: 27734, agd.CountrySY: 29256, agd.CountrySZ: 328169, - agd.CountryTC: 394311, + agd.CountryTC: 22933, agd.CountryTD: 327802, + agd.CountryTF: 52000, agd.CountryTG: 36924, agd.CountryTH: 131445, agd.CountryTJ: 43197, - agd.CountryTL: 58731, - agd.CountryTM: 51495, + agd.CountryTK: 4648, + agd.CountryTL: 133606, + agd.CountryTM: 198605, agd.CountryTN: 37705, agd.CountryTO: 38201, agd.CountryTR: 47331, agd.CountryTT: 27800, - agd.CountryTV: 198605, + agd.CountryTV: 23917, agd.CountryTW: 3462, agd.CountryTZ: 36908, agd.CountryUA: 15895, agd.CountryUG: 37075, - agd.CountryUS: 21928, + agd.CountryUS: 7922, agd.CountryUY: 6057, agd.CountryUZ: 8193, agd.CountryVC: 46408, @@ -1136,12 +3472,12 @@ var DefaultCountryTopASNs = map[agd.Country]agd.ASN{ agd.CountryVG: 396357, agd.CountryVI: 14434, agd.CountryVN: 7552, - agd.CountryVU: 9249, + agd.CountryVU: 45355, agd.CountryWF: 45879, agd.CountryWS: 17993, agd.CountryXK: 21246, agd.CountryYE: 30873, - agd.CountryYT: 49902, + agd.CountryYT: 3215, agd.CountryZA: 37457, agd.CountryZM: 37287, agd.CountryZW: 37204, diff --git a/internal/geoip/file_test.go b/internal/geoip/file_test.go index ee8b234..fe0c677 100644 --- a/internal/geoip/file_test.go +++ b/internal/geoip/file_test.go @@ -222,7 +222,6 @@ func BenchmarkNewFile(b *testing.B) { b.ReportAllocs() b.ResetTimer() - for i := 0; i < b.N; i++ { fileSink, errSink = geoip.NewFile(conf) } diff --git a/internal/geoip/filescanner.go b/internal/geoip/filescanner.go index 5824d60..219837a 100644 --- a/internal/geoip/filescanner.go +++ b/internal/geoip/filescanner.go @@ -154,10 +154,6 @@ 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/metrics/access.go b/internal/metrics/access.go new file mode 100644 index 0000000..d3438e0 --- /dev/null +++ b/internal/metrics/access.go @@ -0,0 +1,26 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +// AccessBlockedForSubnetTotal is a counter with the total count of request +// blocked for client's subnet by access manager. +// +// TODO(d.kolyshev): Consider adding rule label. +var AccessBlockedForSubnetTotal = promauto.NewCounter(prometheus.CounterOpts{ + Name: "blocked_subnet_total", + Namespace: namespace, + Subsystem: subsystemAccess, + Help: "Total count of blocked subnet requests.", +}) + +// AccessBlockedForHostTotal is a counter with the total count of request +// blocked for request's host by access manager. +var AccessBlockedForHostTotal = promauto.NewCounter(prometheus.CounterOpts{ + Name: "blocked_host_total", + Namespace: namespace, + Subsystem: subsystemAccess, + Help: "Total count of blocked host requests.", +}) diff --git a/internal/metrics/backend.go b/internal/metrics/backend.go index 87761f4..90a3e73 100644 --- a/internal/metrics/backend.go +++ b/internal/metrics/backend.go @@ -69,3 +69,34 @@ var ProfilesSyncDuration = promauto.NewHistogram(prometheus.HistogramOpts{ // massive. This is why the buckets go up to 240 seconds. Buckets: []float64{0.01, 0.1, 1, 5, 10, 30, 60, 120, 240}, }) + +// ProfilesFullSyncDuration is a gauge with the duration of the last full sync. +// It is a gauge because full syncs are not expected to be common. +var ProfilesFullSyncDuration = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "profiles_full_sync_duration_seconds", + Subsystem: subsystemBackend, + Namespace: namespace, + Help: "Time elapsed on fully syncing user profiles with the backend, in seconds.", +}) + +// GRPCAvgProfileRecvDuration is a histogram with the average duration of a +// receive of a single profile during a backend call. +var GRPCAvgProfileRecvDuration = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "grpc_avg_profile_recv_duration_seconds", + Subsystem: subsystemBackend, + Namespace: namespace, + Help: "The average duration of a receive of a profile during a call to the backend, " + + "in seconds.", + Buckets: []float64{0.000_001, 0.000_010, 0.000_100, 0.001}, +}) + +// GRPCAvgProfileDecDuration is a histogram with the average duration of +// decoding a single profile during a backend call. +var GRPCAvgProfileDecDuration = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "grpc_avg_profile_dec_duration_seconds", + Subsystem: subsystemBackend, + Namespace: namespace, + Help: "The average duration of decoding one profile during a call to the backend, " + + "in seconds.", + Buckets: []float64{0.000_001, 0.000_010, 0.000_100, 0.001}, +}) diff --git a/internal/metrics/billstat.go b/internal/metrics/billstat.go index 1e031e7..05d9b16 100644 --- a/internal/metrics/billstat.go +++ b/internal/metrics/billstat.go @@ -32,4 +32,14 @@ var ( Subsystem: subsystemBillStat, Help: "Time when the billing statistics were uploaded last time.", }) + + // BillStatUploadDuration is a histogram with the duration of the billing + // statistics upload. + BillStatUploadDuration = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "bill_stat_upload_duration", + Namespace: namespace, + Subsystem: subsystemBillStat, + Help: "Time elapsed on uploading billing statistics to the backend.", + Buckets: []float64{0.001, 0.01, 0.1, 1, 5, 10, 30, 60, 120}, + }) ) diff --git a/internal/metrics/bindtodevice.go b/internal/metrics/bindtodevice.go new file mode 100644 index 0000000..137c994 --- /dev/null +++ b/internal/metrics/bindtodevice.go @@ -0,0 +1,59 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + bindToDeviceUnknownRequestsTotal = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "unknown_requests_total", + Namespace: namespace, + Subsystem: subsystemBindToDevice, + Help: "The total number of DNS requests to unknown local addresses.", + }, + []string{"proto"}, + ) + + // BindToDeviceUnknownTCPRequestsTotal is the total counter of DNS requests + // over TCP to unknown local addresses. + BindToDeviceUnknownTCPRequestsTotal = bindToDeviceUnknownRequestsTotal.With(prometheus.Labels{ + "proto": "tcp", + }) + + // BindToDeviceUnknownUDPRequestsTotal is the total counter of DNS requests + // over UDP to unknown local addresses. + BindToDeviceUnknownUDPRequestsTotal = bindToDeviceUnknownRequestsTotal.With(prometheus.Labels{ + "proto": "udp", + }) +) + +var ( + // BindToDeviceTCPConnsChanSize is a gauge with the current number of TCP + // connections in the buffer of the channel by each subnet. + BindToDeviceTCPConnsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "tcp_conns_chan_size", + Namespace: namespace, + Subsystem: subsystemBindToDevice, + Help: "The current number of TCP connections in the channel.", + }, []string{"subnet"}) + + // BindToDeviceUDPSessionsChanSize is a gauge with the current number of UDP + // sessions in the buffer of the channel by each subnet. + BindToDeviceUDPSessionsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "udp_sessions_chan_size", + Namespace: namespace, + Subsystem: subsystemBindToDevice, + Help: "The current number of UDP sessions in the channel.", + }, []string{"subnet"}) + + // BindToDeviceUDPWriteRequestsChanSize is a gauge with the current number + // of UDP write requests in the buffer of the channel by each subnet. + BindToDeviceUDPWriteRequestsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "udp_write_requests_chan_size", + Namespace: namespace, + Subsystem: subsystemBindToDevice, + Help: "The current number of UDP write requests in the channel.", + }, []string{"subnet"}) +) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 012d721..e49a00a 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -14,22 +14,24 @@ import ( const ( namespace = "dns" - subsystemApplication = "app" - subsystemBackend = "backend" - subsystemBillStat = "billstat" - subsystemConnLimiter = "connlimiter" - subsystemConsul = "consul" - subsystemDNSCheck = "dnscheck" - subsystemDNSDB = "dnsdb" - subsystemDNSSvc = "dnssvc" - subsystemECSCache = "ecscache" - subsystemFilter = "filter" - subsystemGeoIP = "geoip" - subsystemQueryLog = "querylog" - subsystemRuleStat = "rulestat" - subsystemTLS = "tls" - subsystemResearch = "research" - subsystemWebSvc = "websvc" + subsystemAccess = "access" + subsystemApplication = "app" + subsystemBackend = "backend" + subsystemBillStat = "billstat" + subsystemBindToDevice = "bindtodevice" + subsystemConnLimiter = "connlimiter" + subsystemConsul = "consul" + subsystemDNSCheck = "dnscheck" + subsystemDNSDB = "dnsdb" + subsystemDNSSvc = "dnssvc" + subsystemECSCache = "ecscache" + subsystemFilter = "filter" + subsystemGeoIP = "geoip" + subsystemQueryLog = "querylog" + subsystemRuleStat = "rulestat" + subsystemTLS = "tls" + subsystemResearch = "research" + subsystemWebSvc = "websvc" ) // SetUpGauge signals that the server has been started. Use a function here to diff --git a/internal/metrics/tls.go b/internal/metrics/tls.go index 0f6baa7..26bc018 100644 --- a/internal/metrics/tls.go +++ b/internal/metrics/tls.go @@ -160,8 +160,7 @@ func serverNameToLabel( srvCerts []tls.Certificate, ) (label string) { if sni == "" { - // SNI is not provided, so the request is probably made on the IP - // address. + // SNI is empty, so the request is probably made on the IP address. return fmt.Sprintf("%s: other", srvName) } diff --git a/internal/metrics/usercount.go b/internal/metrics/usercount.go index aec6488..079f0b2 100644 --- a/internal/metrics/usercount.go +++ b/internal/metrics/usercount.go @@ -188,19 +188,19 @@ func mustMerge(a, b *hyperloglog.Sketch) { } } -// hyperloglogConfig is a serialized [hyperLogLog.Sketch] with prescision 18 and +// hyperloglogConfig is a serialized [hyperLogLog.Sketch] with precision 18 and // sparse mode enabled. var hyperloglogConfig = [20]byte{ // Version. 0: 0x1, - // Prescision. + // Precision. 1: 18, // Sparse. 3: 0x1, } -// newHyperLogLog creates a new instance of hyperloglog.Sketch with prescision -// 18 and sparse mode enabled. +// newHyperLogLog creates a new instance of hyperloglog.Sketch with precision 18 +// and sparse mode enabled. func newHyperLogLog() (sk *hyperloglog.Sketch) { sk = &hyperloglog.Sketch{} err := sk.UnmarshalBinary(hyperloglogConfig[:]) diff --git a/internal/metrics/usercount_internal_test.go b/internal/metrics/usercount_internal_test.go index 4642269..230370e 100644 --- a/internal/metrics/usercount_internal_test.go +++ b/internal/metrics/usercount_internal_test.go @@ -228,18 +228,16 @@ func BenchmarkUserCounter_Estimate(b *testing.B) { } b.Run("sparse", func(b *testing.B) { - b.ResetTimer() b.ReportAllocs() - + b.ResetTimer() for i := 0; i < b.N; i++ { uint64Sink, uint64Sink = sparseCounter.estimate() } }) b.Run("sequential", func(b *testing.B) { - b.ResetTimer() b.ReportAllocs() - + b.ResetTimer() 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 deleted file mode 100644 index c647a5a..0000000 --- a/internal/profiledb/internal/filecachejson/filecachejson.go +++ /dev/null @@ -1,126 +0,0 @@ -// Package filecachejson contains an implementation of the file-cache storage -// that encodes data using JSON. -package filecachejson - -import ( - "encoding/json" - "fmt" - "os" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" - "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" - renameio "github.com/google/renameio/v2" -) - -// Storage is the file-cache storage that encodes data using JSON. -type Storage struct { - path string -} - -// New returns a new JSON-encoded file-cache storage. -func New(cachePath string) (s *Storage) { - return &Storage{ - path: cachePath, - } -} - -// fileCache is the structure for the JSON filesystem cache of a profile -// database. -// -// NOTE: Do not change fields of this structure without incrementing -// [internal.FileCacheVersion]. -type fileCache struct { - SyncTime time.Time `json:"sync_time"` - Profiles []*agd.Profile `json:"profiles"` - Devices []*agd.Device `json:"devices"` - Version int32 `json:"version"` -} - -// logPrefix is the logging prefix for the JSON-encoded file-cache. -const logPrefix = "profiledb json cache" - -var _ internal.FileCacheStorage = (*Storage)(nil) - -// Load implements the [internal.FileCacheStorage] interface for *Storage. -func (s *Storage) Load() (c *internal.FileCache, err error) { - log.Info("%s: loading", logPrefix) - - data, err := s.loadFromFile() - if err != nil { - return nil, fmt.Errorf("loading from file: %w", err) - } - - if data == nil { - log.Info("%s: file not present", logPrefix) - - 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, - Devices: data.Devices, - Version: data.Version, - }, nil -} - -// loadFromFile loads the profile data from cache file. -func (s *Storage) loadFromFile() (data *fileCache, err error) { - file, err := os.Open(s.path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // File could be deleted or not yet created, go on. - return nil, nil - } - - return nil, err - } - defer func() { err = errors.WithDeferred(err, file.Close()) }() - - data = &fileCache{} - err = json.NewDecoder(file).Decode(data) - if err != nil { - return nil, fmt.Errorf("decoding json: %w", err) - } - - return data, nil -} - -// Store implements the [internal.FileCacheStorage] interface for *Storage. -func (s *Storage) Store(c *internal.FileCache) (err error) { - profNum := len(c.Profiles) - log.Info("%s: saving %d profiles to %q", logPrefix, profNum, s.path) - defer log.Info("%s: saved %d profiles to %q", logPrefix, profNum, s.path) - - data := &fileCache{ - SyncTime: c.SyncTime, - Profiles: c.Profiles, - Devices: c.Devices, - Version: c.Version, - } - - cache, err := json.Marshal(data) - if err != nil { - return fmt.Errorf("encoding json: %w", err) - } - - err = renameio.WriteFile(s.path, cache, 0o600) - if err != nil { - // Don't wrap the error, because it's informative enough as is. - return err - } - - return nil -} diff --git a/internal/profiledb/internal/filecachejson/filecachejson_test.go b/internal/profiledb/internal/filecachejson/filecachejson_test.go deleted file mode 100644 index 7c017f1..0000000 --- a/internal/profiledb/internal/filecachejson/filecachejson_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package filecachejson_test - -import ( - "path/filepath" - "testing" - "time" - - "github.com/AdguardTeam/AdGuardDNS/internal/agd" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachejson" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/profiledbtest" - "github.com/AdguardTeam/golibs/testutil" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMain(m *testing.M) { - testutil.DiscardLogOutput(m) -} - -func TestStorage(t *testing.T) { - prof, dev := profiledbtest.NewProfile(t) - cachePath := filepath.Join(t.TempDir(), "profiles.json") - s := filecachejson.New(cachePath) - require.NotNil(t, s) - - fc := &internal.FileCache{ - SyncTime: time.Now().Round(0).UTC(), - Profiles: []*agd.Profile{prof}, - Devices: []*agd.Device{dev}, - Version: internal.FileCacheVersion, - } - - err := s.Store(fc) - require.NoError(t, err) - - gotFC, err := s.Load() - require.NoError(t, err) - require.NotNil(t, gotFC) - require.NotEmpty(t, *gotFC) - - assert.Equal(t, fc, gotFC) -} - -func TestStorage_Load_noFile(t *testing.T) { - cachePath := filepath.Join(t.TempDir(), "profiles.json") - s := filecachejson.New(cachePath) - require.NotNil(t, s) - - fc, err := s.Load() - assert.NoError(t, err) - assert.Nil(t, fc) -} diff --git a/internal/profiledb/internal/filecachepb/filecache.pb.go b/internal/profiledb/internal/filecachepb/filecache.pb.go index bb202ea..72521ee 100644 --- a/internal/profiledb/internal/filecachepb/filecache.pb.go +++ b/internal/profiledb/internal/filecachepb/filecache.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v4.23.4 // source: filecache.proto diff --git a/internal/profiledb/internal/filecachepb/filecachepb.go b/internal/profiledb/internal/filecachepb/filecachepb.go index aaafcd2..2a49747 100644 --- a/internal/profiledb/internal/filecachepb/filecachepb.go +++ b/internal/profiledb/internal/filecachepb/filecachepb.go @@ -7,6 +7,7 @@ import ( "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf" "github.com/AdguardTeam/AdGuardDNS/internal/agdtime" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" @@ -82,7 +83,9 @@ func (x *Profile) toInternal() (prof *agd.Profile, err error) { // Consider rule-list IDs to have been prevalidated. RuleListIDs: unsafelyConvertStrSlice[string, agd.FilterListID](x.RuleListIds), // Consider rule-list IDs to have been prevalidated. - CustomRules: unsafelyConvertStrSlice[string, agd.FilterRuleText](x.CustomRules), + CustomRules: unsafelyConvertStrSlice[string, agd.FilterRuleText]( + x.CustomRules, + ), FilteredResponseTTL: x.FilteredResponseTtl.AsDuration(), FilteringEnabled: x.FilteringEnabled, SafeBrowsing: x.SafeBrowsing.toInternal(), @@ -204,7 +207,7 @@ func (x *Device) toInternal() (d *agd.Device, err error) { } var dedicatedIPs []netip.Addr - dedicatedIPs, err = byteSlicesToIPs(x.DedicatedIps) + dedicatedIPs, err = agdprotobuf.ByteSlicesToIPs(x.DedicatedIps) if err != nil { return nil, fmt.Errorf("dedicated ips: %w", err) } @@ -220,26 +223,6 @@ func (x *Device) toInternal() (d *agd.Device, err error) { }, nil } -// byteSlicesToIPs converts a slice of byte slices into a slice of netip.Addrs. -func byteSlicesToIPs(data [][]byte) (ips []netip.Addr, err error) { - if data == nil { - return nil, nil - } - - ips = make([]netip.Addr, 0, len(data)) - for i, ipData := range data { - var ip netip.Addr - err = ip.UnmarshalBinary(ipData) - if err != nil { - return nil, fmt.Errorf("ip at index %d: %w", i, err) - } - - ips = append(ips, ip) - } - - return ips, nil -} - // toInternal converts a protobuf safe browsing settings structure to an // internal one. func (x *SafeBrowsingSettings) toInternal() (s *agd.SafeBrowsingSettings) { @@ -259,13 +242,17 @@ func profilesToProtobuf(profiles []*agd.Profile) (pbProfiles []*Profile) { pbProfiles = make([]*Profile, 0, len(profiles)) for _, p := range profiles { pbProfiles = append(pbProfiles, &Profile{ - Parental: parentalToProtobuf(p.Parental), - BlockingMode: blockingModeToProtobuf(p.BlockingMode), - ProfileId: string(p.ID), - UpdateTime: timestamppb.New(p.UpdateTime), - DeviceIds: unsafelyConvertStrSlice[agd.DeviceID, string](p.DeviceIDs), - RuleListIds: unsafelyConvertStrSlice[agd.FilterListID, string](p.RuleListIDs), - CustomRules: unsafelyConvertStrSlice[agd.FilterRuleText, string](p.CustomRules), + Parental: parentalToProtobuf(p.Parental), + BlockingMode: blockingModeToProtobuf(p.BlockingMode), + ProfileId: string(p.ID), + UpdateTime: timestamppb.New(p.UpdateTime), + DeviceIds: unsafelyConvertStrSlice[agd.DeviceID, string](p.DeviceIDs), + RuleListIds: unsafelyConvertStrSlice[agd.FilterListID, string]( + p.RuleListIDs, + ), + CustomRules: unsafelyConvertStrSlice[agd.FilterRuleText, string]( + p.CustomRules, + ), FilteredResponseTtl: durationpb.New(p.FilteredResponseTTL), FilteringEnabled: p.FilteringEnabled, SafeBrowsing: safeBrowsingToProtobuf(p.SafeBrowsing), @@ -287,8 +274,10 @@ func parentalToProtobuf(s *agd.ParentalProtectionSettings) (pbSetts *ParentalPro } return &ParentalProtectionSettings{ - Schedule: scheduleToProtobuf(s.Schedule), - BlockedServices: unsafelyConvertStrSlice[agd.BlockedServiceID, string](s.BlockedServices), + Schedule: scheduleToProtobuf(s.Schedule), + BlockedServices: unsafelyConvertStrSlice[agd.BlockedServiceID, string]( + s.BlockedServices, + ), Enabled: s.Enabled, BlockAdult: s.BlockAdult, GeneralSafeSearch: s.GeneralSafeSearch, diff --git a/internal/profiledb/internal/profiledbtest/profiledbtest.go b/internal/profiledb/internal/profiledbtest/profiledbtest.go index cc9cb45..deab918 100644 --- a/internal/profiledb/internal/profiledbtest/profiledbtest.go +++ b/internal/profiledb/internal/profiledbtest/profiledbtest.go @@ -13,18 +13,12 @@ import ( ) // ProfileID is the profile ID for tests. -// -// Keep in sync with internal/profiledb/testdata/profiles.json. const ProfileID agd.ProfileID = "prof1234" // DeviceID is the profile ID for tests. -// -// Keep in sync with internal/profiledb/testdata/profiles.json. const DeviceID agd.DeviceID = "dev1234" // NewProfile returns the common profile and device for tests. -// -// Keep in sync with internal/profiledb/testdata/profiles.json. func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) { tb.Helper() diff --git a/internal/profiledb/profiledb.go b/internal/profiledb/profiledb.go index 43b5b38..2fb1bf1 100644 --- a/internal/profiledb/profiledb.go +++ b/internal/profiledb/profiledb.go @@ -12,7 +12,6 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" - "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachejson" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" @@ -98,15 +97,10 @@ func New( var cacheStorage internal.FileCacheStorage if cacheFilePath == "none" { cacheStorage = internal.EmptyFileCacheStorage{} + } else if ext := filepath.Ext(cacheFilePath); ext == ".pb" { + cacheStorage = filecachepb.New(cacheFilePath) } else { - switch ext := filepath.Ext(cacheFilePath); ext { - case ".json": - cacheStorage = filecachejson.New(cacheFilePath) - case ".pb": - cacheStorage = filecachepb.New(cacheFilePath) - default: - return nil, fmt.Errorf("file %q is neither json nor protobuf", cacheFilePath) - } + return nil, fmt.Errorf("file %q is not protobuf", cacheFilePath) } db = &Default{ @@ -161,14 +155,22 @@ var _ agd.Refresher = (*Default)(nil) // internal maps and the synchronization time using the data it receives from // the storage. func (db *Default) Refresh(ctx context.Context) (err error) { + sinceLastFullSync := time.Since(db.lastFullSync) + isFullSync := sinceLastFullSync >= db.fullSyncIvl + var totalProfiles, totalDevices int startTime := time.Now() defer func() { metrics.ProfilesSyncTime.SetToCurrentTime() - metrics.ProfilesSyncDuration.Observe(time.Since(startTime).Seconds()) metrics.ProfilesCountGauge.Set(float64(totalProfiles)) metrics.DevicesCountGauge.Set(float64(totalDevices)) metrics.SetStatusGauge(metrics.ProfilesSyncStatus, err) + + dur := time.Since(startTime).Seconds() + metrics.ProfilesSyncDuration.Observe(dur) + if isFullSync { + metrics.ProfilesFullSyncDuration.Set(dur) + } }() reqID := agd.NewRequestID() @@ -180,9 +182,6 @@ func (db *Default) Refresh(ctx context.Context) (err error) { defer db.refreshMu.Unlock() syncTime := db.syncTime - - sinceLastFullSync := time.Since(db.lastFullSync) - isFullSync := sinceLastFullSync >= db.fullSyncIvl if isFullSync { log.Info("profiledb: full sync, %s since %s", sinceLastFullSync, db.lastFullSync) diff --git a/internal/profiledb/profiledb_test.go b/internal/profiledb/profiledb_test.go index a9345b2..af78807 100644 --- a/internal/profiledb/profiledb_test.go +++ b/internal/profiledb/profiledb_test.go @@ -1,10 +1,8 @@ package profiledb_test import ( - "bytes" "context" "net/netip" - "os" "path/filepath" "testing" "time" @@ -13,6 +11,8 @@ import ( "github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal" + "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb" "github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/profiledbtest" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/testutil" @@ -25,8 +25,6 @@ func TestMain(m *testing.M) { } // Common IPs for tests -// -// Keep in sync with testdata/profiles.json. var ( testClientIPv4 = netip.MustParseAddr("1.2.3.4") testOtherClientIPv4 = netip.MustParseAddr("1.2.3.5") @@ -290,20 +288,19 @@ func TestDefaultProfileDB_fileCache_success(t *testing.T) { OnProfiles: onProfiles, } - cacheFileTmplPath := filepath.Join("testdata", "profiles.json") - data, err := os.ReadFile(cacheFileTmplPath) - require.NoError(t, err) - // Use the time with monotonic clocks stripped down. wantSyncTime := time.Now().Round(0).UTC() - data = bytes.ReplaceAll( - data, - []byte("SYNC_TIME"), - []byte(wantSyncTime.Format(time.RFC3339Nano)), - ) - cacheFilePath := filepath.Join(t.TempDir(), "profiles.json") - err = os.WriteFile(cacheFilePath, data, 0o600) + prof, dev := profiledbtest.NewProfile(t) + + cacheFilePath := filepath.Join(t.TempDir(), "profiles.pb") + pbCache := filecachepb.New(cacheFilePath) + err := pbCache.Store(&internal.FileCache{ + SyncTime: wantSyncTime, + Profiles: []*agd.Profile{prof}, + Devices: []*agd.Device{dev}, + Version: internal.FileCacheVersion, + }) require.NoError(t, err) db, err := profiledb.New(ps, 1*time.Minute, cacheFilePath) @@ -312,10 +309,8 @@ func TestDefaultProfileDB_fileCache_success(t *testing.T) { assert.Equal(t, wantSyncTime, gotSyncTime) - prof, dev := profiledbtest.NewProfile(t) p, d, err := db.ProfileByDeviceID(context.Background(), dev.ID) require.NoError(t, err) - assert.Equal(t, dev, d) assert.Equal(t, prof, p) } @@ -333,8 +328,11 @@ func TestDefaultProfileDB_fileCache_badVersion(t *testing.T) { }, } - cacheFilePath := filepath.Join(t.TempDir(), "profiles.json") - err := os.WriteFile(cacheFilePath, []byte(`{"version":1000}`), 0o600) + cacheFilePath := filepath.Join(t.TempDir(), "profiles.pb") + pbCache := filecachepb.New(cacheFilePath) + err := pbCache.Store(&internal.FileCache{ + Version: 10000, + }) require.NoError(t, err) db, err := profiledb.New(ps, 1*time.Minute, cacheFilePath) diff --git a/internal/profiledb/testdata/profiles.json b/internal/profiledb/testdata/profiles.json deleted file mode 100644 index 1aca6a8..0000000 --- a/internal/profiledb/testdata/profiles.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "sync_time": "SYNC_TIME", - "profiles": [ - { - "Parental": { - "Schedule": { - "Week": [ - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - }, - { - "Start": 0, - "End": 700 - } - ], - "TimeZone": "Europe/Brussels" - }, - "Enabled": true - }, - "BlockingMode": { - "type": "null_ip" - }, - "ID": "prof1234", - "UpdateTime": "0001-01-01T00:00:00.000Z", - "DeviceIDs": [ - "dev1234" - ], - "RuleListIDs": [ - "adguard_dns_filter" - ], - "CustomRules": [ - "|blocked-by-custom.example" - ], - "FilteredResponseTTL": 10000000000, - "FilteringEnabled": true, - "SafeBrowsing": { - "Enabled": true, - "BlockDangerousDomains": true, - "BlockNewlyRegisteredDomains": false - }, - "RuleListsEnabled": true, - "QueryLogEnabled": true, - "Deleted": false, - "BlockPrivateRelay": true, - "BlockFirefoxCanary": true - } - ], - "devices": [ - { - "ID": "dev1234", - "LinkedIP": "1.2.3.4", - "Name": "dev1", - "DedicatedIPs": [ - "1.2.4.5" - ], - "FilteringEnabled": true - } - ], - "version": 7 -} diff --git a/internal/querylog/fs.go b/internal/querylog/fs.go index 0393a36..f9ee5b7 100644 --- a/internal/querylog/fs.go +++ b/internal/querylog/fs.go @@ -6,10 +6,10 @@ import ( "encoding/json" "fmt" "os" - "sync" "time" "github.com/AdguardTeam/AdGuardDNS/internal/agd" + "github.com/AdguardTeam/AdGuardDNS/internal/agdsync" "github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/golibs/errors" @@ -27,14 +27,12 @@ type FileSystemConfig struct { func NewFileSystem(c *FileSystemConfig) (l *FileSystem) { return &FileSystem{ path: c.Path, - bufferPool: &sync.Pool{ - New: func() (buf any) { - return &entryBuffer{ - ent: &jsonlEntry{}, - buf: &bytes.Buffer{}, - } - }, - }, + bufferPool: agdsync.NewTypedPool(func() (v *entryBuffer) { + return &entryBuffer{ + ent: &jsonlEntry{}, + buf: &bytes.Buffer{}, + } + }), } } @@ -47,10 +45,9 @@ type entryBuffer struct { // FileSystem is the file system implementation of the AdGuard DNS query log. type FileSystem struct { - // bufferPool is a pool with *entryBuffer instances we're using to avoid - // extra allocations when serializing query log items to JSON and writing - // them. - bufferPool *sync.Pool + // bufferPool is a pool with [*entryBuffer] instances used to avoid extra + // allocations when serializing query log items to JSON and writing them. + bufferPool *agdsync.TypedPool[entryBuffer] // path is the path to the query log file. path string @@ -72,7 +69,7 @@ func (l *FileSystem) Write(_ context.Context, e *Entry) (err error) { metrics.QueryLogItemsCount.Inc() }() - entBuf := l.bufferPool.Get().(*entryBuffer) + entBuf := l.bufferPool.Get() defer l.bufferPool.Put(entBuf) entBuf.buf.Reset() diff --git a/internal/tools/go.mod b/internal/tools/go.mod index 4eeff43..72fe3a7 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -8,28 +8,29 @@ require ( 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 + github.com/securego/gosec/v2 v2.17.0 github.com/uudashr/gocognit v1.0.7 - golang.org/x/tools v0.11.1 - golang.org/x/vuln v1.0.0 + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 + golang.org/x/vuln v1.0.1 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 google.golang.org/protobuf v1.31.0 - honnef.co/go/tools v0.4.3 + honnef.co/go/tools v0.4.5 mvdan.cc/gofumpt v0.5.0 - mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868 + mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 ) require ( github.com/BurntSushi/toml v1.3.2 // indirect + github.com/ccojocar/zxcvbn-go v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // 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-20230801115018-d63ba01acd4b // indirect + golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index deff817..0cafb37 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -1,6 +1,7 @@ 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/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= +github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -16,8 +17,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 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/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= @@ -30,16 +31,12 @@ github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fx github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw= github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU= github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -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/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI= +github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc= 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= @@ -54,8 +51,8 @@ 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-20230801115018-d63ba01acd4b h1:3dfup1Bt5y1sKG6rbyAX4qNymwAtJcqx+Aqm1DPP/Qg= -golang.org/x/exp/typeparams v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63 h1:XkcpbHJE31bhdecT6qfUGtB7MCIKA8Vb9uGOyX/T364= +golang.org/x/exp/typeparams v0.0.0-20230817173708-d852ddb80c63/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/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= @@ -68,7 +65,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R 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.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= 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= @@ -84,28 +81,30 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/tools v0.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/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU= +golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= @@ -113,9 +112,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= -honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= +honnef.co/go/tools v0.4.5 h1:YGD4H+SuIOOqsyoLOpZDWcieM28W47/zRO7f+9V3nvo= +honnef.co/go/tools v0.4.5/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k= 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-20230610194454-9ea02bef9868 h1:F4Q7pXcrU9UiU1fq0ZWqSOxKjNAteRuDr7JDk7uVLRQ= -mvdan.cc/unparam v0.0.0-20230610194454-9ea02bef9868/go.mod h1:6ZaiQyI7Tiq0HQ56g6N8TlkSd80/LyagZeaw8mb7jYE= +mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0 h1:NAENkqZ+Xofhqs4R4Af+i3HpZj1M23SFn/lHfRh1D4E= +mvdan.cc/unparam v0.0.0-20230815095028-f7c6fb1088f0/go.mod h1:flQN1deud3vIpPdF88533Lpp/MvzGLgPIPjB1kgBf4I= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index 6a1a029..fccb97b 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -13,6 +13,7 @@ import ( _ "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" + _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" _ "google.golang.org/protobuf/cmd/protoc-gen-go" _ "honnef.co/go/tools/cmd/staticcheck" _ "mvdan.cc/gofumpt" diff --git a/scripts/make/go-gen.sh b/scripts/make/go-gen.sh new file mode 100644 index 0000000..39079ee --- /dev/null +++ b/scripts/make/go-gen.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +verbose="${VERBOSE:-0}" +readonly verbose + +if [ "$verbose" -gt '1' ] +then + env + set -x +elif [ "$verbose" -gt '0' ] +then + set -x +else + set +x +fi + +# Exit the script if a pipeline fails (-e), prevent accidental filename +# expansion (-f), and consider undefined variables as errors (-u). +set -e -f -u + +# Allow users to override the go command from environment. For example, to +# build two releases with two different Go versions and test the difference. +go="${GO:-go}" +readonly go + +( + cd ./internal/agd/ + "$go" run ./country_generate.go +) + +( + cd ./internal/geoip/ + "$go" run ./asntops_generate.go +) + +( + cd ./internal/profiledb/internal/filecachepb/ + protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto +) + +( + cd ./internal/backendpb/ + protoc --go_opt=paths=source_relative --go_out=.\ + --go-grpc_opt=paths=source_relative --go-grpc_out=. ./backend.proto +) diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index b2c0a7d..3d99053 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -131,6 +131,7 @@ underscores() { git ls-files '*_*.go'\ | grep -F\ -e '_generate.go'\ + -e '_grpc.pb.go'\ -e '_linux.go'\ -e '_noreuseport.go'\ -e '_others.go'\ @@ -177,45 +178,62 @@ 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/\ - ; +# TODO(a.garipov): Enable 10 for all. +# +# TODO(a.garipov): Redo once https://github.com/uudashr/gocognit/issues/22 is +# fixed. +gocognit_paths="\ +./internal/metrics 19 +./internal/filter/internal/composite/ 15 +./internal/ecscache 15 +./internal/backend/ 14 +./internal/dnsserver/ 14 +./internal/dnsmsg/ 11 +./internal/filter/hashprefix/ 11 +./internal/agd/ 10 +./internal/agdhttp/ 10 +./internal/agdio/ 10 +./internal/agdnet/ 10 +./internal/agdprotobuf/ 10 +./internal/agdtest/ 10 +./internal/agdtime/ 10 +./internal/billstat/ 10 +./internal/bindtodevice/ 10 +./internal/cmd/ 10 +./internal/connlimiter/ 10 +./internal/consul/ 10 +./internal/debugsvc/ 10 +./internal/dnscheck/ 10 +./internal/dnsdb/ 10 +./internal/dnsserver/cache/ 10 +./internal/dnsserver/dnsservertest/ 10 +./internal/dnsserver/forward/ 10 +./internal/dnsserver/netext/ 10 +./internal/dnsserver/pool/ 10 +./internal/dnsserver/prometheus/ 10 +./internal/dnsserver/querylog/ 10 +./internal/dnsserver/ratelimit/ 10 +./internal/dnssvc/ 10 +./internal/errcoll/ 10 +./internal/filter/internal/custom/ 10 +./internal/filter/internal/filtertest/ 10 +./internal/filter/internal/resultcache/ 10 +./internal/filter/internal/rulelist/ 10 +./internal/filter/internal/safesearch/ 10 +./internal/filter/internal/serviceblock/ 10 +./internal/geoip/ 10 +./internal/optlog/ 10 +./internal/profiledb/internal/profiledbtest/ 10 +./internal/querylog/ 10 +./internal/rulestat/ 10 +./internal/tools/ 10 +./internal/websvc/ 10" +readonly gocognit_paths + +echo "$gocognit_paths" | while read -r path max +do + run_linter gocognit --over="$max" "$path" +done run_linter ineffassign ./... "$dnssrvmod" @@ -232,9 +250,11 @@ run_linter nilness ./... "$dnssrvmod" # Do not use fieldalignment on $dnssrvmod, because ameshkov likes to place # struct fields in an order that he considers more readable. # -# TODO(a.garipov): Remove the loop once golang/go#60509 is fixed. +# TODO(a.garipov): Remove the loop once golang/go#60509, golang/go#61574 are +# fixed. ( run_linter fieldalignment ./main.go + run_linter -e shadow --strict ./main.go set +f for d in ./internal/*/ ./internal/*/*/ ./internal/*/*/*/ @@ -243,18 +263,20 @@ run_linter nilness ./... "$dnssrvmod" in (*/testdata/*|\ ./internal/dnsserver/*|\ + ./internal/backendpb/|\ ./internal/profiledb/internal/filecachepb/|\ ./internal/tools/) continue ;; (*) run_linter fieldalignment "$d" + run_linter -e shadow --strict "$d" ;; esac done ) -run_linter -e shadow --strict ./... "$dnssrvmod" +run_linter -e shadow --strict "$dnssrvmod" run_linter gosec --quiet ./... "$dnssrvmod" diff --git a/scripts/make/go-tools.sh b/scripts/make/go-tools.sh index c9f2c92..25ea395 100644 --- a/scripts/make/go-tools.sh +++ b/scripts/make/go-tools.sh @@ -46,6 +46,7 @@ rm -f\ bin/misspell\ bin/nilness\ bin/protoc-gen-go\ + bin/protoc-gen-go-grpc\ bin/shadow\ bin/staticcheck\ bin/unparam\ @@ -75,6 +76,7 @@ env\ golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow\ golang.org/x/vuln/cmd/govulncheck\ google.golang.org/protobuf/cmd/protoc-gen-go\ + google.golang.org/grpc/cmd/protoc-gen-go-grpc\ honnef.co/go/tools/cmd/staticcheck\ mvdan.cc/gofumpt\ mvdan.cc/unparam\