diff --git a/.github/config.yaml b/.github/config.yaml
new file mode 100644
index 0000000..9c676dc
--- /dev/null
+++ b/.github/config.yaml
@@ -0,0 +1,13 @@
+'blank_issues_enabled': false
+'contact_links':
+ - 'about': >
+ Please report filtering issues, for example advertising filters
+ misfiring or safe browsing false positives, using the form on our
+ website
+ 'name': 'AdGuard filters issues'
+ 'url': 'https://link.adtidy.org/forward.html?action=report&app=home&from=github'
+ - 'about': >
+ Please send requests for new blocked services and vetted filtering lists
+ to the Hostlists Registry repository
+ 'name': 'Blocked services and vetted filtering rule lists: AdGuard Hostlists Registry'
+ 'url': 'https://github.com/AdguardTeam/HostlistsRegistry'
diff --git a/.github/feature_request_template.yaml b/.github/feature_request_template.yaml
new file mode 100644
index 0000000..2189781
--- /dev/null
+++ b/.github/feature_request_template.yaml
@@ -0,0 +1,30 @@
+---
+
+name: 🌱 Feature request
+description: Create a feature request to help us improve AdGuard DNS.
+labels: [ 'feature request' ]
+body:
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: Issue Details
+ description: What happened?
+ placeholder: Is your feature request related to a problem? Please add a clear and concise description of what the problem is.
+ validations:
+ required: false
+ - type: textarea
+ id: how_it_should_be
+ attributes:
+ label: Proposed solution
+ description:
+ placeholder: Describe the solution you'd like in a clear and concise manner.
+ validations:
+ required: false
+ - type: textarea
+ id: additional
+ attributes:
+ label: Alternative solution
+ description:
+ placeholder: A clear and concise description of any alternative solutions or features you've considered.
+ validations:
+ required: false
diff --git a/.github/issue_template.yaml b/.github/issue_template.yaml
new file mode 100644
index 0000000..9cdb386
--- /dev/null
+++ b/.github/issue_template.yaml
@@ -0,0 +1,111 @@
+---
+
+name: 🐞 Bug report
+description: Create a bug report to help us improve AdGuard DNS.
+labels: [ 'bug' ]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ validations:
+ required: true
+ - type: dropdown
+ attributes:
+ label: Platform
+ description:
+ options:
+ - Windows
+ - macOS
+ - Android
+ - iOS
+ - Linux
+ - Router
+ - Other
+ validations:
+ required: true
+ - type: dropdown
+ attributes:
+ label: Protocol
+ description: Which DNS protocol do you use?
+ options:
+ - Regular
+ - DNSCrypt
+ - DNS-over-HTTPS
+ - DNS-over-TLS
+ - DNS-over-QUIC
+ validations:
+ required: true
+ - type: dropdown
+ attributes:
+ label: Do you use AdGuard app?
+ description:
+ options:
+ - Yes I am
+ - No I don't
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Your configuration
+ description:
+ placeholder: Specify additional information about your AdGuard DNS configuration.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Traceroute to AdGuard DNS
+ description: Paste the output of the command "traceroute 94.140.14.14".
+ validations:
+ required: false
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: Issue Details
+ description: Please provide a set of steps to reproduce the issue.
+ placeholder: |
+ Steps to reproduce:
+ 1.
+ 2.
+ 3.
+ validations:
+ required: true
+ - type: textarea
+ id: how_it_should_be
+ attributes:
+ label: Expected Behavior
+ description:
+ placeholder: A clear and concise description of what you expected to happen.
+ validations:
+ required: false
+ - type: textarea
+ id: how_it_is
+ attributes:
+ label: Actual Behavior
+ description:
+ placeholder: A clear description of what happened instead.
+ validations:
+ required: true
+ - type: textarea
+ id: screens
+ attributes:
+ label: Screenshots
+ description: |
+ If applicable add screenshots explaining your problem.
+ You can drag and drop images or paste them from clipboard.
+ Use ` ` tag to hide screenshots under the spoiler.
+ placeholder: If applicable add screenshots explaining your problem.
+ value: |
+ Screenshot 1:
+
+
+ validations:
+ required: false
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional Information
+ description:
+ placeholder: Add any other context about the problem here.
+ validations:
+ required: false
diff --git a/README.md b/README.md
index 607d448..78bbcaf 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,7 @@ following features:
## Software License
-Copyright (C) 2022 AdGuard Software Ltd.
+Copyright (C) 2022-2023 AdGuard Software Ltd.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
diff --git a/config.dist.yml b/config.dist.yml
index 9550cfc..02d83fb 100644
--- a/config.dist.yml
+++ b/config.dist.yml
@@ -8,7 +8,6 @@ ratelimit:
refuseany: true
# If response is larger than this, it is counted as several responses.
response_size_estimate: 1KB
- rps: 30
# Rate limit options for IPv4 addresses.
ipv4:
# Rate of requests per second for one subnet for IPv4 addresses.
diff --git a/doc/development.md b/doc/development.md
index fca97e6..c31a5e4 100644
--- a/doc/development.md
+++ b/doc/development.md
@@ -1,8 +1,19 @@
# AdGuard DNS Development Setup
+## Contents
+
+ * [Initial setup](#init)
+ * [Common Makefile macros and targets](#makefile)
+ * [How to run AdGuard DNS](#run)
+ * [Testing](#testing)
+
+
+
+## Initial setup
+
Development is supported on Linux and macOS (aka Darwin) systems.
-1. Install Go 1.18 or later.
+1. Install Go 1.20 or later.
1. Call `make init` to set up the Git pre-commit hook.
@@ -11,7 +22,7 @@ Development is supported on Linux and macOS (aka Darwin) systems.
-## Common Makefile Macros And Targets
+## Common Makefile macros and targets
Most development tasks are done through the use of our Makefile. Please keep
the Makefile POSIX-compliant and portable.
@@ -92,13 +103,13 @@ This is not an extensive list. See `../Makefile`.
-## How To Run AdGuard DNS
+## How to run AdGuard DNS
This is an example on how to run AdGuard DNS locally.
- ### Step 1: Prepare The TLS Certificate And The Key
+ ### Step 1: prepare the TLS certificate and the key
Keeping the test files in the `test` directory since it's added to `.gitignore`:
@@ -122,7 +133,7 @@ openssl rand 32 > ./tls_key_2
- ### Step 2: Prepare The DNSCrypt Configuration
+ ### Step 2: prepare the DNSCrypt configuration
Install the [`dnscrypt`][dnsc] tool:
@@ -143,7 +154,7 @@ dnscrypt generate -p testdns -o ./dnscrypt.yml
- ### Step 3: Prepare The Configuration File
+ ### Step 3: prepare the configuration file
```sh
cd ../
@@ -152,7 +163,7 @@ cp -f config.dist.yml config.yml
- ### Step 4: Prepare The Test Data
+ ### Step 4: prepare the test data
```sh
echo '
Dangerous content ahead' > ./test/block_page_sb.html
@@ -163,7 +174,7 @@ echo 'Error 500' > ./test/error_500.html
- ### Step 5: Compile AdGuard DNS
+ ### Step 5: compile AdGuard DNS
```sh
make build
@@ -171,7 +182,7 @@ make build
- ### Step 6: Prepare Cache Data And GeoIP
+ ### Step 6: prepare cache data and GeoIP
We'll use the test versions of the GeoIP databases here.
@@ -184,7 +195,7 @@ curl 'https://raw.githubusercontent.com/maxmind/MaxMind-DB/main/test-data/GeoLit
- ### Step 7: Run AdGuard DNS
+ ### Step 7: run AdGuard DNS
You'll need to supply the following:
@@ -235,7 +246,7 @@ env \
- ### Step 8: Test Your Instance
+ ### Step 8: test your instance
Plain DNS:
@@ -275,3 +286,55 @@ dnslookup example.org sdns://AQcAAAAAAAAADjEyNy4wLjAuMTo1NDQzIAbKgP3dmXybr1DaKIF
[dnsc]: https://github.com/ameshkov/dnscrypt
[dnscdl]: https://github.com/ameshkov/dnscrypt/releases
+
+
+
+## Testing
+
+The `go-bench` and `go-test` targets [described earlier](#makefile-targets)
+should generally be enough, but there are cases where additional testing setup
+is required. One such case is package `bindtodevice`.
+
+
+
+ ### Testing `SO_BINDTODEVICE` features
+
+The `SO_BINDTODEVICE` features require a Linux machine with a particular IP
+routing set up. In order to test these features on architectures other than
+Linux, this repository has a Dockerfile and a convenient script to use it, see
+`scripts/test/bindtodevice.sh`.
+
+A simple example:
+
+ * If your Docker is installed in a way that doesn't require `sudo` to use it:
+
+ ```sh
+ sh ./scripts/test/bindtodevice.sh
+ ```
+
+ * Otherwise:
+
+ ```sh
+ env USE_SUDO=1 sh ./scripts/test/bindtodevice.sh
+ ```
+
+This will build the image and open a shell within the container. The container
+environment is defined by `scripts/test/bindtodevice.docker`, and has all
+utilities required to build the `AdGuardDNS` binary and test it. The working
+directory is also shared with the container through the `/test` directory inside
+it. The container also routes all IP connections to any address in the
+`172.17.0.0/16` subnet to the `eth0` network interface. So, calling `make
+go-test` or a similar command from within the container will actually test the
+`SO_BINDTODEVICE` features:
+
+```sh
+go test --cover -v ./internal/bindtodevice/
+```
+
+If you want to open an additional terminal (for example to launch `AdGuardDNS`
+in one and `dig` it in the other), use `docker exec` like this (you may need
+`sudo` for that):
+
+```sh
+docker exec -i -t agdns_bindtodevice_test /bin/sh
+```
diff --git a/go.mod b/go.mod
index 65ee670..f2a2282 100644
--- a/go.mod
+++ b/go.mod
@@ -1,30 +1,31 @@
module github.com/AdguardTeam/AdGuardDNS
-go 1.19
+go 1.20
require (
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0
- github.com/AdguardTeam/golibs v0.11.4
+ github.com/AdguardTeam/golibs v0.12.1
github.com/AdguardTeam/urlfilter v0.16.1
github.com/ameshkov/dnscrypt/v2 v2.2.5
- github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a
+ github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc
github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
- github.com/caarlos0/env/v6 v6.10.1
- github.com/getsentry/sentry-go v0.15.0
+ github.com/caarlos0/env/v7 v7.1.0
+ github.com/getsentry/sentry-go v0.19.0
github.com/google/renameio v1.0.1
- github.com/miekg/dns v1.1.50
+ github.com/miekg/dns v1.1.52
github.com/oschwald/maxminddb-golang v1.10.0
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/client_model v0.3.0
- github.com/prometheus/common v0.37.0
- github.com/stretchr/testify v1.8.1
- go.etcd.io/bbolt v1.3.6
- golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
- golang.org/x/net v0.4.0
- golang.org/x/sys v0.3.0
- golang.org/x/time v0.2.0
+ github.com/prometheus/common v0.41.0
+ github.com/quic-go/quic-go v0.33.0
+ github.com/stretchr/testify v1.8.2
+ go.etcd.io/bbolt v1.3.7
+ golang.org/x/exp v0.0.0-20230307190834-24139beb5833
+ golang.org/x/net v0.8.0
+ golang.org/x/sys v0.6.0
+ golang.org/x/time v0.3.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -33,26 +34,25 @@ require (
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
+ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
- github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
- github.com/lucas-clemente/quic-go v0.31.0 // indirect
- github.com/marten-seemann/qpack v0.3.0 // indirect
- github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
- github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
+ github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
- github.com/onsi/ginkgo/v2 v2.5.1 // indirect
+ github.com/onsi/ginkgo/v2 v2.9.0 // indirect
github.com/panjf2000/ants/v2 v2.7.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/procfs v0.8.0 // indirect
- golang.org/x/crypto v0.3.0 // indirect
- golang.org/x/mod v0.7.0 // indirect
- golang.org/x/text v0.5.0 // indirect
- golang.org/x/tools v0.3.0 // indirect
+ github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/quic-go/qpack v0.4.0 // indirect
+ github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
+ github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
+ golang.org/x/crypto v0.7.0 // indirect
+ golang.org/x/mod v0.9.0 // indirect
+ golang.org/x/text v0.8.0 // indirect
+ golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 2ba411c..703fbdc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,222 +1,75 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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.11.4 h1:IltyvxwCTN+xxJF5sh6VadF8Zfbf8elgCm9dgijSVzM=
-github.com/AdguardTeam/golibs v0.11.4/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
+github.com/AdguardTeam/golibs v0.12.1 h1:bJfFzCnUCl+QsP6prUltM2Sjt0fTiDBPlxuAwfKP3g8=
+github.com/AdguardTeam/golibs v0.12.1/go.mod h1:rIglKDHdLvFT1UbhumBLHO9S4cvWS9MEyT1njommI/Y=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY=
github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
-github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a h1:eqjiAL3qooftPm8b9C1GsSSRcmlw7iOva8vdBTmV2PY=
-github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc h1:Keo7wQ7UODUaHcEi7ltENhbAK2VgZjfat6mLy03tQzo=
+github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY=
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
-github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
-github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+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=
-github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
-github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
+github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
+github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/getsentry/sentry-go v0.19.0 h1:BcCH3CN5tXt5aML+gwmbFwVptLLQA+eT866fCO9wVOM=
+github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKYwpEqryTOC/nE=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
+github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
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/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
-github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
-github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
-github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
-github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
-github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
-github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
-github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
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.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
-github.com/miekg/dns v1.1.50/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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
+github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw=
-github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
-github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
+github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
+github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
+github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M=
@@ -224,386 +77,108 @@ github.com/panjf2000/ants/v2 v2.7.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9i
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
-github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
-github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw=
+github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
+github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
+github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
+github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
+github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
github.com/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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
-go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
+go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
+golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
+golang.org/x/mod v0.9.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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
-golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/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-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
-golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
-golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
+golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/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=
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.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/go.work b/go.work
index 76d1b85..b85227b 100644
--- a/go.work
+++ b/go.work
@@ -1,4 +1,4 @@
-go 1.19
+go 1.20
use (
.
diff --git a/go.work.sum b/go.work.sum
index 918589d..820e538 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -9,11 +9,7 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0 h1:SPOUaucgtVl
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412 h1:GvWw74lx5noHocd+f6HBMXK6DuggBB1dhVkuGZbv7qM=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c h1:ivON6cwHK1OH26MZyWDCnbTRZZf0IhNsENoNAKFS1g4=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8=
-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.10.7/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
-github.com/AdguardTeam/golibs v0.11.4 h1:IltyvxwCTN+xxJF5sh6VadF8Zfbf8elgCm9dgijSVzM=
-github.com/AdguardTeam/golibs v0.11.4/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA=
github.com/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGjKN9pinUU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
@@ -78,6 +74,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
+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-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@@ -207,6 +204,8 @@ go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d h1:E2M5QgjZ/Jg+ObCQAudsXxuTsLj7Nl5RV/lZcQZmKSo=
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/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
@@ -216,7 +215,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -224,12 +222,12 @@ 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -245,7 +243,6 @@ gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
diff --git a/internal/agd/device.go b/internal/agd/device.go
index 930386d..221f815 100644
--- a/internal/agd/device.go
+++ b/internal/agd/device.go
@@ -58,7 +58,7 @@ func NewDeviceID(s string) (id DeviceID, err error) {
return "", err
}
- err = netutil.ValidateDomainNameLabel(s)
+ err = netutil.ValidateHostnameLabel(s)
if err != nil {
// Unwrap the error to replace the domain name label wrapper message
// with our own.
diff --git a/internal/agd/profile.go b/internal/agd/profile.go
index 672f2c6..1b0696a 100644
--- a/internal/agd/profile.go
+++ b/internal/agd/profile.go
@@ -5,6 +5,7 @@ import (
"math"
"time"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors"
)
@@ -14,7 +15,8 @@ import (
// the infrastructure, a profile is also called a “DNS server”. We call it
// profile, because it's less confusing.
//
-// NOTE: Increment defaultProfileDBCacheVersion on any change of this structure.
+// NOTE: Increment [defaultProfileDBCacheVersion] on any change of this
+// structure.
//
// TODO(a.garipov): Consider making it closer to the config file and the backend
// response by grouping parental, rule list, and safe browsing settings into
@@ -24,6 +26,9 @@ type Profile struct {
// FilteringEnabled is set to false.
Parental *ParentalProtectionSettings
+ // BlockingMode defines the way blocked responses are constructed.
+ BlockingMode dnsmsg.BlockingModeCodec
+
// ID is the unique ID of this profile.
ID ProfileID
diff --git a/internal/agd/profiledb.go b/internal/agd/profiledb.go
index 39c3b10..fcb1a3d 100644
--- a/internal/agd/profiledb.go
+++ b/internal/agd/profiledb.go
@@ -179,6 +179,8 @@ type profileCache struct {
// saveStorageCache saves profiles data to cache file.
func (db *DefaultProfileDB) saveProfileCache(ctx context.Context) (err error) {
+ log.Info("profiledb: saving profile cache")
+
var resp *PSProfilesResponse
resp, err = db.storage.Profiles(ctx, &PSProfilesRequest{
SyncTime: time.Time{},
@@ -205,33 +207,43 @@ func (db *DefaultProfileDB) saveProfileCache(ctx context.Context) (err error) {
return err
}
- log.Debug("profiledb: cache: saved %d profiles to %q", len(resp.Profiles), db.cacheFilePath)
+ log.Info("profiledb: cache: saved %d profiles to %q", len(resp.Profiles), db.cacheFilePath)
return nil
}
// defaultProfileDBCacheVersion is the version of cached data structure. It's
// manually incremented on every change in [profileCache] structure.
-const defaultProfileDBCacheVersion = 1
+const defaultProfileDBCacheVersion = 2
// loadProfileCache loads profiles data from cache file.
func (db *DefaultProfileDB) loadProfileCache() (err error) {
+ log.Info("profiledb: loading cache")
+
data, err := db.loadStorageCache()
if err != nil {
return fmt.Errorf("loading cache: %w", err)
}
if data == nil {
+ log.Info("profiledb: cache is empty")
+
return nil
}
if data.Version == defaultProfileDBCacheVersion {
profiles := data.Profiles
devNum := db.setProfiles(profiles)
- log.Debug("profiledb: cache: got %d profiles with %d devices", len(profiles), devNum)
+ log.Info("profiledb: cache: got %d profiles with %d devices", len(profiles), devNum)
db.syncTime = data.SyncTime
db.syncTimeFull = data.SyncTime
+ } else {
+ log.Info(
+ "profiledb: cache version %d is different from %d",
+ data.Version,
+ defaultProfileDBCacheVersion,
+ )
}
return nil
diff --git a/internal/agd/profiledb_test.go b/internal/agd/profiledb_test.go
index 1b6cef6..37020e7 100644
--- a/internal/agd/profiledb_test.go
+++ b/internal/agd/profiledb_test.go
@@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -23,6 +24,9 @@ func newDefaultProfileDB(tb testing.TB, dev *agd.Device) (db *agd.DefaultProfile
) (resp *agd.PSProfilesResponse, err error) {
return &agd.PSProfilesResponse{
Profiles: []*agd.Profile{{
+ BlockingMode: dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeNullIP{},
+ },
ID: testProfID,
Devices: []*agd.Device{dev},
}},
diff --git a/internal/agd/server.go b/internal/agd/server.go
index c27c97a..1211779 100644
--- a/internal/agd/server.go
+++ b/internal/agd/server.go
@@ -4,6 +4,7 @@ import (
"crypto/tls"
"net/netip"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns"
@@ -87,8 +88,8 @@ type Server struct {
// Server Name.
Name ServerName
- // BindAddresses are addresses this server binds to.
- BindAddresses []netip.AddrPort
+ // BindData are the socket binding data for this server.
+ BindData []*ServerBindData
// Protocol is the protocol of the server.
Protocol Protocol
@@ -98,6 +99,22 @@ type Server struct {
LinkedIPEnabled bool
}
+// ServerBindData are the socket binding data for a server. Either AddrPort or
+// ListenConfig with Address must be set.
+//
+// TODO(a.garipov): Add support for ListenConfig in the config file.
+//
+// TODO(a.garipov): Consider turning this into a sum type.
+//
+// TODO(a.garipov): Consider renaming this and the one in websvc to something
+// like BindConfig.
+type ServerBindData struct {
+ ListenConfig netext.ListenConfig
+ Address string
+
+ AddrPort netip.AddrPort
+}
+
// ServerName is the name of a server.
type ServerName string
diff --git a/internal/agdmaps/agdmaps.go b/internal/agdmaps/agdmaps.go
new file mode 100644
index 0000000..d47c07e
--- /dev/null
+++ b/internal/agdmaps/agdmaps.go
@@ -0,0 +1,40 @@
+// Package agdmaps contains utilities for map handling.
+package agdmaps
+
+import (
+ "golang.org/x/exp/constraints"
+ "golang.org/x/exp/maps"
+ "golang.org/x/exp/slices"
+)
+
+// OrderedRange is like the usual Go range but sorts the keys before iterating
+// ensuring a predictable order. If cont is false, OrderedRange stops the
+// iteration.
+func OrderedRange[K constraints.Ordered, V any, M ~map[K]V](m M, f func(k K, v V) (cont bool)) {
+ keys := maps.Keys(m)
+ slices.Sort(keys)
+ for _, k := range keys {
+ if !f(k, m[k]) {
+ break
+ }
+ }
+}
+
+// OrderedRangeError is like [OrderedRange] but uses an error to signal that the
+// iteration must be stopped. err is the same error as the one returned from f,
+// or nil if no errors are returned.
+func OrderedRangeError[K constraints.Ordered, V any, M ~map[K]V](
+ m M,
+ f func(k K, v V) (err error),
+) (err error) {
+ keys := maps.Keys(m)
+ slices.Sort(keys)
+ for _, k := range keys {
+ err = f(k, m[k])
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/internal/agdtest/agdtest.go b/internal/agdtest/agdtest.go
index fb83fe6..3c8b0e7 100644
--- a/internal/agdtest/agdtest.go
+++ b/internal/agdtest/agdtest.go
@@ -1,3 +1,18 @@
// Package agdtest contains simple mocks for common interfaces and other test
// utilities.
package agdtest
+
+import (
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+)
+
+// FilteredResponseTTL is the common filtering response TTL for tests. It is
+// also used by [NewConstructor].
+const FilteredResponseTTL = 10 * time.Second
+
+// NewConstructor returns a standard dnsmsg.Constructor for tests.
+func NewConstructor() (c *dnsmsg.Constructor) {
+ return dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, FilteredResponseTTL)
+}
diff --git a/internal/backend/billstat.go b/internal/backend/billstat.go
index 8f0b182..5090ae4 100644
--- a/internal/backend/billstat.go
+++ b/internal/backend/billstat.go
@@ -10,10 +10,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/golibs/errors"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
)
// Billing Statistics Uploader
@@ -114,13 +113,8 @@ type v1DevicesActivityReqDevice struct {
// billStatRecsToReq converts billing statistics records into devices for the
// devices activity HTTP API.
func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityReqDevice) {
- // Sort the keys to make the queries reproducible and testable.
- deviceIDs := maps.Keys(records)
- slices.Sort(deviceIDs)
-
- devices = make([]*v1DevicesActivityReqDevice, 0, len(deviceIDs))
- for _, id := range deviceIDs {
- rec := records[id]
+ devices = make([]*v1DevicesActivityReqDevice, 0, len(records))
+ agdmaps.OrderedRange(records, func(id agd.DeviceID, rec *billstat.Record) (cont bool) {
devices = append(devices, &v1DevicesActivityReqDevice{
ClientCountry: rec.Country,
DeviceID: id,
@@ -129,7 +123,9 @@ func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityRe
Queries: rec.Queries,
Proto: uint8(rec.Proto),
})
- }
+
+ return true
+ })
return devices
}
diff --git a/internal/backend/profiledb.go b/internal/backend/profiledb.go
index 4e089b2..e7b7aba 100644
--- a/internal/backend/profiledb.go
+++ b/internal/backend/profiledb.go
@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
@@ -158,6 +159,7 @@ type v1SettingsRespSettings struct {
Parental *v1SettingsRespParental `json:"parental"`
RuleLists *v1SettingsRespRuleLists `json:"rule_lists"`
SafeBrowsing *v1SettingsRespSafeBrowsing `json:"safe_browsing"`
+ BlockingMode dnsmsg.BlockingModeCodec `json:"blocking_mode"`
Devices []*v1SettingsRespDevice `json:"devices"`
CustomRules []string `json:"custom_rules"`
FilteredResponseTTL uint32 `json:"filtered_response_ttl"`
@@ -180,6 +182,9 @@ func (rs *v1SettingsRespSettings) UnmarshalJSON(b []byte) (err error) {
type defaultDec v1SettingsRespSettings
s := defaultDec{
+ BlockingMode: dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeNullIP{},
+ },
BlockFirefoxCanary: true,
}
@@ -496,6 +501,7 @@ func (r *v1SettingsResp) toInternal(
pr.Profiles = append(pr.Profiles, &agd.Profile{
Parental: parental,
+ BlockingMode: s.BlockingMode,
ID: id,
UpdateTime: updTime,
Devices: devices,
diff --git a/internal/backend/profiledb_test.go b/internal/backend/profiledb_test.go
index 2e16fa0..521d234 100644
--- a/internal/backend/profiledb_test.go
+++ b/internal/backend/profiledb_test.go
@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/backend"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -30,6 +31,10 @@ func TestProfileStorage_Profiles(t *testing.T) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pt := testutil.PanicT{}
+ // NOTE: Keep testdata/profiles.json formatted using jq. Example of a
+ // formatting command using sponge(1) from the current directory:
+ //
+ // jq '.' ./testdata/profiles.json | sponge ./testdata/profiles.json
reqURLStr = r.URL.String()
b, err := os.ReadFile(filepath.Join("testdata", "profiles.json"))
require.NoError(pt, err)
@@ -86,6 +91,7 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
Start: 0,
End: 59,
}
+
wantParental := &agd.ParentalProtectionSettings{
Schedule: &agd.ParentalProtectionSchedule{
Week: &agd.WeeklySchedule{
@@ -105,12 +111,23 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
GeneralSafeSearch: false,
YoutubeSafeSearch: false,
}
+
wantLinkedIP := netip.AddrFrom4([4]byte{1, 2, 3, 4})
+ wantBlockingMode := dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeCustomIP{
+ IPv4: netip.MustParseAddr("1.2.3.4"),
+ IPv6: netip.MustParseAddr("1234::cdef"),
+ },
+ }
+
want := &agd.PSProfilesResponse{
SyncTime: syncTime,
Profiles: []*agd.Profile{{
- Parental: nil,
+ Parental: nil,
+ BlockingMode: dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeNullIP{},
+ },
ID: "37f97ee9",
UpdateTime: updTime,
Devices: []*agd.Device{{
@@ -133,9 +150,10 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
BlockPrivateRelay: true,
BlockFirefoxCanary: true,
}, {
- Parental: wantParental,
- ID: "83f3ea8f",
- UpdateTime: updTime,
+ Parental: wantParental,
+ BlockingMode: wantBlockingMode,
+ ID: "83f3ea8f",
+ UpdateTime: updTime,
Devices: []*agd.Device{{
ID: "0d7724fa",
Name: "Device 1",
diff --git a/internal/backend/testdata/profiles.json b/internal/backend/testdata/profiles.json
index 11af374..6a937f8 100644
--- a/internal/backend/testdata/profiles.json
+++ b/internal/backend/testdata/profiles.json
@@ -5,13 +5,11 @@
"dns_id": "37f97ee9",
"filtering_enabled": true,
"query_log_enabled": true,
- "safe_browsing":
- {
+ "safe_browsing": {
"enabled": true
},
"deleted": false,
"block_private_relay": true,
- "block_firefox_canary": true,
"devices": [
{
"id": "118ffe93",
@@ -24,8 +22,7 @@
"filtering_enabled": true
}
],
- "rule_lists":
- {
+ "rule_lists": {
"enabled": true,
"ids": [
"1"
@@ -38,8 +35,7 @@
"dns_id": "83f3ea8f",
"filtering_enabled": true,
"query_log_enabled": true,
- "safe_browsing":
- {
+ "safe_browsing": {
"enabled": true
},
"deleted": true,
@@ -107,6 +103,11 @@
]
},
"filtered_response_ttl": 3600,
+ "blocking_mode": {
+ "type": "custom_ip",
+ "ipv4": "1.2.3.4",
+ "ipv6": "1234::cdef"
+ },
"custom_rules": [
"||example.org^"
]
diff --git a/internal/bindtodevice/bindtodevice.go b/internal/bindtodevice/bindtodevice.go
new file mode 100644
index 0000000..d1aaa5d
--- /dev/null
+++ b/internal/bindtodevice/bindtodevice.go
@@ -0,0 +1,61 @@
+// Package bindtodevice contains an implementation of the [netext.ListenConfig]
+// interface that uses Linux's SO_BINDTODEVICE socket option to be able to bind
+// to a device.
+//
+// TODO(a.garipov): Finish the package. The current plan is to eventually have
+// something like this:
+//
+// mgr, err := bindtodevice.New()
+// err := mgr.Add("wlp3s0_plain_dns", "wlp3s0", 53)
+// subnet := netip.MustParsePrefix("1.2.3.0/24")
+// lc, err := mgr.ListenConfig("wlp3s0_plain_dns", subnet)
+// err := mgr.Start()
+//
+// Approximate YAML configuration example:
+//
+// 'interface_listeners':
+// # Put listeners into a list so that there is space for future additional
+// # settings, such as timeouts and buffer sizes.
+// 'list':
+// 'iface0_plain_dns':
+// 'interface': 'iface0'
+// 'port': 53
+// 'iface0_plain_dns_secondary':
+// 'interface': 'iface0'
+// 'port': 5353
+// # …
+// # …
+// 'server_groups':
+// # …
+// 'servers':
+// - 'name': 'default_dns'
+// # …
+// bind_interfaces:
+// - 'id': 'iface0_plain_dns'
+// 'subnet': '1.2.3.0/24'
+// - 'id': 'iface0_plain_dns_secondary'
+// 'subnet': '1.2.3.0/24'
+package bindtodevice
+
+import (
+ "fmt"
+ "net"
+)
+
+// ID is the unique identifier of an interface listener.
+type ID string
+
+// unit is a convenient alias for struct{}.
+type unit = struct{}
+
+// Convenient constants containing type names for error reporting using
+// [wrapConnError].
+const (
+ tnChanPConn = "chanPacketConn"
+ tnChanLsnr = "chanListener"
+)
+
+// wrapConnError is a helper for creating informative errors.
+func wrapConnError(typeName, methodName string, laddr net.Addr, err error) (wrapped error) {
+ return fmt.Errorf("bindtodevice: %s %s: %s: %w", typeName, laddr, methodName, err)
+}
diff --git a/internal/bindtodevice/bindtodevice_internal_test.go b/internal/bindtodevice/bindtodevice_internal_test.go
new file mode 100644
index 0000000..1925747
--- /dev/null
+++ b/internal/bindtodevice/bindtodevice_internal_test.go
@@ -0,0 +1,21 @@
+package bindtodevice
+
+import (
+ "net"
+ "time"
+)
+
+// Common addresses for tests.
+var (
+ testLAddr = &net.UDPAddr{
+ IP: net.IP{1, 2, 3, 4},
+ Port: 53,
+ }
+ testRAddr = &net.UDPAddr{
+ IP: net.IP{5, 6, 7, 8},
+ Port: 1234,
+ }
+)
+
+// Common timeout for tests
+const testTimeout = 1 * time.Second
diff --git a/internal/bindtodevice/bindtodevice_test.go b/internal/bindtodevice/bindtodevice_test.go
new file mode 100644
index 0000000..7f70d8c
--- /dev/null
+++ b/internal/bindtodevice/bindtodevice_test.go
@@ -0,0 +1,20 @@
+package bindtodevice_test
+
+import "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
+
+// Common interface listener IDs for tests
+const (
+ testID1 bindtodevice.ID = "id1"
+ testID2 bindtodevice.ID = "id2"
+)
+
+// Common port numbers for tests.
+//
+// TODO(a.garipov): Figure a way to use 0 in most real tests.
+const (
+ testPort1 uint16 = 12345
+ testPort2 uint16 = 12346
+)
+
+// testIfaceName is the common network interface name for tests.
+const testIfaceName = "not_a_real_iface0"
diff --git a/internal/bindtodevice/chanindex_linux.go b/internal/bindtodevice/chanindex_linux.go
new file mode 100644
index 0000000..3f4937f
--- /dev/null
+++ b/internal/bindtodevice/chanindex_linux.go
@@ -0,0 +1,140 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "fmt"
+ "net"
+ "net/netip"
+
+ "golang.org/x/exp/slices"
+)
+
+// chanIndex is the data structure that contains the channels, to which the
+// [Manager] sends new connections and packets based on their protocol (TCP vs.
+// UDP), and subnet.
+//
+// In both slices a subnet with the largest prefix (the narrowest subnet) is
+// sorted closer to the beginning.
+type chanIndex struct {
+ packetConns []*indexPacketConn
+ listeners []*indexListener
+}
+
+// indexPacketConn contains data of a [chanPacketConn] in the index.
+type indexPacketConn struct {
+ channel chan *packetSession
+ subnet netip.Prefix
+}
+
+// indexListener contains data of a [chanListener] in the index.
+type indexListener struct {
+ channel chan net.Conn
+ subnet netip.Prefix
+}
+
+// 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:
+ return 0
+ case subnetSortsBefore(x, y):
+ return -1
+ default:
+ return 1
+ }
+}
+
+// addPacketConnChannel adds the channel to the subnet index. It returns an
+// error if there is already one for this subnet. subnet should be masked.
+//
+// TODO(a.garipov): Merge with [addListenerChannel].
+func (idx *chanIndex) addPacketConnChannel(
+ subnet netip.Prefix,
+ ch chan *packetSession,
+) (err error) {
+ c := &indexPacketConn{
+ channel: ch,
+ subnet: subnet,
+ }
+
+ cmpFunc := func(x, y *indexPacketConn) (cmp int) {
+ return subnetCompare(x.subnet, y.subnet)
+ }
+
+ newIdx, ok := slices.BinarySearchFunc(idx.packetConns, c, cmpFunc)
+ if ok {
+ return fmt.Errorf("packetconn channel for subnet %s already registered", subnet)
+ }
+
+ // TODO(a.garipov): Consider using a list for idx.packetConns. Currently,
+ // len(listeners) is small enough for O(n) to not matter, and this method is
+ // only actively called during initialization anyway.
+ idx.packetConns = slices.Insert(idx.packetConns, newIdx, c)
+
+ return nil
+}
+
+// addListenerChannel adds the channel to the subnet index. It returns an error
+// if there is already one for this subnet. subnet should be masked.
+//
+// TODO(a.garipov): Merge with [addPacketConnChannel].
+func (idx *chanIndex) addListenerChannel(subnet netip.Prefix, ch chan net.Conn) (err error) {
+ l := &indexListener{
+ channel: ch,
+ subnet: subnet,
+ }
+
+ cmpFunc := func(x, y *indexListener) (cmp int) {
+ return subnetCompare(x.subnet, y.subnet)
+ }
+
+ newIdx, ok := slices.BinarySearchFunc(idx.listeners, l, cmpFunc)
+ if ok {
+ return fmt.Errorf("listener channel for subnet %s already registered", subnet)
+ }
+
+ // TODO(a.garipov): Consider using a list for idx.listeners. Currently,
+ // len(listeners) is small enough for O(n) to not matter, and this method is
+ // only actively called during initialization anyway.
+ idx.listeners = slices.Insert(idx.listeners, newIdx, l)
+
+ return nil
+}
+
+// packetConnChannel returns a packet-connection channel which accepts
+// connections to local address laddr or nil if there is no such channel
+func (idx *chanIndex) packetConnChannel(laddr netip.Addr) (ch chan *packetSession) {
+ for _, c := range idx.packetConns {
+ if c.subnet.Contains(laddr) {
+ return c.channel
+ }
+ }
+
+ return nil
+}
+
+// listenerChannel returns a listener channel which accepts connections to local
+// address laddr or nil if there is no such channel
+func (idx *chanIndex) listenerChannel(laddr netip.Addr) (ch chan net.Conn) {
+ for _, l := range idx.listeners {
+ if l.subnet.Contains(laddr) {
+ return l.channel
+ }
+ }
+
+ return nil
+}
diff --git a/internal/bindtodevice/chanindex_linux_test.go b/internal/bindtodevice/chanindex_linux_test.go
new file mode 100644
index 0000000..778c49e
--- /dev/null
+++ b/internal/bindtodevice/chanindex_linux_test.go
@@ -0,0 +1,29 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/exp/slices"
+)
+
+func TestSubnetSortsBefore(t *testing.T) {
+ want := []netip.Prefix{
+ netip.MustParsePrefix("1.0.0.0/24"),
+ netip.MustParsePrefix("1.2.3.0/24"),
+ netip.MustParsePrefix("1.0.0.0/16"),
+ netip.MustParsePrefix("1.2.0.0/16"),
+ }
+ got := []netip.Prefix{
+ netip.MustParsePrefix("1.0.0.0/16"),
+ netip.MustParsePrefix("1.0.0.0/24"),
+ netip.MustParsePrefix("1.2.0.0/16"),
+ netip.MustParsePrefix("1.2.3.0/24"),
+ }
+
+ slices.SortFunc(got, subnetSortsBefore)
+ assert.Equalf(t, want, got, "got (as strings): %q", got)
+}
diff --git a/internal/bindtodevice/chanlistenconfig_linux.go b/internal/bindtodevice/chanlistenconfig_linux.go
new file mode 100644
index 0000000..a6915bc
--- /dev/null
+++ b/internal/bindtodevice/chanlistenconfig_linux.go
@@ -0,0 +1,44 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "context"
+ "net"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+)
+
+// chanListenConfig 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 {
+ packetConn *chanPacketConn
+ listener *chanListener
+}
+
+// type check
+var _ netext.ListenConfig = (*chanListenConfig)(nil)
+
+// Listen implements the [netext.ListenConfig] interface for *chanListenConfig.
+func (lc *chanListenConfig) Listen(
+ ctx context.Context,
+ network string,
+ address string,
+) (l net.Listener, err error) {
+ return lc.listener, nil
+}
+
+// ListenPacket implements the [netext.ListenConfig] interface for
+// *chanListenConfig.
+func (lc *chanListenConfig) ListenPacket(
+ ctx context.Context,
+ network string,
+ address string,
+) (c net.PacketConn, err error) {
+ return lc.packetConn, nil
+}
diff --git a/internal/bindtodevice/chanlistenconfig_linux_internal_test.go b/internal/bindtodevice/chanlistenconfig_linux_internal_test.go
new file mode 100644
index 0000000..3fb0639
--- /dev/null
+++ b/internal/bindtodevice/chanlistenconfig_linux_internal_test.go
@@ -0,0 +1,32 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestChanListenConfig(t *testing.T) {
+ pc := newChanPacketConn(nil, nil, testLAddr)
+ lsnr := newChanListener(nil, testLAddr)
+ c := chanListenConfig{
+ packetConn: pc,
+ listener: lsnr,
+ }
+
+ ctx := context.Background()
+
+ gotPC, err := c.ListenPacket(ctx, "", "")
+ require.NoError(t, err)
+
+ assert.Equal(t, pc, gotPC)
+
+ gotLsnr, err := c.Listen(ctx, "", "")
+ require.NoError(t, err)
+
+ assert.Equal(t, lsnr, gotLsnr)
+}
diff --git a/internal/bindtodevice/chanlistener_linux.go b/internal/bindtodevice/chanlistener_linux.go
new file mode 100644
index 0000000..433e63b
--- /dev/null
+++ b/internal/bindtodevice/chanlistener_linux.go
@@ -0,0 +1,60 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net"
+ "sync"
+)
+
+// 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.
+type chanListener struct {
+ closeOnce *sync.Once
+ conns chan net.Conn
+ laddr net.Addr
+}
+
+// newChanListener returns a new properly initialized *chanListener.
+func newChanListener(conns chan net.Conn, laddr net.Addr) (l *chanListener) {
+ return &chanListener{
+ closeOnce: &sync.Once{},
+ conns: conns,
+ laddr: laddr,
+ }
+}
+
+// type check
+var _ net.Listener = (*chanListener)(nil)
+
+// Accept implements the [net.Listener] interface for *chanListener.
+func (l *chanListener) Accept() (c net.Conn, err error) {
+ var ok bool
+ c, ok = <-l.conns
+ if !ok {
+ return nil, wrapConnError(tnChanLsnr, "Accept", l.laddr, net.ErrClosed)
+ }
+
+ return c, nil
+}
+
+// Addr implements the [net.Listener] interface for *chanListener.
+func (l *chanListener) Addr() (addr net.Addr) { return l.laddr }
+
+// Close implements the [net.Listener] interface for *chanListener.
+func (l *chanListener) Close() (err error) {
+ closedNow := false
+ l.closeOnce.Do(func() {
+ close(l.conns)
+ closedNow = true
+ })
+
+ if !closedNow {
+ return wrapConnError(tnChanLsnr, "Close", l.laddr, net.ErrClosed)
+ }
+
+ return nil
+}
diff --git a/internal/bindtodevice/chanlistener_linux_internal_test.go b/internal/bindtodevice/chanlistener_linux_internal_test.go
new file mode 100644
index 0000000..da85c27
--- /dev/null
+++ b/internal/bindtodevice/chanlistener_linux_internal_test.go
@@ -0,0 +1,48 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestChanListener_Accept(t *testing.T) {
+ conns := make(chan net.Conn, 1)
+ l := newChanListener(conns, testLAddr)
+
+ // A simple way to have a distinct net.Conn without actually implementing
+ // the entire interface.
+ c := struct {
+ net.Conn
+ Value int
+ }{
+ Value: 1,
+ }
+
+ conns <- c
+
+ got, err := l.Accept()
+ require.NoError(t, err)
+
+ assert.Equal(t, c, got)
+}
+
+func TestChanListener_Addr(t *testing.T) {
+ l := newChanListener(nil, testLAddr)
+ got := l.Addr()
+ assert.Equal(t, testLAddr, got)
+}
+
+func TestChanListener_Close(t *testing.T) {
+ conns := make(chan net.Conn)
+ l := newChanListener(conns, testLAddr)
+ err := l.Close()
+ assert.NoError(t, err)
+
+ err = l.Close()
+ assert.Error(t, err)
+}
diff --git a/internal/bindtodevice/chanpacketconn_linux.go b/internal/bindtodevice/chanpacketconn_linux.go
new file mode 100644
index 0000000..dac6e09
--- /dev/null
+++ b/internal/bindtodevice/chanpacketconn_linux.go
@@ -0,0 +1,291 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+)
+
+// 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
+// DNS-over-UDP.
+type chanPacketConn struct {
+ closeOnce *sync.Once
+ sessions chan *packetSession
+ laddr net.Addr
+
+ // deadlineMu protects readDeadline and writeDeadline.
+ deadlineMu *sync.RWMutex
+ readDeadline time.Time
+ writeDeadline time.Time
+
+ writeRequests chan *packetConnWriteReq
+}
+
+// newChanPacketConn returns a new properly initialized *chanPacketConn.
+func newChanPacketConn(
+ sessions chan *packetSession,
+ writeRequests chan *packetConnWriteReq,
+ laddr net.Addr,
+) (c *chanPacketConn) {
+ return &chanPacketConn{
+ closeOnce: &sync.Once{},
+ sessions: sessions,
+ laddr: laddr,
+
+ deadlineMu: &sync.RWMutex{},
+
+ writeRequests: writeRequests,
+ }
+}
+
+// packetConnWriteReq is a request to write a piece of data to the original
+// packet connection. resp, body, and either raddr or session must be set.
+type packetConnWriteReq struct {
+ resp chan *packetConnWriteResp
+ session *packetSession
+ raddr net.Addr
+ deadline time.Time
+ body []byte
+}
+
+// packetConnWriteResp is a response to a [packetConnWriteReq].
+type packetConnWriteResp struct {
+ err error
+ written int
+}
+
+// type check
+var _ netext.SessionPacketConn = (*chanPacketConn)(nil)
+
+// Close implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) Close() (err error) {
+ closedNow := false
+ c.closeOnce.Do(func() {
+ close(c.sessions)
+ closedNow = true
+ })
+
+ if !closedNow {
+ return wrapConnError(tnChanPConn, "Close", c.laddr, net.ErrClosed)
+ }
+
+ return nil
+}
+
+// LocalAddr implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) LocalAddr() (addr net.Addr) { return c.laddr }
+
+// ReadFrom implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) ReadFrom(b []byte) (n int, raddr net.Addr, err error) {
+ n, sess, err := c.readFromSession(b, "ReadFrom")
+
+ return n, sess.RemoteAddr(), err
+}
+
+// ReadFromSession implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) ReadFromSession(b []byte) (n int, s netext.PacketSession, err error) {
+ return c.readFromSession(b, "ReadFromSession")
+}
+
+// readFromSession contains the common code of [ReadFrom] and [ReadFromSession].
+func (c *chanPacketConn) readFromSession(
+ b []byte,
+ fnName string,
+) (n int, s netext.PacketSession, err error) {
+ var deadline time.Time
+ func() {
+ c.deadlineMu.RLock()
+ defer c.deadlineMu.RUnlock()
+
+ deadline = c.readDeadline
+ }()
+
+ timerCh, stopTimer, err := timerFromDeadline(deadline)
+ if err != nil {
+ err = fmt.Errorf("setting deadline: %w", err)
+
+ return 0, nil, wrapConnError(tnChanPConn, fnName, c.laddr, err)
+ }
+ defer stopTimer()
+
+ sess, err := receiveWithTimer(c.sessions, timerCh)
+ if err != nil {
+ err = fmt.Errorf("receiving: %w", err)
+
+ return 0, nil, wrapConnError(tnChanPConn, fnName, c.laddr, err)
+ }
+
+ n = copy(b, sess.readBody)
+
+ return n, sess, nil
+}
+
+// timerFromDeadline converts a deadline value into a timer channel. stopTimer
+// must be deferred by the caller.
+func timerFromDeadline(deadline time.Time) (timerCh <-chan time.Time, stopTimer func(), err error) {
+ if deadline.IsZero() {
+ return nil, func() {}, nil
+ }
+
+ d := time.Until(deadline)
+ if d <= 0 {
+ return nil, func() {}, os.ErrDeadlineExceeded
+ }
+
+ timer := time.NewTimer(time.Until(deadline))
+ timerCh = timer.C
+ stopTimer = func() {
+ if !timer.Stop() {
+ // We don't know if the timer's value has been consumed yet or not,
+ // so use a select with default to make sure that this doesn't
+ // block.
+ select {
+ case <-timerCh:
+ default:
+ }
+ }
+ }
+
+ return timerCh, stopTimer, nil
+}
+
+// SetDeadline implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) SetDeadline(t time.Time) (err error) {
+ c.deadlineMu.Lock()
+ defer c.deadlineMu.Unlock()
+
+ c.readDeadline = t
+ c.writeDeadline = t
+
+ return nil
+}
+
+// SetReadDeadline implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) SetReadDeadline(t time.Time) (err error) {
+ c.deadlineMu.Lock()
+ defer c.deadlineMu.Unlock()
+
+ c.readDeadline = t
+
+ return nil
+}
+
+// SetWriteDeadline implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) SetWriteDeadline(t time.Time) (err error) {
+ c.deadlineMu.Lock()
+ defer c.deadlineMu.Unlock()
+
+ c.writeDeadline = t
+
+ return nil
+}
+
+// WriteTo implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) WriteTo(b []byte, raddr net.Addr) (n int, err error) {
+ return c.writeToSession(b, nil, raddr, "WriteTo")
+}
+
+// WriteToSession implements the [netext.SessionPacketConn] interface for
+// *chanPacketConn.
+func (c *chanPacketConn) WriteToSession(
+ b []byte,
+ s netext.PacketSession,
+) (n int, err error) {
+ return c.writeToSession(b, s.(*packetSession), nil, "WriteToSession")
+}
+
+// writeToSession contains the common code of [WriteTo] and [WriteToSession].
+func (c *chanPacketConn) writeToSession(
+ b []byte,
+ s *packetSession,
+ raddr net.Addr,
+ fnName string,
+) (n int, err error) {
+ var deadline time.Time
+ func() {
+ c.deadlineMu.RLock()
+ defer c.deadlineMu.RUnlock()
+
+ deadline = c.writeDeadline
+ }()
+
+ timerCh, stopTimer, err := timerFromDeadline(deadline)
+ if err != nil {
+ err = fmt.Errorf("setting deadline: %w", err)
+
+ return 0, wrapConnError(tnChanPConn, fnName, c.laddr, err)
+ }
+ defer stopTimer()
+
+ resp := make(chan *packetConnWriteResp, 1)
+ req := &packetConnWriteReq{
+ resp: resp,
+ session: s,
+ raddr: raddr,
+ deadline: deadline,
+ body: b,
+ }
+ err = sendWithTimer(c.writeRequests, req, timerCh)
+ if err != nil {
+ err = fmt.Errorf("sending write request: %w", err)
+
+ return 0, wrapConnError(tnChanPConn, fnName, c.laddr, err)
+ }
+
+ r, err := receiveWithTimer(resp, timerCh)
+ if err != nil {
+ err = fmt.Errorf("receiving write response: %w", err)
+
+ return 0, wrapConnError(tnChanPConn, fnName, c.laddr, err)
+ }
+
+ return r.written, r.err
+}
+
+// receiveWithTimer is a helper function that uses a timer channel to indicate
+// that a receive did not succeed in time. If the channel is closed, err is
+// [net.ErrClosed]. If the receive from timerCh succeeded first, err is
+// [os.ErrDeadlineExceeded].
+func receiveWithTimer[T any](ch <-chan T, timerCh <-chan time.Time) (v T, err error) {
+ var ok bool
+ select {
+ case v, ok = <-ch:
+ if !ok {
+ err = net.ErrClosed
+ }
+ case <-timerCh:
+ err = os.ErrDeadlineExceeded
+ }
+
+ return v, err
+}
+
+// sendWithTimer is a helper function that uses a timer channel to indicate that
+// a send did not succeed in time. If the receive from timerCh succeeded first,
+// err is [os.ErrDeadlineExceeded].
+func sendWithTimer[T any](ch chan<- T, v T, timerCh <-chan time.Time) (err error) {
+ select {
+ case ch <- v:
+ return nil
+ case <-timerCh:
+ return os.ErrDeadlineExceeded
+ }
+}
diff --git a/internal/bindtodevice/chanpacketconn_linux_internal_test.go b/internal/bindtodevice/chanpacketconn_linux_internal_test.go
new file mode 100644
index 0000000..7ee22b1
--- /dev/null
+++ b/internal/bindtodevice/chanpacketconn_linux_internal_test.go
@@ -0,0 +1,192 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net"
+ "testing"
+ "time"
+
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestChanPacketConn_Close(t *testing.T) {
+ sessions := make(chan *packetSession)
+ c := newChanPacketConn(sessions, nil, testLAddr)
+ err := c.Close()
+ assert.NoError(t, err)
+
+ err = c.Close()
+ assert.Error(t, err)
+}
+
+func TestChanPacketConn_LocalAddr(t *testing.T) {
+ c := newChanPacketConn(nil, nil, testLAddr)
+ got := c.LocalAddr()
+ assert.Equal(t, testLAddr, got)
+}
+
+func TestChanPacketConn_ReadFromSession(t *testing.T) {
+ sessions := make(chan *packetSession, 1)
+ c := newChanPacketConn(sessions, nil, testLAddr)
+
+ body := []byte("hello")
+ bodyLen := len(body)
+
+ respOOB := []byte("not a real response oob")
+
+ ps := &packetSession{
+ laddr: testLAddr,
+ raddr: testRAddr,
+ readBody: body,
+ respOOB: respOOB,
+ }
+
+ sessions <- ps
+
+ deadline := time.Now().Add(testTimeout)
+ err := c.SetReadDeadline(deadline)
+ require.NoError(t, err)
+
+ b := make([]byte, bodyLen)
+ n, sess, err := c.ReadFromSession(b)
+ require.NoError(t, err)
+
+ assert.Equal(t, bodyLen, n)
+ assert.Equal(t, body, b)
+
+ require.NotNil(t, sess)
+
+ s := testutil.RequireTypeAssert[*packetSession](t, sess)
+ assert.Equal(t, respOOB, s.respOOB)
+
+ sessions <- ps
+
+ b = make([]byte, bodyLen)
+ n, raddr, err := c.ReadFrom(b)
+ require.NoError(t, err)
+
+ assert.Equal(t, bodyLen, n)
+ assert.Equal(t, body, b)
+
+ require.NotNil(t, raddr)
+
+ assert.Equal(t, testRAddr, raddr)
+}
+
+func TestChanPacketConn_WriteToSession(t *testing.T) {
+ sessions := make(chan *packetSession, 1)
+ writes := make(chan *packetConnWriteReq, 1)
+ c := newChanPacketConn(sessions, writes, testLAddr)
+
+ body := []byte("hello")
+ bodyLen := len(body)
+
+ respOOB := []byte("not a real response oob")
+
+ ps := &packetSession{
+ laddr: testLAddr,
+ raddr: testRAddr,
+ readBody: nil,
+ respOOB: respOOB,
+ }
+
+ deadline := time.Now().Add(testTimeout)
+ err := c.SetWriteDeadline(deadline)
+ require.NoError(t, err)
+
+ go checkWriteReqAndRespond(writes, nil, body, respOOB, deadline)
+
+ n, err := c.WriteToSession(body, ps)
+ require.NoError(t, err)
+
+ assert.Equal(t, bodyLen, n)
+
+ go checkWriteReqAndRespond(writes, testRAddr, body, nil, deadline)
+
+ n, err = c.WriteTo(body, testRAddr)
+ require.NoError(t, err)
+
+ assert.Equal(t, bodyLen, n)
+}
+
+// checkWriteReqAndRespond is a test helper that receives data from writes,
+// checks it against the required values, and sends back a response.
+func checkWriteReqAndRespond(
+ writes chan *packetConnWriteReq,
+ wantRaddr *net.UDPAddr,
+ wantBody []byte,
+ wantRespOOB []byte,
+ wantDeadline time.Time,
+) {
+ pt := testutil.PanicT{}
+
+ req, ok := testutil.RequireReceive(pt, writes, testTimeout)
+ require.NotNil(pt, req)
+ require.NotNil(pt, req.resp)
+ require.True(pt, ok)
+
+ if wantRaddr != nil {
+ assert.Nil(pt, req.session)
+ assert.Equal(pt, testRAddr, req.raddr)
+ } else {
+ require.NotNil(pt, req.session)
+
+ assert.Equal(pt, wantRespOOB, req.session.respOOB)
+ assert.Nil(pt, req.raddr)
+ }
+
+ assert.Equal(pt, wantDeadline, req.deadline)
+ assert.Equal(pt, wantBody, req.body)
+
+ testutil.RequireSend(pt, req.resp, &packetConnWriteResp{
+ err: nil,
+ written: len(wantBody),
+ }, testTimeout)
+}
+
+func TestChanPacketConn_deadlines(t *testing.T) {
+ c := newChanPacketConn(nil, nil, testLAddr)
+ deadline := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
+
+ testCases := []struct {
+ f func(deadline time.Time) (err error)
+ deadline time.Time
+ wantReadDeadline time.Time
+ wantWriteDeadline time.Time
+ name string
+ }{{
+ f: c.SetReadDeadline,
+ deadline: deadline,
+ wantReadDeadline: deadline,
+ wantWriteDeadline: time.Time{},
+ name: "read",
+ }, {
+ f: c.SetWriteDeadline,
+ deadline: deadline,
+ wantReadDeadline: time.Time{},
+ wantWriteDeadline: deadline,
+ name: "write",
+ }, {
+ f: c.SetDeadline,
+ deadline: deadline,
+ wantReadDeadline: deadline,
+ wantWriteDeadline: deadline,
+ name: "both",
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err := c.SetDeadline(time.Time{})
+ require.NoError(t, err)
+
+ err = tc.f(tc.deadline)
+ require.NoError(t, err)
+
+ assert.Equal(t, tc.wantReadDeadline, c.readDeadline)
+ assert.Equal(t, tc.wantWriteDeadline, c.writeDeadline)
+ })
+ }
+}
diff --git a/internal/bindtodevice/interfacelistener_linux.go b/internal/bindtodevice/interfacelistener_linux.go
new file mode 100644
index 0000000..197b357
--- /dev/null
+++ b/internal/bindtodevice/interfacelistener_linux.go
@@ -0,0 +1,174 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "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 {
+ channels *chanIndex
+ writeRequests chan *packetConnWriteReq
+ done chan unit
+ listenConf *net.ListenConfig
+ errColl agd.ErrorCollector
+ ifaceName string
+ port uint16
+}
+
+// listenTCP runs the TCP listening loop. It is intended to be used as a
+// goroutine. errCh receives nil if the listening has started successfully or
+// the listening error if not.
+func (l *interfaceListener) listenTCP(errCh chan<- error) {
+ defer log.OnPanic("interfaceListener.listenTCP")
+
+ ctx := context.Background()
+ addrStr := netutil.JoinHostPort("0.0.0.0", int(l.port))
+ tcpListener, err := l.listenConf.Listen(ctx, "tcp", addrStr)
+
+ errCh <- err
+ if err != nil {
+ return
+ }
+
+ logPrefix := fmt.Sprintf("bindtodevice: listener %s:%d: tcp", l.ifaceName, l.port)
+
+ log.Info("%s: starting", logPrefix)
+
+ for {
+ select {
+ case <-l.done:
+ log.Info("%s: done", logPrefix)
+
+ return
+ default:
+ // Go on.
+ }
+
+ var conn net.Conn
+ conn, err = tcpListener.Accept()
+ if err != nil {
+ agd.Collectf(ctx, l.errColl, "%s: accepting: %w", logPrefix, err)
+
+ continue
+ }
+
+ laddr := netutil.NetAddrToAddrPort(conn.LocalAddr())
+ ch := l.channels.listenerChannel(laddr.Addr())
+ if ch == nil {
+ log.Info("%s: no channel for laddr %s", logPrefix, laddr)
+
+ continue
+ }
+
+ ch <- conn
+ }
+}
+
+// listenUDP runs the UDP listening loop. It is intended to be used as a
+// goroutine. errCh receives nil if the listening has started successfully or
+// the listening error if not.
+func (l *interfaceListener) listenUDP(errCh chan<- error) {
+ defer log.OnPanic("interfaceListener.listenUDP")
+
+ ctx := context.Background()
+ addrStr := netutil.JoinHostPort("0.0.0.0", int(l.port))
+ packetConn, err := l.listenConf.ListenPacket(ctx, "udp", addrStr)
+ if err != nil {
+ errCh <- err
+
+ return
+ }
+
+ udpConn := packetConn.(*net.UDPConn)
+
+ errCh <- nil
+
+ go l.writeUDP(udpConn)
+
+ logPrefix := fmt.Sprintf("bindtodevice: listener %s:%d: udp", l.ifaceName, l.port)
+
+ log.Info("%s: starting", logPrefix)
+
+ for {
+ select {
+ case <-l.done:
+ log.Info("%s: done", logPrefix)
+
+ return
+ default:
+ // Go on.
+ }
+
+ // TODO(a.garipov): Consider customization of body sizes.
+ var sess *packetSession
+ sess, err = readPacketSession(udpConn, dns.DefaultMsgSize)
+ if err != nil {
+ agd.Collectf(ctx, l.errColl, "%s: reading session: %w", logPrefix, err)
+
+ continue
+ }
+
+ laddr := sess.laddr.AddrPort().Addr()
+ ch := l.channels.packetConnChannel(laddr)
+ if ch == nil {
+ log.Info("%s: no channel for laddr %s", logPrefix, laddr)
+
+ continue
+ }
+
+ ch <- sess
+ }
+}
+
+// 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")
+
+ logPrefix := fmt.Sprintf("bindtodevice: listener %s:%d: udp write", l.ifaceName, l.port)
+ for {
+ var req *packetConnWriteReq
+ select {
+ case <-l.done:
+ log.Info("%s: done", logPrefix)
+
+ return
+ case req = <-l.writeRequests:
+ // Go on.
+ }
+
+ resp := &packetConnWriteResp{}
+ resp.err = c.SetWriteDeadline(req.deadline)
+ if resp.err != nil {
+ req.resp <- resp
+
+ continue
+ }
+
+ if s := req.session; s == nil {
+ resp.written, resp.err = c.WriteTo(req.body, req.raddr)
+ } else {
+ resp.written, _, resp.err = c.WriteMsgUDP(
+ req.body,
+ s.respOOB,
+ req.session.raddr,
+ )
+ }
+
+ resetDeadlineErr := c.SetWriteDeadline(time.Time{})
+
+ resp.err = errors.WithDeferred(resp.err, resetDeadlineErr)
+
+ req.resp <- resp
+ }
+}
diff --git a/internal/bindtodevice/manager.go b/internal/bindtodevice/manager.go
new file mode 100644
index 0000000..3eebd4c
--- /dev/null
+++ b/internal/bindtodevice/manager.go
@@ -0,0 +1,15 @@
+package bindtodevice
+
+import "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+
+// ManagerConfig is the configuration structure for [NewManager]. All fields
+// must be set.
+type ManagerConfig struct {
+ // ErrColl is the error collector that is used to collect non-critical
+ // errors.
+ ErrColl agd.ErrorCollector
+
+ // ChannelBufferSize is the size of the buffers of the channels used to
+ // dispatch TCP connections and UDP sessions.
+ ChannelBufferSize int
+}
diff --git a/internal/bindtodevice/manager_linux.go b/internal/bindtodevice/manager_linux.go
new file mode 100644
index 0000000..13c79fb
--- /dev/null
+++ b/internal/bindtodevice/manager_linux.go
@@ -0,0 +1,180 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/netip"
+ "sync"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/errors"
+ "github.com/AdguardTeam/golibs/log"
+)
+
+// Manager creates individual listeners and dispatches connections to them.
+type Manager struct {
+ closeOnce *sync.Once
+ ifaceListeners map[ID]*interfaceListener
+ errColl agd.ErrorCollector
+ done chan unit
+ chanBufSize int
+}
+
+// NewManager returns a new manager of interface listeners.
+func NewManager(c *ManagerConfig) (m *Manager) {
+ return &Manager{
+ closeOnce: &sync.Once{},
+ ifaceListeners: map[ID]*interfaceListener{},
+ errColl: c.ErrColl,
+ done: make(chan unit),
+ chanBufSize: c.ChannelBufferSize,
+ }
+}
+
+// Add creates a new interface-listener record in m.
+//
+// Add must not be called after Start is called.
+func (m *Manager) Add(id ID, ifaceName string, port uint16) (err error) {
+ defer func() { err = errors.Annotate(err, "adding interface listener with id %q: %w", id) }()
+
+ validateDup := func(lsnrID ID, lsnr *interfaceListener) (lsnrErr error) {
+ lsnrIfaceName, lsnrPort := lsnr.ifaceName, lsnr.port
+ if lsnrID == id {
+ return fmt.Errorf(
+ "listener for interface with id %q already exists: %s:%d",
+ lsnrID,
+ lsnrIfaceName,
+ lsnrPort,
+ )
+ }
+
+ if lsnrIfaceName == ifaceName && lsnrPort == port {
+ return fmt.Errorf(
+ "listener for %s:%d already exists with id %q",
+ lsnrIfaceName,
+ lsnrPort,
+ lsnrID,
+ )
+ }
+
+ return nil
+ }
+
+ err = agdmaps.OrderedRangeError(m.ifaceListeners, validateDup)
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return err
+ }
+
+ m.ifaceListeners[id] = &interfaceListener{
+ channels: &chanIndex{},
+ writeRequests: make(chan *packetConnWriteReq, m.chanBufSize),
+ done: m.done,
+ listenConf: newListenConfig(ifaceName),
+ errColl: m.errColl,
+ ifaceName: ifaceName,
+ port: 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.
+//
+// ListenConfig must not be called after Start is called.
+func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c netext.ListenConfig, err error) {
+ if masked := subnet.Masked(); subnet != masked {
+ return nil, fmt.Errorf(
+ "subnet %s for interface listener %q not masked (expected %s)",
+ subnet,
+ id,
+ masked,
+ )
+ }
+
+ l, ok := m.ifaceListeners[id]
+ if !ok {
+ return nil, fmt.Errorf("no listener for interface %q", id)
+ }
+
+ connCh := make(chan net.Conn, m.chanBufSize)
+ err = l.channels.addListenerChannel(subnet, connCh)
+ if err != nil {
+ return nil, fmt.Errorf("adding tcp conn channel: %w", err)
+ }
+
+ sessCh := make(chan *packetSession, m.chanBufSize)
+ err = l.channels.addPacketConnChannel(subnet, sessCh)
+ if err != nil {
+ // Technically shouldn't happen, since [chanIndex.addListenerChannel]
+ // has already checked for duplicates.
+ return nil, fmt.Errorf("adding udp conn channel: %w", err)
+ }
+
+ return &chanListenConfig{
+ packetConn: newChanPacketConn(sessCh, l.writeRequests, &prefixNetAddr{
+ prefix: subnet,
+ network: "udp",
+ port: l.port,
+ }),
+ listener: newChanListener(connCh, &prefixNetAddr{
+ prefix: subnet,
+ network: "tcp",
+ port: l.port,
+ }),
+ }, nil
+}
+
+// type check
+var _ agd.Service = (*Manager)(nil)
+
+// Start implements the [agd.Service] interface for *Manager.
+func (m *Manager) Start() (err error) {
+ numListen := 2 * len(m.ifaceListeners)
+ errCh := make(chan error, numListen)
+
+ log.Info("bindtodevice: starting %d listeners", numListen)
+
+ for _, lsnr := range m.ifaceListeners {
+ go lsnr.listenTCP(errCh)
+ go lsnr.listenUDP(errCh)
+ }
+
+ errs := make([]error, numListen)
+ for i := range errs {
+ errs[i] = <-errCh
+ }
+
+ err = errors.Join(errs...)
+ if err != nil {
+ return fmt.Errorf("starting bindtodevice manager: %w", err)
+ }
+
+ log.Info("bindtodevice: started all %d listeners", numListen)
+
+ return nil
+}
+
+// Shutdown implements the [agd.Service] interface for *Manager.
+//
+// TODO(a.garipov): Consider waiting for all sockets to close.
+func (m *Manager) Shutdown(_ context.Context) (err error) {
+ closedNow := false
+ m.closeOnce.Do(func() {
+ close(m.done)
+ closedNow = true
+ })
+
+ if !closedNow {
+ return net.ErrClosed
+ }
+
+ return nil
+}
diff --git a/internal/bindtodevice/manager_linux_test.go b/internal/bindtodevice/manager_linux_test.go
new file mode 100644
index 0000000..e33ea39
--- /dev/null
+++ b/internal/bindtodevice/manager_linux_test.go
@@ -0,0 +1,148 @@
+//go:build linux
+
+package bindtodevice_test
+
+import (
+ "context"
+ "net"
+ "net/netip"
+ "testing"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
+ "github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
+ "github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TODO(a.garipov): Add tests for other platforms?
+
+func TestManager_Add(t *testing.T) {
+ errColl := &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, _ error) { panic("not implemented") },
+ }
+
+ m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
+ ErrColl: errColl,
+ ChannelBufferSize: 1,
+ })
+ require.NotNil(t, m)
+
+ // Don't use a table, since the results of these subtests depend on each
+ // other.
+ t.Run("success", func(t *testing.T) {
+ err := m.Add(testID1, testIfaceName, testPort1)
+ assert.NoError(t, err)
+ })
+
+ t.Run("dup_id", func(t *testing.T) {
+ err := m.Add(testID1, testIfaceName, testPort1)
+ assert.Error(t, err)
+ })
+
+ t.Run("dup_iface_port", func(t *testing.T) {
+ err := m.Add(testID2, testIfaceName, testPort1)
+ assert.Error(t, err)
+ })
+
+ t.Run("success_other", func(t *testing.T) {
+ err := m.Add(testID2, testIfaceName, testPort2)
+ assert.NoError(t, err)
+ })
+}
+
+func TestManager_ListenConfig(t *testing.T) {
+ errColl := &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, _ error) { panic("not implemented") },
+ }
+
+ m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
+ ErrColl: errColl,
+ ChannelBufferSize: 1,
+ })
+ require.NotNil(t, m)
+
+ err := m.Add(testID1, testIfaceName, testPort1)
+ require.NoError(t, err)
+
+ subnet := netip.MustParsePrefix("1.2.3.0/24")
+
+ // Don't use a table, since the results of these subtests depend on each
+ // other.
+ t.Run("not_found", func(t *testing.T) {
+ lc, lcErr := m.ListenConfig(testID2, subnet)
+ assert.Nil(t, lc)
+ assert.Error(t, lcErr)
+ })
+
+ t.Run("unmasked", func(t *testing.T) {
+ badSubnet := netip.MustParsePrefix("1.2.3.4/24")
+ lc, lcErr := m.ListenConfig(testID1, badSubnet)
+ assert.Nil(t, lc)
+ assert.Error(t, lcErr)
+ })
+
+ t.Run("success", func(t *testing.T) {
+ lc, lcErr := m.ListenConfig(testID1, subnet)
+ assert.NotNil(t, lc)
+ assert.NoError(t, lcErr)
+ })
+
+ t.Run("dup", func(t *testing.T) {
+ lc, lcErr := m.ListenConfig(testID1, subnet)
+ assert.Nil(t, lc)
+ assert.Error(t, lcErr)
+ })
+}
+
+func TestManager(t *testing.T) {
+ iface, ifaceNet := bindtodevice.InterfaceForTests(t)
+ if iface == nil {
+ t.Skipf(
+ "test %s skipped: please set env var %s",
+ t.Name(),
+ bindtodevice.TestInterfaceEnvVarName,
+ )
+ }
+
+ ifaceName := iface.Name
+
+ errColl := &agdtest.ErrorCollector{
+ OnCollect: func(_ context.Context, _ error) { panic("not implemented") },
+ }
+
+ m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
+ ErrColl: errColl,
+ ChannelBufferSize: 1,
+ })
+ require.NotNil(t, m)
+
+ // TODO(a.garipov): Add support for zero port.
+ err := m.Add(testID1, ifaceName, testPort1)
+ require.NoError(t, err)
+
+ subnet, err := netutil.IPNetToPrefixNoMapped(&net.IPNet{
+ IP: ifaceNet.IP.Mask(ifaceNet.Mask),
+ Mask: ifaceNet.Mask,
+ })
+ require.NoError(t, err)
+
+ lc, err := m.ListenConfig(testID1, subnet)
+ require.NoError(t, err)
+ require.NotNil(t, lc)
+
+ err = m.Start()
+ require.NoError(t, err)
+ testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return m.Shutdown(context.Background())
+ })
+
+ t.Run("tcp", func(t *testing.T) {
+ bindtodevice.SubtestListenControlTCP(t, lc, ifaceName, ifaceNet)
+ })
+
+ t.Run("udp", func(t *testing.T) {
+ bindtodevice.SubtestListenControlUDP(t, lc, ifaceName, ifaceNet)
+ })
+}
diff --git a/internal/bindtodevice/manager_others.go b/internal/bindtodevice/manager_others.go
new file mode 100644
index 0000000..457e481
--- /dev/null
+++ b/internal/bindtodevice/manager_others.go
@@ -0,0 +1,55 @@
+//go:build !linux
+
+package bindtodevice
+
+import (
+ "context"
+ "net/netip"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/errors"
+)
+
+// Manager creates individual listeners and dispatches connections to them.
+//
+// It is only suported on Linux.
+type Manager struct{}
+
+// NewManager returns a new manager of interface listeners.
+//
+// It is only suported on Linux.
+func NewManager(c *ManagerConfig) (m *Manager) {
+ return &Manager{}
+}
+
+// errUnsupported is returned from all [Manager] methods on OSs other than
+// Linux.
+const errUnsupported errors.Error = "bindtodevice is only supported on linux"
+
+// Add creates a new interface-listener record in m.
+//
+// It is only suported on Linux.
+func (m *Manager) Add(id ID, ifaceName string, port uint16) (err error) { 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.
+//
+// It is only suported on Linux.
+func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c netext.ListenConfig, err error) {
+ return nil, errUnsupported
+}
+
+// type check
+var _ agd.Service = (*Manager)(nil)
+
+// Start implements the [agd.Service] interface for *Manager.
+//
+// It is only suported on Linux.
+func (m *Manager) Start() (err error) { return errUnsupported }
+
+// Shutdown implements the [agd.Service] interface for *Manager.
+//
+// It is only suported on Linux.
+func (m *Manager) Shutdown(_ context.Context) (err error) { return errUnsupported }
diff --git a/internal/bindtodevice/packetsession_linux.go b/internal/bindtodevice/packetsession_linux.go
new file mode 100644
index 0000000..e127c70
--- /dev/null
+++ b/internal/bindtodevice/packetsession_linux.go
@@ -0,0 +1,29 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+)
+
+// packetSession is a [netext.PacketSession] that contains additional
+// information about the packet read from a UDP connection that has the
+// SO_BINDTODEVICE option set.
+type packetSession struct {
+ laddr *net.UDPAddr
+ raddr *net.UDPAddr
+ readBody []byte
+ respOOB []byte
+}
+
+// type check
+var _ netext.PacketSession = (*packetSession)(nil)
+
+// LocalAddr implements the [netext.PacketSession] interface for *packetSession.
+func (s *packetSession) LocalAddr() (addr net.Addr) { return s.laddr }
+
+// RemoteAddr implements the [netext.PacketSession] interface for
+// *packetSession.
+func (s *packetSession) RemoteAddr() (addr net.Addr) { return s.raddr }
diff --git a/internal/bindtodevice/prefixaddr_linux.go b/internal/bindtodevice/prefixaddr_linux.go
new file mode 100644
index 0000000..aa8f9e6
--- /dev/null
+++ b/internal/bindtodevice/prefixaddr_linux.go
@@ -0,0 +1,37 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "fmt"
+ "net"
+ "net/netip"
+)
+
+// prefixNetAddr is a wrapper around netip.Prefix that makes it a [net.Addr].
+//
+// TODO(a.garipov): Support port 0, which will probably require atomic
+// operations and assistance in [Manager.Start].
+type prefixNetAddr struct {
+ prefix netip.Prefix
+ network string
+ port uint16
+}
+
+// type check
+var _ net.Addr = (*prefixNetAddr)(nil)
+
+// String implements the [net.Addr] interface for *prefixNetAddr. 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].
+func (addr *prefixNetAddr) String() (n string) {
+ return fmt.Sprintf(
+ "%s/%d",
+ netip.AddrPortFrom(addr.prefix.Addr(), addr.port),
+ addr.prefix.Bits(),
+ )
+}
+
+// Network implements the [net.Addr] interface for *prefixNetAddr.
+func (addr *prefixNetAddr) Network() (n string) { return addr.network }
diff --git a/internal/bindtodevice/prefixaddr_linux_internal_test.go b/internal/bindtodevice/prefixaddr_linux_internal_test.go
new file mode 100644
index 0000000..bc95326
--- /dev/null
+++ b/internal/bindtodevice/prefixaddr_linux_internal_test.go
@@ -0,0 +1,26 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "net/netip"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPrefixAddr(t *testing.T) {
+ const (
+ wantStr = "1.2.3.0:56789/24"
+ network = "tcp"
+ )
+
+ pa := &prefixNetAddr{
+ prefix: netip.MustParsePrefix("1.2.3.0/24"),
+ network: network,
+ port: 56789,
+ }
+
+ assert.Equal(t, wantStr, pa.String())
+ assert.Equal(t, network, pa.Network())
+}
diff --git a/internal/bindtodevice/socket_linux.go b/internal/bindtodevice/socket_linux.go
new file mode 100644
index 0000000..122fa17
--- /dev/null
+++ b/internal/bindtodevice/socket_linux.go
@@ -0,0 +1,190 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "fmt"
+ "net"
+ "syscall"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/errors"
+ "golang.org/x/sys/unix"
+)
+
+// newListenConfig returns a [net.ListenConfig] that can bind to a network
+// interface (device) by its name.
+func newListenConfig(devName string) (lc *net.ListenConfig) {
+ c := &net.ListenConfig{
+ Control: func(network, address string, c syscall.RawConn) (err error) {
+ return listenControl(devName, network, address, c)
+ },
+ }
+
+ return c
+}
+
+// listenControl is used as a [net.ListenConfig.Control] function to set
+// additional socket options, including SO_BINDTODEVICE.
+func listenControl(devName, network, _ string, c syscall.RawConn) (err error) {
+ var ctrlFunc func(fd uintptr, devName string) (err error)
+
+ switch network {
+ case "tcp", "tcp4", "tcp6":
+ ctrlFunc = setTCPSockOpt
+ case "udp", "udp4", "udp6":
+ ctrlFunc = setUDPSockOpt
+ default:
+ return fmt.Errorf("bad network %q", network)
+ }
+
+ var opErr error
+ err = c.Control(func(fd uintptr) {
+ opErr = ctrlFunc(fd, devName)
+ })
+
+ return errors.WithDeferred(opErr, err)
+}
+
+// setTCPSockOpt sets the SO_BINDTODEVICE and other socket options for a TCP
+// connection.
+func setTCPSockOpt(fd uintptr, devName string) (err error) {
+ defer func() { err = errors.Annotate(err, "setting tcp opts: %w") }()
+
+ fdInt := int(fd)
+ err = unix.SetsockoptString(fdInt, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, devName)
+ if err != nil {
+ return fmt.Errorf("setting SO_BINDTODEVICE: %w", err)
+ }
+
+ err = unix.SetsockoptInt(fdInt, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+ if err != nil {
+ return fmt.Errorf("setting SO_REUSEPORT: %w", err)
+ }
+
+ return nil
+}
+
+// setUDPSockOpt sets the SO_BINDTODEVICE and other socket options for a UDP
+// connection.
+func setUDPSockOpt(fd uintptr, devName string) (err error) {
+ defer func() { err = errors.Annotate(err, "setting udp opts: %w") }()
+
+ fdInt := int(fd)
+ err = unix.SetsockoptString(fdInt, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, devName)
+ if err != nil {
+ return fmt.Errorf("setting SO_BINDTODEVICE: %w", err)
+ }
+
+ intOpts := []struct {
+ name string
+ level int
+ opt int
+ }{{
+ name: "SO_REUSEPORT",
+ level: unix.SOL_SOCKET,
+ opt: unix.SO_REUSEPORT,
+ }, {
+ name: "IP_RECVORIGDSTADDR",
+ level: unix.IPPROTO_IP,
+ opt: unix.IP_RECVORIGDSTADDR,
+ }, {
+ name: "IP_FREEBIND",
+ level: unix.IPPROTO_IP,
+ opt: unix.IP_FREEBIND,
+ }, {
+ name: "IPV6_RECVORIGDSTADDR",
+ level: unix.IPPROTO_IPV6,
+ opt: unix.IPV6_RECVORIGDSTADDR,
+ }, {
+ name: "IPV6_FREEBIND",
+ level: unix.IPPROTO_IPV6,
+ opt: unix.IPV6_FREEBIND,
+ }}
+
+ for _, o := range intOpts {
+ err = unix.SetsockoptInt(fdInt, o.level, o.opt, 1)
+ if err != nil {
+ return fmt.Errorf("setting %s: %w", o.name, err)
+ }
+ }
+
+ return nil
+}
+
+// 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)
+ if err != nil {
+ return nil, fmt.Errorf("reading: %w", err)
+ }
+
+ var ctrlMsgs []unix.SocketControlMessage
+ ctrlMsgs, err = unix.ParseSocketControlMessage(oob[:oobn])
+ if err != nil {
+ return nil, fmt.Errorf("parsing ctrl messages: %w", err)
+ }
+
+ if l := len(ctrlMsgs); l != 1 {
+ return nil, fmt.Errorf("expected 1 ctrl message, got %d", l)
+ }
+
+ ctrlMsg := ctrlMsgs[0]
+ origDstSockAddr, err := unix.ParseOrigDstAddr(&ctrlMsg)
+ if err != nil {
+ return nil, fmt.Errorf("parsing orig dst: %w", err)
+ }
+
+ origDstAddr, respOOB, err := sockAddrData(origDstSockAddr)
+ if err != nil {
+ return nil, err
+ }
+
+ sess = &packetSession{
+ laddr: origDstAddr,
+ raddr: raddr,
+ readBody: b[:n],
+ respOOB: respOOB,
+ }
+
+ return sess, nil
+}
+
+// sockAddrData converts the provided socket address into a UDP address as well
+// as encodes the response packet information.
+func sockAddrData(sockAddr unix.Sockaddr) (origDstAddr *net.UDPAddr, respOOB []byte, err error) {
+ switch sockAddr := sockAddr.(type) {
+ case *unix.SockaddrInet4:
+ origDstAddr = &net.UDPAddr{
+ IP: sockAddr.Addr[:],
+ Port: sockAddr.Port,
+ }
+
+ pktInfo := &unix.Inet4Pktinfo{
+ Addr: sockAddr.Addr,
+ }
+
+ respOOB = unix.PktInfo4(pktInfo)
+ case *unix.SockaddrInet6:
+ origDstAddr = &net.UDPAddr{
+ IP: sockAddr.Addr[:],
+ Port: sockAddr.Port,
+ }
+
+ pktInfo := &unix.Inet6Pktinfo{
+ Addr: sockAddr.Addr,
+ Ifindex: sockAddr.ZoneId,
+ }
+
+ respOOB = unix.PktInfo6(pktInfo)
+ default:
+ return nil, nil, fmt.Errorf("bad orig dst sockaddr type %T", sockAddr)
+ }
+
+ return origDstAddr, respOOB, nil
+}
diff --git a/internal/bindtodevice/socket_linux_internal_test.go b/internal/bindtodevice/socket_linux_internal_test.go
new file mode 100644
index 0000000..c2bd054
--- /dev/null
+++ b/internal/bindtodevice/socket_linux_internal_test.go
@@ -0,0 +1,382 @@
+//go:build linux
+
+package bindtodevice
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/netip"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
+ "github.com/AdguardTeam/golibs/netutil"
+ "github.com/AdguardTeam/golibs/testutil"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/exp/slices"
+)
+
+// TestInterfaceEnvVarName is the environment variable name the presence and
+// value of which define whether to run the SO_BINDTODEVICE tests and on which
+// network interface.
+const TestInterfaceEnvVarName = "ADGUARD_DNS_TEST_NET_INTERFACE"
+
+// InterfaceForTests returns the network interface designated for tests, if
+// any, as well as its first network.
+func InterfaceForTests(t testing.TB) (iface *net.Interface, ifaceNet *net.IPNet) {
+ t.Helper()
+
+ ifaceName, ok := os.LookupEnv(TestInterfaceEnvVarName)
+ if !ok {
+ return nil, nil
+ }
+
+ iface, err := net.InterfaceByName(ifaceName)
+ require.NoError(t, err)
+
+ reqAddrs, err := iface.Addrs()
+ require.NoError(t, err)
+ require.NotEmpty(t, reqAddrs)
+
+ ifaceNet = testutil.RequireTypeAssert[*net.IPNet](t, reqAddrs[0])
+ masked := &net.IPNet{
+ IP: ifaceNet.IP.Mask(ifaceNet.Mask),
+ Mask: ifaceNet.Mask,
+ }
+ t.Logf(
+ "assuming following command has been called:\n"+
+ "ip route add local %[1]s dev %[2]s\n"+
+ "after the test:\n"+
+ "ip route del local %[1]s dev %[2]s",
+ masked,
+ ifaceName,
+ )
+
+ return iface, ifaceNet
+}
+
+// TestListenControl checks the SO_BINDTODEVICE handling. The test assumes that
+// the correct routing has already been set up on the machine. To test the
+// package an actual network interface is required. To set that up:
+//
+// 1. Run ip a to locate the interface you want to use and its subnet. For
+// example, "wlp3s0" and "192.168.10.0/23".
+//
+// 2. Add a route for that interface: "ip route add local 192.168.10.0/23 dev
+// wlp3s0". You might need sudo for that.
+//
+// 3. Run the test itself: "env ADGUARD_DNS_TEST_NET_INTERFACE='wlp3s0' go test
+// -v ./internal/bindtodevice/".
+//
+// 4. Delete the route you added in step 2: "ip route del local 192.168.10.0/23
+// dev wlp3s0". You might need sudo for that.
+//
+// An all-in-one example, with sudo:
+//
+// sudo ip route add local 192.168.10.0/23 dev wlp3s0\
+// ; env ADGUARD_DNS_TEST_NET_INTERFACE='wlp3s0'\
+// go test ./internal/bindtodevice/\
+// ; sudo ip route del local 192.168.10.0/23 dev wlp3s0
+func TestListenControl(t *testing.T) {
+ iface, ifaceNet := InterfaceForTests(t)
+ if iface == nil {
+ t.Skipf("test %s skipped: please set env var %s", t.Name(), TestInterfaceEnvVarName)
+ }
+
+ ifaceName := iface.Name
+ lc := newListenConfig(ifaceName)
+ require.NotNil(t, lc)
+
+ t.Run("tcp", func(t *testing.T) {
+ SubtestListenControlTCP(t, lc, ifaceName, ifaceNet)
+ })
+
+ t.Run("udp", func(t *testing.T) {
+ SubtestListenControlUDP(t, lc, ifaceName, ifaceNet)
+ })
+}
+
+// SubtestListenControlTCP is a shared subtest that uses lc to dial a listener and
+// perform two-way communication using the resulting connection.
+func SubtestListenControlTCP(
+ t *testing.T,
+ lc netext.ListenConfig,
+ ifaceName string,
+ ifaceNet *net.IPNet,
+) {
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ t.Cleanup(cancel)
+
+ lsnr, err := lc.Listen(ctx, "tcp", "0.0.0.0:0")
+ require.NoError(t, err)
+ testutil.CleanupAndRequireSuccess(t, lsnr.Close)
+
+ // Make sure we can work with [prefixNetAddr] as well.
+ addrStr, _, _ := strings.Cut(lsnr.Addr().String(), "/")
+ addr, err := netip.ParseAddrPort(addrStr)
+ require.NoError(t, err)
+
+ addrPort := int(addr.Port())
+ ifaceAddr := &net.TCPAddr{
+ IP: ifaceNet.IP,
+ Port: addrPort,
+ }
+
+ normalize(ifaceAddr)
+
+ t.Run("main_interface_addr", func(t *testing.T) {
+ t.Logf("using addr %s for iface %s", ifaceAddr, ifaceName)
+
+ testListenControlTCPQuery(t, lsnr, ifaceAddr)
+ })
+
+ t.Run("other_interface_addr", func(t *testing.T) {
+ otherIfaceAddr := &net.TCPAddr{
+ IP: closestIP(t, ifaceNet, ifaceAddr.IP),
+ Port: ifaceAddr.Port,
+ }
+
+ normalize(otherIfaceAddr)
+
+ t.Logf("using addr %s for iface %s", otherIfaceAddr, ifaceName)
+
+ testListenControlTCPQuery(t, lsnr, otherIfaceAddr)
+ })
+}
+
+func testListenControlTCPQuery(t *testing.T, lsnr net.Listener, reqAddr *net.TCPAddr) {
+ req, resp := []byte("hello"), []byte("world")
+ reqLen, respLen := len(req), len(resp)
+
+ go requestTCP(reqAddr, slices.Clone(req), slices.Clone(resp))
+
+ localConn, err := lsnr.Accept()
+ require.NoError(t, err)
+ testutil.CleanupAndRequireSuccess(t, localConn.Close)
+
+ laddr := testutil.RequireTypeAssert[*net.TCPAddr](t, localConn.LocalAddr())
+ normalize(laddr)
+ assert.Equal(t, reqAddr, laddr)
+
+ err = localConn.SetReadDeadline(time.Now().Add(testTimeout))
+ require.NoError(t, err)
+
+ gotReq := make([]byte, reqLen)
+ n, err := localConn.Read(gotReq)
+ require.NoError(t, err)
+
+ assert.Equal(t, reqLen, n)
+ assert.Equal(t, req, gotReq)
+
+ err = localConn.SetWriteDeadline(time.Now().Add(testTimeout))
+ require.NoError(t, err)
+
+ n, err = localConn.Write(resp)
+ require.NoError(t, err)
+
+ assert.Equal(t, respLen, n)
+}
+
+// SubtestListenControlUDP is a shared subtest that uses lc to dial a packet
+// connection and perform two-way communication with it.
+func SubtestListenControlUDP(
+ t *testing.T,
+ lc netext.ListenConfig,
+ ifaceName string,
+ ifaceNet *net.IPNet,
+) {
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ t.Cleanup(cancel)
+
+ packetConn, err := lc.ListenPacket(ctx, "udp", "0.0.0.0:0")
+ require.NoError(t, err)
+ testutil.CleanupAndRequireSuccess(t, packetConn.Close)
+
+ // Make sure we can work with [prefixNetAddr] as well.
+ addrStr, _, _ := strings.Cut(packetConn.LocalAddr().String(), "/")
+ addr, err := netip.ParseAddrPort(addrStr)
+ require.NoError(t, err)
+
+ addrPort := int(addr.Port())
+ ifaceAddr := &net.UDPAddr{
+ IP: ifaceNet.IP,
+ Port: addrPort,
+ }
+
+ normalize(ifaceAddr)
+
+ t.Run("main_interface_addr", func(t *testing.T) {
+ t.Logf("using addr %s for iface %s", ifaceAddr, ifaceName)
+
+ testListenControlUDPQuery(t, packetConn, ifaceAddr)
+ })
+
+ t.Run("other_interface_addr", func(t *testing.T) {
+ otherIfaceAddr := &net.UDPAddr{
+ IP: closestIP(t, ifaceNet, ifaceAddr.IP),
+ Port: ifaceAddr.Port,
+ }
+
+ normalize(otherIfaceAddr)
+
+ t.Logf("using addr %s for iface %s", otherIfaceAddr, ifaceName)
+
+ testListenControlUDPQuery(t, packetConn, otherIfaceAddr)
+ })
+}
+
+func testListenControlUDPQuery(t *testing.T, packetConn net.PacketConn, reqAddr *net.UDPAddr) {
+ req, resp := []byte("hello"), []byte("world")
+ reqLen, respLen := len(req), len(resp)
+
+ go requestUDP(reqAddr, slices.Clone(req), slices.Clone(resp))
+
+ err := packetConn.SetReadDeadline(time.Now().Add(testTimeout))
+ require.NoError(t, err)
+
+ var sess *packetSession
+ switch c := packetConn.(type) {
+ case *net.UDPConn:
+ sess, err = readPacketSession(c, reqLen)
+ require.NoError(t, err)
+ case netext.SessionPacketConn:
+ var s netext.PacketSession
+ _, s, err = c.ReadFromSession(req)
+ require.NoError(t, err)
+
+ sess = testutil.RequireTypeAssert[*packetSession](t, s)
+ default:
+ t.Fatalf("bad packet conn type %T(%[1]v)", c)
+ }
+
+ assert.Equal(t, reqAddr, sess.laddr)
+ assert.Equal(t, req, sess.readBody)
+
+ err = packetConn.SetWriteDeadline(time.Now().Add(testTimeout))
+ require.NoError(t, err)
+
+ var n int
+ switch c := packetConn.(type) {
+ case *net.UDPConn:
+ n, _, err = c.WriteMsgUDP(resp, sess.respOOB, sess.raddr)
+ require.NoError(t, err)
+ case netext.SessionPacketConn:
+ n, err = c.WriteToSession(resp, sess)
+ require.NoError(t, err)
+ }
+
+ assert.Equal(t, respLen, n)
+}
+
+// requestTCP is a test helper for making TCP queries. It is intended to be
+// used as a goroutine.
+func requestTCP(raddr *net.TCPAddr, req, wantResp []byte) {
+ pt := testutil.PanicT{}
+
+ remoteConn, err := net.DialTCP("tcp", nil, raddr)
+ require.NoError(pt, err)
+ defer func() {
+ closeErr := remoteConn.Close()
+ require.NoError(pt, closeErr)
+ }()
+
+ err = remoteConn.SetWriteDeadline(time.Now().Add(testTimeout))
+ require.NoError(pt, err)
+
+ n, err := remoteConn.Write(req)
+ require.NoError(pt, err)
+
+ assert.Equal(pt, len(req), n)
+
+ wantRespLen := len(wantResp)
+ resp := make([]byte, wantRespLen)
+ err = remoteConn.SetReadDeadline(time.Now().Add(testTimeout))
+ require.NoError(pt, err)
+
+ n, err = remoteConn.Read(resp)
+ require.NoError(pt, err)
+
+ assert.Equal(pt, wantRespLen, n)
+ assert.Equal(pt, wantResp, resp)
+}
+
+// requestUDP is a test helper for making UDP queries. It is intended to be
+// used as a goroutine.
+func requestUDP(raddr *net.UDPAddr, req, wantResp []byte) {
+ pt := testutil.PanicT{}
+
+ remoteConn, err := net.DialUDP("udp", nil, raddr)
+ require.NoError(pt, err)
+ defer func() {
+ closeErr := remoteConn.Close()
+ require.NoError(pt, closeErr)
+ }()
+
+ err = remoteConn.SetWriteDeadline(time.Now().Add(testTimeout))
+ require.NoError(pt, err)
+
+ n, err := remoteConn.Write(req)
+ require.NoError(pt, err)
+
+ assert.Equal(pt, len(req), n)
+
+ wantRespLen := len(wantResp)
+ resp := make([]byte, wantRespLen)
+ err = remoteConn.SetReadDeadline(time.Now().Add(testTimeout))
+ require.NoError(pt, err)
+
+ n, err = remoteConn.Read(resp)
+ require.NoError(pt, err)
+
+ assert.Equal(pt, wantRespLen, n)
+ assert.Equal(pt, wantResp, resp)
+}
+
+// normalize sets the IP address of addr to a 4-byte version of the IP address
+// if it is an IPv4 address.
+func normalize(addr net.Addr) {
+ switch addr := addr.(type) {
+ case *net.TCPAddr:
+ ip4 := addr.IP.To4()
+ if ip4 != nil {
+ addr.IP = ip4
+ }
+ case *net.UDPAddr:
+ ip4 := addr.IP.To4()
+ if ip4 != nil {
+ addr.IP = ip4
+ }
+ default:
+ panic(fmt.Errorf("bad type %T", addr))
+ }
+}
+
+// closestIP is a test helper that provides a closest IP address based on the
+// provided IP network.
+func closestIP(t testing.TB, n *net.IPNet, ip net.IP) (closest net.IP) {
+ t.Helper()
+
+ ipAddr, err := netutil.IPToAddrNoMapped(ip)
+ require.NoError(t, err)
+
+ ipNet, err := netutil.IPNetToPrefixNoMapped(n)
+ require.NoError(t, err)
+
+ nextAddr := ipAddr.Next()
+ if ipNet.Contains(nextAddr) {
+ return nextAddr.AsSlice()
+ }
+
+ prevAddr := ipAddr.Prev()
+ if ipNet.Contains(prevAddr) {
+ return prevAddr.AsSlice()
+ }
+
+ t.Fatalf("neither %s nor %s are in %s", nextAddr, prevAddr, ipNet)
+
+ return nil
+}
diff --git a/internal/cmd/additional.go b/internal/cmd/additional.go
index 5e3359c..3fd54c1 100644
--- a/internal/cmd/additional.go
+++ b/internal/cmd/additional.go
@@ -3,28 +3,22 @@ package cmd
import (
"fmt"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdmaps"
"github.com/prometheus/common/model"
- "golang.org/x/exp/maps"
- "golang.org/x/exp/slices"
)
+// Additional prometheus information configuration
+
// additionalInfo is a extra info configuration.
type additionalInfo map[string]string
// validateAdditionalInfo return an error is the section is invalid.
func (c additionalInfo) validate() (err error) {
- if c == nil {
- return nil
- }
-
- keys := maps.Keys(c)
- slices.Sort(keys)
-
- for _, k := range keys {
- if !model.LabelName(k).IsValid() {
- return fmt.Errorf("prometheus labels must match %s, got %q", model.LabelNameRE, k)
+ return agdmaps.OrderedRangeError(c, func(k, _ string) (keyErr error) {
+ if model.LabelName(k).IsValid() {
+ return nil
}
- }
- return nil
+ return fmt.Errorf("prometheus labels must match %s, got %q", model.LabelNameRE, k)
+ })
}
diff --git a/internal/cmd/backend.go b/internal/cmd/backend.go
index c67ce89..78e9fc2 100644
--- a/internal/cmd/backend.go
+++ b/internal/cmd/backend.go
@@ -1,10 +1,13 @@
package cmd
import (
+ "context"
+ "fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/backend"
+ "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
)
@@ -64,3 +67,68 @@ func (c *backendConfig) validate() (err error) {
return nil
}
}
+
+// setupBackend creates and returns a profile database and a billing-statistics
+// recorder as well as starts and registers their refreshers in the signal
+// handler.
+func setupBackend(
+ conf *backendConfig,
+ envs *environments,
+ sigHdlr signalHandler,
+ errColl agd.ErrorCollector,
+) (profDB *agd.DefaultProfileDB, rec *billstat.RuntimeRecorder, err error) {
+ profStrgConf, billStatConf := conf.toInternal(envs, errColl)
+ rec = billstat.NewRuntimeRecorder(&billstat.RuntimeRecorderConfig{
+ Uploader: backend.NewBillStat(billStatConf),
+ })
+
+ refrIvl := conf.RefreshIvl.Duration
+ timeout := conf.Timeout.Duration
+ billStatRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: func() (ctx context.Context, cancel context.CancelFunc) {
+ return context.WithTimeout(context.Background(), timeout)
+ },
+ Refresher: rec,
+ ErrColl: errColl,
+ Name: "billstat",
+ Interval: refrIvl,
+ RefreshOnShutdown: true,
+ RoutineLogsAreDebug: true,
+ })
+ err = billStatRefr.Start()
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting bill stat recorder refresher: %w", err)
+ }
+
+ sigHdlr.add(billStatRefr)
+
+ profStrg := backend.NewProfileStorage(profStrgConf)
+ profDB, err = agd.NewDefaultProfileDB(
+ profStrg,
+ conf.FullRefreshIvl.Duration,
+ envs.ProfilesCachePath,
+ )
+ if err != nil {
+ return nil, nil, fmt.Errorf("creating default profile database: %w", err)
+ }
+
+ profDBRefr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: func() (ctx context.Context, cancel context.CancelFunc) {
+ return context.WithTimeout(context.Background(), timeout)
+ },
+ Refresher: profDB,
+ ErrColl: errColl,
+ Name: "profiledb",
+ Interval: refrIvl,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: true,
+ })
+ err = profDBRefr.Start()
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting default profile database refresher: %w", err)
+ }
+
+ sigHdlr.add(profDBRefr)
+
+ return profDB, rec, nil
+}
diff --git a/internal/cmd/check.go b/internal/cmd/check.go
index d907f3e..93c6657 100644
--- a/internal/cmd/check.go
+++ b/internal/cmd/check.go
@@ -15,7 +15,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil"
)
-// DNS Server Check Configuration
+// DNS server check configuration
// checkConfig is the DNS server checking configuration.
type checkConfig struct {
@@ -86,8 +86,6 @@ func (c *checkConfig) toInternal(
// validate returns an error if the DNS server checking configuration is
// invalid.
-//
-// TODO(a.garipov): Factor out IP validation; add IPv6 validation.
func (c *checkConfig) validate() (err error) {
if c == nil {
return errNilConfig
@@ -135,6 +133,8 @@ func (c *checkConfig) validate() (err error) {
// validateNonNilIPs returns an error if ips is empty or had IP addresses of
// incorrect protocol version.
+//
+// TODO(a.garipov): Merge with [validateAddrs].
func validateNonNilIPs(ips []netip.Addr, fam netutil.AddrFamily) (err error) {
if len(ips) == 0 {
return fmt.Errorf("no %s", fam)
diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go
index 064b47c..e9de141 100644
--- a/internal/cmd/cmd.go
+++ b/internal/cmd/cmd.go
@@ -8,20 +8,15 @@ import (
"math/rand"
"os"
"runtime"
- "sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
- "github.com/AdguardTeam/AdGuardDNS/internal/backend"
- "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
- "github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
@@ -37,6 +32,8 @@ import (
func Main() {
// Initial Configuration
+ //lint:ignore SA1019 According to ameshkov, using a non-cryptographically
+ //secure RNG is fine for things such as random upstream selection.
rand.Seed(time.Now().UnixNano())
// Log only to stdout and let users decide how to process it.
@@ -50,7 +47,7 @@ func Main() {
// Signal service startup now that we have the logs set up.
log.Info("main: starting adguard dns")
- // Error Collector
+ // Error collector
//
// TODO(a.garipov): Consider parsing SENTRY_DSN separately to set sentry up
// first and collect panics from the readEnvs call above as well.
@@ -60,7 +57,7 @@ func Main() {
defer collectPanics(errColl)
- // Configuration File
+ // Configuration file
c, err := readConfig(envs.ConfPath)
check(err)
@@ -68,88 +65,54 @@ func Main() {
err = c.validate()
check(err)
- // Additional Metrics
+ // Additional metrics
metrics.SetAdditionalInfo(c.AdditionalMetricsInfo)
- // GeoIP Database
+ // Signal handler
- // We start GeoIP initialization early in a dedicated routine cause it
- // takes time, later we wait for completion and continue with GeoIP.
+ sigHdlr := newSignalHandler()
+
+ // GeoIP database
+
+ // We start GeoIP initialization early in a dedicated routine cause it takes
+ // time, later we wait for completion and continue with GeoIP.
//
// See AGDNS-884.
- geoIPMu := &sync.Mutex{}
+ geoIP, geoIPRefr := &geoip.File{}, &agd.RefreshWorker{}
+ geoIPErrCh := make(chan error, 1)
- var (
- geoIP *geoip.File
- geoIPErr error
- )
-
- geoIPMu.Lock()
- go func() {
- defer geoIPMu.Unlock()
-
- geoIP, geoIPErr = envs.geoIP(c.GeoIP)
- }()
+ go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl)
// Safe-browsing and adult-blocking filters
// TODO(ameshkov): Consider making configurable.
- filteringResolver := agdnet.NewCachingResolver(
- agdnet.DefaultResolver{},
- 1*timeutil.Day,
- )
+ filteringResolver := agdnet.NewCachingResolver(agdnet.DefaultResolver{}, 1*timeutil.Day)
err = os.MkdirAll(envs.FilterCachePath, agd.DefaultDirPerm)
check(err)
- safeBrowsingConf, err := c.SafeBrowsing.toInternal(
- errColl,
+ safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter(
+ c.SafeBrowsing,
filteringResolver,
agd.FilterListIDSafeBrowsing,
envs.FilterCachePath,
+ sigHdlr,
+ errColl,
)
check(err)
- safeBrowsingFilter, err := filter.NewHashPrefix(safeBrowsingConf)
- check(err)
-
- safeBrowsingUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: safeBrowsingFilter,
- ErrColl: errColl,
- Name: string(agd.FilterListIDSafeBrowsing),
- Interval: safeBrowsingConf.Staleness,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: false,
- })
- err = safeBrowsingUpd.Start()
- check(err)
-
- adultBlockingConf, err := c.AdultBlocking.toInternal(
- errColl,
+ adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter(
+ c.AdultBlocking,
filteringResolver,
agd.FilterListIDAdultBlocking,
envs.FilterCachePath,
+ sigHdlr,
+ errColl,
)
check(err)
- adultBlockingFilter, err := filter.NewHashPrefix(adultBlockingConf)
- check(err)
-
- adultBlockingUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: adultBlockingFilter,
- ErrColl: errColl,
- Name: string(agd.FilterListIDAdultBlocking),
- Interval: adultBlockingConf.Staleness,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: false,
- })
- err = adultBlockingUpd.Start()
- check(err)
-
// Filter storage and filtering groups
fltStrgConf := c.Filters.toInternal(
@@ -160,31 +123,16 @@ func Main() {
adultBlockingFilter,
)
- fltStrg, err := filter.NewDefaultStorage(fltStrgConf)
+ fltRefrTimeout := c.Filters.RefreshTimeout.Duration
+ fltStrg, err := setupFilterStorage(fltStrgConf, sigHdlr, errColl, fltRefrTimeout)
check(err)
- fltStrgUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: func() (ctx context.Context, cancel context.CancelFunc) {
- return context.WithTimeout(context.Background(), c.Filters.RefreshTimeout.Duration)
- },
- Refresher: fltStrg,
- ErrColl: errColl,
- Name: "filters",
- Interval: fltStrgConf.RefreshIvl,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: false,
- })
- err = fltStrgUpd.Start()
- check(err)
-
- // Server Groups
-
fltGroups, err := c.FilteringGroups.toInternal(fltStrg)
check(err)
- messages := &dnsmsg.Constructor{
- FilteredResponseTTL: c.Filters.ResponseTTL.Duration,
- }
+ // Server groups
+
+ messages := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, c.Filters.ResponseTTL.Duration)
srvGrps, err := c.ServerGroups.toInternal(messages, fltGroups)
check(err)
@@ -197,133 +145,45 @@ func Main() {
check(err)
}
- // TLS Session Tickets Rotation
+ // TLS session-tickets rotation
- tickRot, err := newTicketRotator(srvGrps)
+ err = setupTicketRotator(srvGrps, sigHdlr, errColl)
check(err)
- tickRotUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: tickRot,
- ErrColl: errColl,
- Name: "tickrot",
- // TODO(ameshkov): Consider making configurable.
- Interval: 1 * time.Minute,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: true,
- })
- err = tickRotUpd.Start()
+ // Profiles database and billing statistics
+
+ profDB, billStatRec, err := setupBackend(c.Backend, envs, sigHdlr, errColl)
check(err)
- // Profiles Database
-
- profStrgConf, billStatConf := c.Backend.toInternal(envs, errColl)
- profStrg := backend.NewProfileStorage(profStrgConf)
-
- // Billing Statistics
-
- billStatRec := billstat.NewRuntimeRecorder(&billstat.RuntimeRecorderConfig{
- Uploader: backend.NewBillStat(billStatConf),
- })
-
- billStatRecUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: billStatRec,
- ErrColl: errColl,
- Name: "billstat",
- Interval: c.Backend.BillStatIvl.Duration,
- RefreshOnShutdown: true,
- RoutineLogsAreDebug: true,
- })
- err = billStatRecUpd.Start()
- check(err)
-
- profDB, err := agd.NewDefaultProfileDB(
- profStrg,
- c.Backend.FullRefreshIvl.Duration,
- envs.ProfilesCachePath,
- )
- check(err)
-
- profDBUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: func() (ctx context.Context, cancel context.CancelFunc) {
- return context.WithTimeout(context.Background(), c.Backend.Timeout.Duration)
- },
- Refresher: profDB,
- ErrColl: errColl,
- Name: "profiledb",
- Interval: c.Backend.RefreshIvl.Duration,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: true,
- })
- err = profDBUpd.Start()
- check(err)
-
- // Query Log
-
- queryLog := c.buildQueryLog(envs)
-
- // DNS Checker
+ // DNS checker
dnsCk, err := dnscheck.NewConsul(c.Check.toInternal(envs, messages, errColl))
check(err)
// DNSDB
- dnsDB, dnsDBUpd := envs.buildDNSDB(errColl)
- err = dnsDBUpd.Start()
+ dnsDB, err := envs.buildDNSDB(sigHdlr, errColl)
check(err)
- // Filtering Rule Statistics
+ // Filtering-rule statistics
- ruleStat, ruleStatUpd := envs.ruleStat(errColl)
- err = ruleStatUpd.Start()
+ ruleStat, err := envs.buildRuleStat(sigHdlr, errColl)
check(err)
- // Rate Limiting
+ // Rate limiting
- allowSubnets, err := agdnet.ParseSubnets(c.RateLimit.Allowlist.List...)
+ consulAllowlistURL := &envs.ConsulAllowlistURL.URL
+ rateLimiter, err := setupRateLimiter(c.RateLimit, consulAllowlistURL, sigHdlr, errColl)
check(err)
- allowlist := ratelimit.NewDynamicAllowlist(allowSubnets, nil)
- allowlistRefresher, err := consul.NewAllowlistRefresher(allowlist, &envs.ConsulAllowlistURL.URL)
- check(err)
-
- allowlistUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: allowlistRefresher,
- ErrColl: errColl,
- Name: "allowlist",
- Interval: c.RateLimit.Allowlist.RefreshIvl.Duration,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: false,
- })
- err = allowlistUpd.Start()
- check(err)
-
- rateLimiter := ratelimit.NewBackOff(c.RateLimit.toInternal(allowlist))
-
- // GeoIP Database
+ // GeoIP database
// Wait for long-running GeoIP initialization.
- geoIPMu.Lock()
- defer geoIPMu.Unlock()
+ check(<-geoIPErrCh)
- check(geoIPErr)
+ sigHdlr.add(geoIPRefr)
- geoIPUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
- Context: ctxWithDefaultTimeout,
- Refresher: geoIP,
- ErrColl: errColl,
- Name: "geoip",
- Interval: c.GeoIP.RefreshIvl.Duration,
- RefreshOnShutdown: false,
- RoutineLogsAreDebug: false,
- })
- err = geoIPUpd.Start()
- check(err)
-
- // Web Service
+ // Web service
webConf, err := c.Web.toInternal(envs, dnsCk, errColl)
check(err)
@@ -333,7 +193,9 @@ func Main() {
// instead of returning an error.
_ = webSvc.Start()
- // DNS Service
+ sigHdlr.add(webSvc)
+
+ // DNS service
metricsListener := prometheus.NewForwardMetricsListener(len(c.Upstream.FallbackServers) + 1)
@@ -351,11 +213,8 @@ func Main() {
}, c.Upstream.Healthcheck.Enabled)
dnsConf := &dnssvc.Config{
- Messages: messages,
- SafeBrowsing: filter.NewSafeBrowsingServer(
- safeBrowsingConf.Hashes,
- adultBlockingConf.Hashes,
- ),
+ Messages: messages,
+ SafeBrowsing: filter.NewSafeBrowsingServer(safeBrowsingHashes, adultBlockingHashes),
BillStat: billStatRec,
ProfileDB: profDB,
DNSCheck: dnsCk,
@@ -365,7 +224,7 @@ func Main() {
FilterStorage: fltStrg,
GeoIP: geoIP,
Handler: handler,
- QueryLog: queryLog,
+ QueryLog: c.buildQueryLog(envs),
RuleStat: ruleStat,
Upstream: upstream,
RateLimit: rateLimiter,
@@ -380,6 +239,10 @@ func Main() {
dnsSvc, err := dnssvc.New(dnsConf)
check(err)
+ sigHdlr.add(dnsSvc)
+
+ // Connectivity check
+
err = connectivityCheck(dnsConf, c.ConnectivityCheck)
check(err)
@@ -387,11 +250,15 @@ func Main() {
err = upstreamHealthcheckUpd.Start()
check(err)
+ sigHdlr.add(upstreamHealthcheckUpd)
+
// The DNS service is considered critical, so its Start method panics
// instead of returning an error.
_ = dnsSvc.Start()
- // Debug HTTP Service
+ sigHdlr.add(dnsSvc)
+
+ // Debug HTTP-service
debugSvc := debugsvc.New(envs.debugConf(dnsDB))
@@ -399,6 +266,8 @@ func Main() {
// instead of returning an error.
_ = debugSvc.Start()
+ sigHdlr.add(debugSvc)
+
// Signal that the server is started.
metrics.SetUpGauge(
agd.Version(),
@@ -408,23 +277,7 @@ func Main() {
runtime.Version(),
)
- h := newSignalHandler(
- debugSvc,
- webSvc,
- dnsSvc,
- safeBrowsingUpd,
- adultBlockingUpd,
- profDBUpd,
- dnsDBUpd,
- geoIPUpd,
- ruleStatUpd,
- allowlistUpd,
- fltStrgUpd,
- tickRotUpd,
- billStatRecUpd,
- )
-
- os.Exit(h.handle())
+ os.Exit(sigHdlr.handle())
}
// collectPanics reports all panics in Main. It should be called in a defer.
@@ -448,13 +301,3 @@ func collectPanics(errColl agd.ErrorCollector) {
panic(v)
}
-
-// defaultTimeout is the timeout used for some operations where another timeout
-// hasn't been defined yet.
-const defaultTimeout = 30 * time.Second
-
-// ctxWithDefaultTimeout is a helper function that returns a context with
-// timeout set to defaultTimeout.
-func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
- return context.WithTimeout(context.Background(), defaultTimeout)
-}
diff --git a/internal/cmd/config.go b/internal/cmd/config.go
index 9bed65f..af24751 100644
--- a/internal/cmd/config.go
+++ b/internal/cmd/config.go
@@ -1,12 +1,13 @@
package cmd
import (
+ "context"
"fmt"
"os"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/errors"
- "github.com/AdguardTeam/golibs/timeutil"
"gopkg.in/yaml.v2"
)
@@ -154,60 +155,6 @@ func (c *configuration) validate() (err error) {
return nil
}
-// queryLogConfig is the query log configuration.
-type queryLogConfig struct {
- // File contains the JSONL file query log configuration.
- File *queryLogFileConfig `yaml:"file"`
-}
-
-// validate returns an error if the query log configuration is invalid.
-func (c *queryLogConfig) validate() (err error) {
- switch {
- case c == nil:
- return errNilConfig
- case c.File == nil:
- return fmt.Errorf("file: %w", errNilConfig)
- default:
- return nil
- }
-}
-
-// queryLogFileConfig is the JSONL file query log configuration.
-type queryLogFileConfig struct {
- Enabled bool `yaml:"enabled"`
-}
-
-// geoIPConfig is the GeoIP database configuration.
-type geoIPConfig struct {
- // HostCacheSize is the size of the hostname lookup cache, in entries.
- HostCacheSize int `yaml:"host_cache_size"`
-
- // IPCacheSize is the size of the IP lookup cache, in entries.
- IPCacheSize int `yaml:"ip_cache_size"`
-
- // RefreshIvl defines how often AdGuard DNS reopens the GeoIP database
- // files.
- RefreshIvl timeutil.Duration `yaml:"refresh_interval"`
-}
-
-// validate returns an error if the GeoIP database configuration is invalid.
-func (c *geoIPConfig) validate() (err error) {
- switch {
- case c == nil:
- return errNilConfig
- case c.HostCacheSize <= 0:
- // Note that while geoip.File can work with an empty host cache, that
- // feature is only used for tests.
- return newMustBePositiveError("host_cache_size", c.HostCacheSize)
- case c.IPCacheSize <= 0:
- return newMustBePositiveError("ip_cache_size", c.IPCacheSize)
- case c.RefreshIvl.Duration <= 0:
- return newMustBePositiveError("refresh_interval", c.RefreshIvl)
- default:
- return nil
- }
-}
-
// readConfig reads the configuration.
func readConfig(confPath string) (c *configuration, err error) {
// #nosec G304 -- Trust the path to the configuration file that is given
@@ -225,3 +172,13 @@ func readConfig(confPath string) (c *configuration, err error) {
return c, nil
}
+
+// defaultTimeout is the timeout used for some operations where another timeout
+// hasn't been defined yet.
+const defaultTimeout = 30 * time.Second
+
+// ctxWithDefaultTimeout is a helper function that returns a context with
+// timeout set to defaultTimeout.
+func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
+ return context.WithTimeout(context.Background(), defaultTimeout)
+}
diff --git a/internal/cmd/conncheck.go b/internal/cmd/conncheck.go
index 904bc95..720f3dd 100644
--- a/internal/cmd/conncheck.go
+++ b/internal/cmd/conncheck.go
@@ -11,6 +11,8 @@ import (
"github.com/AdguardTeam/golibs/log"
)
+// Connectivity check configuration
+
// connCheckConfig is the connectivity check configuration.
type connCheckConfig struct {
// ProbeIPv4 is a probe v4 address to perform a check to.
@@ -82,8 +84,8 @@ func connectivityCheck(c *dnssvc.Config, connCheck *connCheckConfig) error {
func containsIPv6BindAddress(serverGroups []*agd.ServerGroup) (ok bool) {
for _, srvGrp := range serverGroups {
for _, s := range srvGrp.Servers {
- for _, addr := range s.BindAddresses {
- if addr.Addr().Is6() {
+ for _, bindData := range s.BindData {
+ if addr := bindData.AddrPort; addr.IsValid() && addr.Addr().Is6() {
return true
}
}
diff --git a/internal/cmd/ddr.go b/internal/cmd/ddr.go
index be8858c..09c16f8 100644
--- a/internal/cmd/ddr.go
+++ b/internal/cmd/ddr.go
@@ -14,7 +14,7 @@ import (
"golang.org/x/exp/slices"
)
-// Discovery Of Designated Resolvers (DDR) Configuration
+// Discovery Of Designated Resolvers (DDR) configuration
// ddrConfig is the configuration for a server group's DDR handler.
type ddrConfig struct {
@@ -118,7 +118,7 @@ func (c *ddrConfig) validate() (err error) {
}
domainSuf := wildcard[2:]
- err = netutil.ValidateDomainName(domainSuf)
+ err = netutil.ValidateHostname(domainSuf)
if err != nil {
return fmt.Errorf("device_records: %w", err)
}
@@ -130,7 +130,7 @@ func (c *ddrConfig) validate() (err error) {
}
for domain, r := range c.PublicRecords {
- err = netutil.ValidateDomainName(domain)
+ err = netutil.ValidateHostname(domain)
if err != nil {
return fmt.Errorf("public_records: %w", err)
}
@@ -184,6 +184,7 @@ func (r *ddrRecord) validate() (err error) {
return errors.Error("doh_path: cannot be empty if https_port is set")
}
+ // TODO(a.garipov): Merge with [validateAddrs] and [validateNonNilIPs].
for i, addr := range r.IPv4Hints {
if !addr.Is4() {
return fmt.Errorf("ipv4_hints: at index %d: not an ipv4 addr", i)
diff --git a/internal/cmd/env.go b/internal/cmd/env.go
index 5454692..4a51188 100644
--- a/internal/cmd/env.go
+++ b/internal/cmd/env.go
@@ -16,11 +16,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
- env "github.com/caarlos0/env/v6"
+ "github.com/caarlos0/env/v7"
"github.com/getsentry/sentry-go"
)
-// Environment Configuration
+// Environment configuration
// environments represents the configuration that is kept in the environment.
type environments struct {
@@ -97,13 +97,14 @@ func (envs *environments) buildErrColl() (errColl agd.ErrorCollector, err error)
return errcoll.NewSentryErrorCollector(cli), nil
}
-// buildDNSDB builds and returns an anonymous statistics collector and its
-// refresh worker.
+// buildDNSDB builds and returns an anonymous statistics collector and register
+// its refresher in sigHdlr, if needed.
func (envs *environments) buildDNSDB(
+ sigHdlr signalHandler,
errColl agd.ErrorCollector,
-) (d dnsdb.Interface, refr agd.Service) {
+) (d dnsdb.Interface, err error) {
if envs.DNSDBPath == "" {
- return dnsdb.Empty{}, agd.EmptyService{}
+ return dnsdb.Empty{}, nil
}
b := dnsdb.NewBolt(&dnsdb.BoltConfig{
@@ -111,7 +112,7 @@ func (envs *environments) buildDNSDB(
ErrColl: errColl,
})
- refr = agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: b,
ErrColl: errColl,
@@ -121,8 +122,14 @@ func (envs *environments) buildDNSDB(
RefreshOnShutdown: true,
RoutineLogsAreDebug: false,
})
+ err = refr.Start()
+ if err != nil {
+ return nil, fmt.Errorf("starting dnsdb refresher: %w", err)
+ }
- return b, refr
+ sigHdlr.add(refr)
+
+ return b, nil
}
// geoIP returns an GeoIP database implementation from environment.
@@ -174,22 +181,23 @@ func (envs *environments) debugConf(dnsDB dnsdb.Interface) (conf *debugsvc.Confi
return conf
}
-// ruleStat returns a filtering rule statistics collector from environment. It
-// also returns the refresh worker service that updates the collector.
-func (envs *environments) ruleStat(
+// buildRuleStat returns a filtering rule statistics collector from environment and
+// registers its refresher in sigHdlr, if necessary.
+func (envs *environments) buildRuleStat(
+ sigHdlr signalHandler,
errColl agd.ErrorCollector,
-) (r rulestat.Interface, refr agd.Service) {
+) (r rulestat.Interface, err error) {
if envs.RuleStatURL == nil {
log.Info("main: warning: not collecting rule stats")
- return rulestat.Empty{}, agd.EmptyService{}
+ return rulestat.Empty{}, nil
}
httpRuleStat := rulestat.NewHTTP(&rulestat.HTTPConfig{
URL: &envs.RuleStatURL.URL,
})
- refr = agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: httpRuleStat,
ErrColl: errColl,
@@ -199,8 +207,14 @@ func (envs *environments) ruleStat(
RefreshOnShutdown: true,
RoutineLogsAreDebug: false,
})
+ err = refr.Start()
+ if err != nil {
+ return nil, fmt.Errorf("starting rulestat refresher: %w", err)
+ }
- return httpRuleStat, refr
+ sigHdlr.add(refr)
+
+ return httpRuleStat, nil
}
// strictBool is a type for booleans that are parsed from the environment more
diff --git a/internal/cmd/error.go b/internal/cmd/error.go
index 30358b6..92ccfd7 100644
--- a/internal/cmd/error.go
+++ b/internal/cmd/error.go
@@ -7,7 +7,7 @@ import (
"golang.org/x/exp/constraints"
)
-// Error Helpers
+// Error-handling utilities
// check is a simple error-checking helper. It must only be used within Main.
func check(err error) {
@@ -40,16 +40,6 @@ type numberOrDuration interface {
constraints.Integer | timeutil.Duration
}
-// validatePositive returns an error if v is not a positive number. prop is the
-// name of the property being checked, used for error messages.
-func validatePositive[T numberOrDuration](prop string, v T) (err error) {
- if d, ok := any(v).(timeutil.Duration); ok && d.Duration <= 0 {
- return newMustBePositiveError(prop, v)
- }
-
- return nil
-}
-
// newMustBePositiveError returns an error about the value that must be positive
// but isn't. prop is the name of the property to mention in the error message.
func newMustBePositiveError[T numberOrDuration](prop string, v T) (err error) {
diff --git a/internal/cmd/filter.go b/internal/cmd/filter.go
index 678d276..fca8414 100644
--- a/internal/cmd/filter.go
+++ b/internal/cmd/filter.go
@@ -1,6 +1,8 @@
package cmd
import (
+ "context"
+ "fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -10,7 +12,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil"
)
-// Filters Configuration
+// Filters configuration
// filtersConfig contains the configuration for the filter lists and filtering
// storage to be used.
@@ -92,3 +94,37 @@ func (c *filtersConfig) validate() (err error) {
return nil
}
}
+
+// setupFilterStorage creates and returns a filter storage as well as starts and
+// registers its refresher in the signal handler.
+func setupFilterStorage(
+ conf *filter.DefaultStorageConfig,
+ sigHdlr signalHandler,
+ errColl agd.ErrorCollector,
+ refreshTimeout time.Duration,
+) (strg *filter.DefaultStorage, err error) {
+ strg, err = filter.NewDefaultStorage(conf)
+ if err != nil {
+ return nil, fmt.Errorf("creating default filter storage: %w", err)
+ }
+
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: func() (ctx context.Context, cancel context.CancelFunc) {
+ return context.WithTimeout(context.Background(), refreshTimeout)
+ },
+ Refresher: strg,
+ ErrColl: errColl,
+ Name: "filters",
+ Interval: conf.RefreshIvl,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: false,
+ })
+ err = refr.Start()
+ if err != nil {
+ return nil, fmt.Errorf("starting default filter storage update: %w", err)
+ }
+
+ sigHdlr.add(refr)
+
+ return strg, nil
+}
diff --git a/internal/cmd/geoip.go b/internal/cmd/geoip.go
new file mode 100644
index 0000000..8d231db
--- /dev/null
+++ b/internal/cmd/geoip.go
@@ -0,0 +1,82 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
+ "github.com/AdguardTeam/golibs/timeutil"
+)
+
+// GeoIP database configuration
+
+// geoIPConfig is the GeoIP database configuration.
+type geoIPConfig struct {
+ // HostCacheSize is the size of the hostname lookup cache, in entries.
+ HostCacheSize int `yaml:"host_cache_size"`
+
+ // IPCacheSize is the size of the IP lookup cache, in entries.
+ IPCacheSize int `yaml:"ip_cache_size"`
+
+ // RefreshIvl defines how often AdGuard DNS reopens the GeoIP database
+ // files.
+ RefreshIvl timeutil.Duration `yaml:"refresh_interval"`
+}
+
+// validate returns an error if the GeoIP database configuration is invalid.
+func (c *geoIPConfig) validate() (err error) {
+ switch {
+ case c == nil:
+ return errNilConfig
+ case c.HostCacheSize <= 0:
+ // Note that while geoip.File can work with an empty host cache, that
+ // feature is only used for tests.
+ return newMustBePositiveError("host_cache_size", c.HostCacheSize)
+ case c.IPCacheSize <= 0:
+ return newMustBePositiveError("ip_cache_size", c.IPCacheSize)
+ case c.RefreshIvl.Duration <= 0:
+ return newMustBePositiveError("refresh_interval", c.RefreshIvl)
+ default:
+ return nil
+ }
+}
+
+// setupGeoIP creates and sets the GeoIP database as well as creates and starts
+// its refresher. It is intended to be used as a goroutine. geoIPPtr and
+// refrPtr must not be nil. errCh receives nil if the database and the
+// refresher have been created successfully or an error if not.
+func setupGeoIP(
+ geoIPPtr *geoip.File,
+ refrPtr *agd.RefreshWorker,
+ errCh chan<- error,
+ conf *geoIPConfig,
+ envs *environments,
+ errColl agd.ErrorCollector,
+) {
+ geoIP, err := envs.geoIP(conf)
+ if err != nil {
+ errCh <- fmt.Errorf("creating geoip: %w", err)
+
+ return
+ }
+
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: ctxWithDefaultTimeout,
+ Refresher: geoIP,
+ ErrColl: errColl,
+ Name: "geoip",
+ Interval: conf.RefreshIvl.Duration,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: false,
+ })
+ err = refr.Start()
+ if err != nil {
+ errCh <- fmt.Errorf("starting geoip refresher: %w", err)
+
+ return
+ }
+
+ *geoIPPtr, *refrPtr = *geoIP, *refr
+
+ errCh <- nil
+}
diff --git a/internal/cmd/querylog.go b/internal/cmd/querylog.go
new file mode 100644
index 0000000..a1ce883
--- /dev/null
+++ b/internal/cmd/querylog.go
@@ -0,0 +1,28 @@
+package cmd
+
+import "fmt"
+
+// Query log configuration
+
+// queryLogConfig is the query log configuration.
+type queryLogConfig struct {
+ // File contains the JSONL file query log configuration.
+ File *queryLogFileConfig `yaml:"file"`
+}
+
+// validate returns an error if the query log configuration is invalid.
+func (c *queryLogConfig) validate() (err error) {
+ switch {
+ case c == nil:
+ return errNilConfig
+ case c.File == nil:
+ return fmt.Errorf("file: %w", errNilConfig)
+ default:
+ return nil
+ }
+}
+
+// queryLogFileConfig is the JSONL file query log configuration.
+type queryLogFileConfig struct {
+ Enabled bool `yaml:"enabled"`
+}
diff --git a/internal/cmd/ratelimit.go b/internal/cmd/ratelimit.go
index 1c25d6b..e4834b0 100644
--- a/internal/cmd/ratelimit.go
+++ b/internal/cmd/ratelimit.go
@@ -2,7 +2,11 @@ package cmd
import (
"fmt"
+ "net/url"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
+ "github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/c2h5oh/datasize"
@@ -117,3 +121,41 @@ func (c *rateLimitConfig) validate() (err error) {
validatePositive("allowlist.refresh_interval", c.Allowlist.RefreshIvl),
)
}
+
+// setupRateLimiter creates and returns a backoff rate limiter as well as starts
+// and registers its refresher in the signal handler.
+func setupRateLimiter(
+ conf *rateLimitConfig,
+ consulAllowlist *url.URL,
+ sigHdlr signalHandler,
+ errColl agd.ErrorCollector,
+) (rateLimiter *ratelimit.BackOff, err error) {
+ allowSubnets, err := agdnet.ParseSubnets(conf.Allowlist.List...)
+ if err != nil {
+ return nil, fmt.Errorf("parsing allowlist subnets: %w", err)
+ }
+
+ allowlist := ratelimit.NewDynamicAllowlist(allowSubnets, nil)
+ refresher, err := consul.NewAllowlistRefresher(allowlist, consulAllowlist)
+ if err != nil {
+ return nil, fmt.Errorf("creating allowlist refresher: %w", err)
+ }
+
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: ctxWithDefaultTimeout,
+ Refresher: refresher,
+ ErrColl: errColl,
+ Name: "allowlist",
+ Interval: conf.Allowlist.RefreshIvl.Duration,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: false,
+ })
+ err = refr.Start()
+ if err != nil {
+ return nil, fmt.Errorf("starting allowlist refresher: %w", err)
+ }
+
+ sigHdlr.add(refr)
+
+ return ratelimit.NewBackOff(conf.toInternal(allowlist)), nil
+}
diff --git a/internal/cmd/safebrowsing.go b/internal/cmd/safebrowsing.go
index a16976b..e8cc36a 100644
--- a/internal/cmd/safebrowsing.go
+++ b/internal/cmd/safebrowsing.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "fmt"
"path/filepath"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -84,3 +85,42 @@ func (c *safeBrowsingConfig) validate() (err error) {
return nil
}
}
+
+// setupHashPrefixFilter creates and returns a hash-prefix filter as well as
+// starts and registers its refresher in the signal handler.
+func setupHashPrefixFilter(
+ conf *safeBrowsingConfig,
+ resolver *agdnet.CachingResolver,
+ id agd.FilterListID,
+ cachePath string,
+ sigHdlr signalHandler,
+ errColl agd.ErrorCollector,
+) (strg *hashstorage.Storage, flt *filter.HashPrefix, err error) {
+ fltConf, err := conf.toInternal(errColl, resolver, id, cachePath)
+ if err != nil {
+ return nil, nil, fmt.Errorf("configuring hash prefix filter %s: %w", id, err)
+ }
+
+ flt, err = filter.NewHashPrefix(fltConf)
+ if err != nil {
+ return nil, nil, fmt.Errorf("creating hash prefix filter %s: %w", id, err)
+ }
+
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: ctxWithDefaultTimeout,
+ Refresher: flt,
+ ErrColl: errColl,
+ Name: string(id),
+ Interval: fltConf.Staleness,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: false,
+ })
+ err = refr.Start()
+ if err != nil {
+ return nil, nil, fmt.Errorf("starting refresher for hash prefix filter %s: %w", id, err)
+ }
+
+ sigHdlr.add(refr)
+
+ return fltConf.Hashes, flt, nil
+}
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 76b8666..56c64f7 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -8,22 +8,22 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil"
- "golang.org/x/exp/slices"
)
-// Server Configuration
+// Server configuration
// toInternal returns the configuration of DNS servers for a single server
// group. srvs is assumed to be valid.
func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err error) {
dnsSrvs = make([]*agd.Server, 0, len(srvs))
for _, srv := range srvs {
+ bindData := srv.bindData()
name := agd.ServerName(srv.Name)
switch p := srv.Protocol; p {
case srvProtoDNS:
dnsSrvs = append(dnsSrvs, &agd.Server{
Name: name,
- BindAddresses: slices.Clone(srv.BindAddresses),
+ BindData: bindData,
Protocol: agd.ProtoDNS,
LinkedIPEnabled: srv.LinkedIPEnabled,
})
@@ -37,7 +37,7 @@ func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err e
dnsSrvs = append(dnsSrvs, &agd.Server{
DNSCrypt: dcConf,
Name: name,
- BindAddresses: slices.Clone(srv.BindAddresses),
+ BindData: bindData,
Protocol: agd.ProtoDNSCrypt,
LinkedIPEnabled: srv.LinkedIPEnabled,
})
@@ -56,7 +56,7 @@ func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err e
dnsSrvs = append(dnsSrvs, &agd.Server{
TLS: tlsConf,
Name: name,
- BindAddresses: slices.Clone(srv.BindAddresses),
+ BindData: bindData,
Protocol: p.toInternal(),
LinkedIPEnabled: srv.LinkedIPEnabled,
})
@@ -166,6 +166,21 @@ type server struct {
LinkedIPEnabled bool `yaml:"linked_ip_enabled"`
}
+// bindData returns the socket binding data for this server.
+func (s *server) bindData() (bindData []*agd.ServerBindData) {
+ addrs := s.BindAddresses
+ bindData = make([]*agd.ServerBindData, 0, len(addrs))
+ for _, addr := range addrs {
+ bindData = append(bindData, &agd.ServerBindData{
+ AddrPort: addr,
+ })
+ }
+
+ // TODO(a.garipov): Support bind_interfaces.
+
+ return bindData
+}
+
// validate returns an error if the configuration is invalid.
func (s *server) validate() (err error) {
switch {
@@ -177,6 +192,11 @@ func (s *server) validate() (err error) {
return errors.Error("no bind_addresses")
}
+ err = validateAddrs(s.BindAddresses)
+ if err != nil {
+ return fmt.Errorf("bind_addresses: %w", err)
+ }
+
err = s.Protocol.validate()
if err != nil {
return fmt.Errorf("protocol: %w", err)
diff --git a/internal/cmd/signal.go b/internal/cmd/signal.go
index e0f45a4..cacf204 100644
--- a/internal/cmd/signal.go
+++ b/internal/cmd/signal.go
@@ -20,6 +20,22 @@ type signalHandler struct {
services []agd.Service
}
+// newSignalHandler returns a new signalHandler that shuts down services.
+func newSignalHandler() (h signalHandler) {
+ h = signalHandler{
+ signal: make(chan os.Signal, 1),
+ }
+
+ signal.Notify(h.signal, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM)
+
+ return h
+}
+
+// add adds a service to the signal handler.
+func (h *signalHandler) add(s agd.Service) {
+ h.services = append(h.services, s)
+}
+
// Exit status constants.
const (
statusSuccess = 0
@@ -54,8 +70,9 @@ func (h *signalHandler) shutdown() (status int) {
defer cancel()
log.Info("sighdlr: shutting down services")
- for i, service := range h.services {
- err := service.Shutdown(ctx)
+ for i := len(h.services) - 1; i >= 0; i-- {
+ s := h.services[i]
+ err := s.Shutdown(ctx)
if err != nil {
log.Error("sighdlr: shutting down service at index %d: %s", i, err)
status = statusError
@@ -66,15 +83,3 @@ func (h *signalHandler) shutdown() (status int) {
return status
}
-
-// newSignalHandler returns a new signalHandler that shuts down svcs.
-func newSignalHandler(svcs ...agd.Service) (h signalHandler) {
- h = signalHandler{
- signal: make(chan os.Signal, 1),
- services: svcs,
- }
-
- signal.Notify(h.signal, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM)
-
- return h
-}
diff --git a/internal/cmd/tls.go b/internal/cmd/tls.go
index 33910d0..88a4cd7 100644
--- a/internal/cmd/tls.go
+++ b/internal/cmd/tls.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
@@ -268,7 +269,7 @@ func readSessionTicketKey(fn string) (key [sessTickLen]byte, err error) {
return key, fmt.Errorf("session ticket in %s: bad len %d, want %d", fn, len(b), sessTickLen)
}
- return *(*[sessTickLen]byte)(b), nil
+ return [sessTickLen]byte(b), nil
}
// enableTLSKeyLogging enables TLS key logging (use for debug purposes only).
@@ -291,3 +292,35 @@ func enableTLSKeyLogging(grps []*agd.ServerGroup, keyLogFileName string) (err er
return nil
}
+
+// setupTicketRotator creates and returns a ticket rotator as well as starts and
+// registers its refresher in the signal handler.
+func setupTicketRotator(
+ srvGrps []*agd.ServerGroup,
+ sigHdlr signalHandler,
+ errColl agd.ErrorCollector,
+) (err error) {
+ tickRot, err := newTicketRotator(srvGrps)
+ if err != nil {
+ return fmt.Errorf("setting up ticket rotator: %w", err)
+ }
+
+ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
+ Context: ctxWithDefaultTimeout,
+ Refresher: tickRot,
+ ErrColl: errColl,
+ Name: "tickrot",
+ // TODO(ameshkov): Consider making configurable.
+ Interval: 1 * time.Minute,
+ RefreshOnShutdown: false,
+ RoutineLogsAreDebug: true,
+ })
+ err = refr.Start()
+ if err != nil {
+ return fmt.Errorf("starting ticket rotator refresh: %w", err)
+ }
+
+ sigHdlr.add(refr)
+
+ return nil
+}
diff --git a/internal/cmd/upstream.go b/internal/cmd/upstream.go
index 7715a16..a1171eb 100644
--- a/internal/cmd/upstream.go
+++ b/internal/cmd/upstream.go
@@ -13,7 +13,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil"
)
-// DNS Upstream Configuration
+// DNS upstream configuration
// upstreamConfig module configuration
type upstreamConfig struct {
@@ -60,10 +60,9 @@ func (c *upstreamConfig) validate() (err error) {
return newMustBePositiveError("timeout", c.Timeout)
}
- for i, ipp := range c.FallbackServers {
- if ipp == (netip.AddrPort{}) {
- return fmt.Errorf("fallback at index %d: no address", i)
- }
+ err = validateAddrs(c.FallbackServers)
+ if err != nil {
+ return fmt.Errorf("fallback: %w", err)
}
return errors.Annotate(c.Healthcheck.validate(), "healthcheck: %w")
diff --git a/internal/cmd/validation.go b/internal/cmd/validation.go
new file mode 100644
index 0000000..a5bba44
--- /dev/null
+++ b/internal/cmd/validation.go
@@ -0,0 +1,41 @@
+package cmd
+
+import (
+ "fmt"
+ "net/netip"
+
+ "github.com/AdguardTeam/golibs/timeutil"
+)
+
+// Validation utilities
+
+// validatePositive returns an error if v is not a positive number. prop is the
+// name of the property being checked, used for error messages.
+func validatePositive[T numberOrDuration](prop string, v T) (err error) {
+ if d, ok := any(v).(timeutil.Duration); ok && d.Duration <= 0 {
+ return newMustBePositiveError(prop, v)
+ }
+
+ return nil
+}
+
+// netipAddr is the type constraint for the types from [netip], which we can
+// validate using [validateAddrs].
+type netipAddr interface {
+ netip.Addr | netip.AddrPort
+
+ IsValid() (ok bool)
+}
+
+// validateAddrs returns an error if any of the addrs isn't valid.
+//
+// TODO(a.garipov): Merge with [validateNonNilIPs].
+func validateAddrs[T netipAddr](addrs []T) (err error) {
+ for i, a := range addrs {
+ if !a.IsValid() {
+ return fmt.Errorf("at index %d: invalid addr", i)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/dnscheck/consul_test.go b/internal/dnscheck/consul_test.go
index 4523f8d..e868e9e 100644
--- a/internal/dnscheck/consul_test.go
+++ b/internal/dnscheck/consul_test.go
@@ -220,12 +220,10 @@ func TestConsul_Check(t *testing.T) {
}}
conf := &dnscheck.ConsulConfig{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: ttl * time.Second,
- },
- Domains: []string{checkDomain},
- IPv4: []net.IP{{1, 2, 3, 4}},
- IPv6: []net.IP{net.ParseIP("1234::5678")},
+ 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")},
}
dnsCk, err := dnscheck.NewConsul(conf)
@@ -249,9 +247,7 @@ func TestConsul_Check(t *testing.T) {
Host: tc.host,
RemoteIP: testRemoteIP,
QType: tc.qtype,
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: ttl * time.Second,
- },
+ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
}
resp, cErr := dnsCk.Check(ctx, req, ri)
diff --git a/internal/dnsmsg/blockingmode.go b/internal/dnsmsg/blockingmode.go
new file mode 100644
index 0000000..048944b
--- /dev/null
+++ b/internal/dnsmsg/blockingmode.go
@@ -0,0 +1,202 @@
+package dnsmsg
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/netip"
+
+ "github.com/AdguardTeam/golibs/errors"
+)
+
+// BlockingMode is a sum type of all possible ways to construct blocked or
+// modified responses. See the following types:
+//
+// - [*BlockingModeCustomIP]
+// - [*BlockingModeNXDOMAIN]
+// - [*BlockingModeNullIP]
+// - [*BlockingModeREFUSED]
+type BlockingMode interface {
+ isBlockingMode()
+}
+
+// BlockingModeCodec is a wrapper around a BlockingMode that implements the
+// [json.Marshaler] and [json.Unmarshaler] interfaces.
+type BlockingModeCodec struct {
+ Mode BlockingMode
+}
+
+// Blocking mode type names.
+const (
+ bmTypeCustomIP = "custom_ip"
+ bmTypeNullIP = "null_ip"
+ bmTypeNXDOMAIN = "nxdomain"
+ bmTypeREFUSED = "refused"
+)
+
+// type check
+var _ json.Marshaler = BlockingModeCodec{}
+
+// MarshalJSON implements the [json.Marshaler] interface for BlockingModeCodec.
+func (c BlockingModeCodec) MarshalJSON() (b []byte, err error) {
+ var j *blockingModeJSON
+ switch m := c.Mode.(type) {
+ case nil:
+ return nil, errors.Error("nil blocking mode")
+ case *BlockingModeCustomIP:
+ j = &blockingModeJSON{
+ Type: bmTypeCustomIP,
+ }
+
+ if m.IPv4.IsValid() {
+ j.IPv4 = &m.IPv4
+ }
+
+ if m.IPv6.IsValid() {
+ j.IPv6 = &m.IPv6
+ }
+ case *BlockingModeNullIP:
+ j = &blockingModeJSON{
+ Type: bmTypeNullIP,
+ }
+ case *BlockingModeNXDOMAIN:
+ j = &blockingModeJSON{
+ Type: bmTypeNXDOMAIN,
+ }
+ case *BlockingModeREFUSED:
+ j = &blockingModeJSON{
+ Type: bmTypeREFUSED,
+ }
+ default:
+ return nil, fmt.Errorf("unexpected blocking mode %T(%[1]v)", m)
+ }
+
+ return json.Marshal(j)
+}
+
+// type check
+var _ json.Unmarshaler = (*BlockingModeCodec)(nil)
+
+// blockingModeJSON contains common fields for all BlockingMode JSON object
+// properties.
+type blockingModeJSON struct {
+ IPv4 *netip.Addr `json:"ipv4,omitempty"`
+ IPv6 *netip.Addr `json:"ipv6,omitempty"`
+ Type string `json:"type"`
+}
+
+// UnmarshalJSON implements the [json.Unmarshaler] interface for
+// *BlockingModeCodec.
+func (c *BlockingModeCodec) UnmarshalJSON(b []byte) (err error) {
+ j := &blockingModeJSON{}
+ err = json.Unmarshal(b, j)
+ if err != nil {
+ // Don't wrap the error, because it's the main JSON one.
+ return err
+ }
+
+ switch t := j.Type; t {
+ case bmTypeCustomIP:
+ var m *BlockingModeCustomIP
+ m, err = j.toCustomIP()
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return err
+ }
+
+ c.Mode = m
+ case bmTypeNullIP:
+ c.Mode = &BlockingModeNullIP{}
+ case bmTypeNXDOMAIN:
+ c.Mode = &BlockingModeNXDOMAIN{}
+ case bmTypeREFUSED:
+ c.Mode = &BlockingModeREFUSED{}
+ default:
+ return fmt.Errorf("unexpected blocking mode type %q", t)
+ }
+
+ return nil
+}
+
+// toCustomIP converts j into a correct *BlockingModeCustomIP. j.Type should be
+// [bmTypeCustomIP].
+func (j *blockingModeJSON) toCustomIP() (m *BlockingModeCustomIP, err error) {
+ defer func() {
+ err = errors.Annotate(err, "bad options for blocking mode %q: %w", bmTypeCustomIP)
+ }()
+
+ ipv4Ptr, ipv6Ptr := j.IPv4, j.IPv6
+ if ipv4Ptr == nil && ipv6Ptr == nil {
+ return nil, errors.Error("ipv4 or ipv6 must be set")
+ }
+
+ m = &BlockingModeCustomIP{}
+ m.IPv4, err = decodeCustomIP(ipv4Ptr, netip.Addr.Is4, "ipv4")
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, err
+ }
+
+ m.IPv6, err = decodeCustomIP(ipv6Ptr, netip.Addr.Is6, "ipv6")
+ if err != nil {
+ // Don't wrap the error, because it's informative enough as is.
+ return nil, err
+ }
+
+ return m, nil
+}
+
+// decodeCustomIP is a helper that dereferences ipPtr, if it is not nil, checks
+// the resulting IP address, and returns an informative error if necessary.
+func decodeCustomIP(
+ ipPtr *netip.Addr,
+ isCorrectProto func(netip.Addr) bool,
+ protoName string,
+) (ip netip.Addr, err error) {
+ if ipPtr == nil {
+ return netip.Addr{}, nil
+ }
+
+ ip = *ipPtr
+ if !isCorrectProto(ip) {
+ return netip.Addr{}, fmt.Errorf("address %q is not %s", ip, protoName)
+ }
+
+ return ip, nil
+}
+
+// BlockingModeCustomIP makes the [dnsmsg.Constructor] return responses with
+// custom IP addresses to A and AAAA requests. For all other types of requests,
+// as well as if one of the addresses isn't set, it returns a response with no
+// answers (aka NODATA).
+type BlockingModeCustomIP struct {
+ IPv4 netip.Addr
+ IPv6 netip.Addr
+}
+
+// isBlockingMode implements the BlockingMode interface for
+// *BlockingModeCustomIP.
+func (*BlockingModeCustomIP) isBlockingMode() {}
+
+// BlockingModeNullIP makes the [dnsmsg.Constructor] return a null-IP response
+// to A and AAAA requests. For all other types of requests, it returns a
+// response with no answers (aka NODATA).
+type BlockingModeNullIP struct{}
+
+// isBlockingMode implements the BlockingMode interface for *BlockingModeNullIP.
+func (*BlockingModeNullIP) isBlockingMode() {}
+
+// BlockingModeNXDOMAIN makes the [dnsmsg.Constructor] return responses with
+// code NXDOMAIN.
+type BlockingModeNXDOMAIN struct{}
+
+// isBlockingMode implements the BlockingMode interface for
+// *BlockingModeNXDOMAIN.
+func (*BlockingModeNXDOMAIN) isBlockingMode() {}
+
+// BlockingModeREFUSED makes the [dnsmsg.Constructor] return responses with
+// code REFUSED.
+type BlockingModeREFUSED struct{}
+
+// isBlockingMode implements the BlockingMode interface for
+// *BlockingModeREFUSED.
+func (*BlockingModeREFUSED) isBlockingMode() {}
diff --git a/internal/dnsmsg/blockingmode_example_test.go b/internal/dnsmsg/blockingmode_example_test.go
new file mode 100644
index 0000000..5f8b537
--- /dev/null
+++ b/internal/dnsmsg/blockingmode_example_test.go
@@ -0,0 +1,158 @@
+package dnsmsg_test
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/netip"
+ "os"
+
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+)
+
+func ExampleBlockingModeCodec_MarshalJSON() {
+ enc := json.NewEncoder(os.Stdout)
+ enc.SetIndent("", " ")
+
+ fmt.Println("Custom IP:")
+ err := enc.Encode(&dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeCustomIP{
+ IPv4: netip.MustParseAddr("1.2.3.4"),
+ IPv6: netip.MustParseAddr("1234::cdef"),
+ },
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("Null IP:")
+ err = enc.Encode(&dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeNullIP{},
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("NXDOMAIN:")
+ err = enc.Encode(&dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeNXDOMAIN{},
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("REFUSED:")
+ err = enc.Encode(&dnsmsg.BlockingModeCodec{
+ Mode: &dnsmsg.BlockingModeREFUSED{},
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ // Output:
+ // Custom IP:
+ // {
+ // "ipv4": "1.2.3.4",
+ // "ipv6": "1234::cdef",
+ // "type": "custom_ip"
+ // }
+ // Null IP:
+ // {
+ // "type": "null_ip"
+ // }
+ // NXDOMAIN:
+ // {
+ // "type": "nxdomain"
+ // }
+ // REFUSED:
+ // {
+ // "type": "refused"
+ // }
+}
+
+func ExampleBlockingModeCodec_UnmarshalJSON() {
+ c := &dnsmsg.BlockingModeCodec{}
+
+ fmt.Println("Custom IP:")
+ err := json.Unmarshal([]byte(`{
+ "type":"custom_ip",
+ "ipv4":"1.2.3.4",
+ "ipv6":"1234::cdef"
+ }`), c)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%T(%+[1]v)\n", c.Mode)
+
+ fmt.Println("Null IP:")
+ err = json.Unmarshal([]byte(`{
+ "type":"null_ip"
+ }`), c)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%T(%+[1]v)\n", c.Mode)
+
+ fmt.Println("NXDOMAIN:")
+ err = json.Unmarshal([]byte(`{
+ "type":"nxdomain"
+ }`), c)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%T(%+[1]v)\n", c.Mode)
+
+ fmt.Println("REFUSED:")
+ err = json.Unmarshal([]byte(`{
+ "type":"refused"
+ }`), c)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%T(%+[1]v)\n", c.Mode)
+
+ // Output:
+ // Custom IP:
+ // *dnsmsg.BlockingModeCustomIP(&{IPv4:1.2.3.4 IPv6:1234::cdef})
+ // Null IP:
+ // *dnsmsg.BlockingModeNullIP(&{})
+ // NXDOMAIN:
+ // *dnsmsg.BlockingModeNXDOMAIN(&{})
+ // REFUSED:
+ // *dnsmsg.BlockingModeREFUSED(&{})
+}
+
+func ExampleBlockingModeCodec_UnmarshalJSON_invalid() {
+ c := &dnsmsg.BlockingModeCodec{}
+
+ err := json.Unmarshal([]byte(`{
+ "type":"bad_type"
+ }`), c)
+ fmt.Println(err)
+
+ err = json.Unmarshal([]byte(`{
+ "type":"custom_ip"
+ }`), c)
+ fmt.Println(err)
+
+ err = json.Unmarshal([]byte(`{
+ "type":"custom_ip",
+ "ipv4":"1234::cdef"
+ }`), c)
+ fmt.Println(err)
+
+ err = json.Unmarshal([]byte(`{
+ "type":"custom_ip",
+ "ipv6":"1.2.3.4"
+ }`), c)
+ fmt.Println(err)
+
+ // Output:
+ // unexpected blocking mode type "bad_type"
+ // bad options for blocking mode "custom_ip": ipv4 or ipv6 must be set
+ // bad options for blocking mode "custom_ip": address "1234::cdef" is not ipv4
+ // bad options for blocking mode "custom_ip": address "1.2.3.4" is not ipv6
+}
diff --git a/internal/dnsmsg/constructor.go b/internal/dnsmsg/constructor.go
index 2a53d6b..ee282de 100644
--- a/internal/dnsmsg/constructor.go
+++ b/internal/dnsmsg/constructor.go
@@ -14,28 +14,65 @@ import (
// Constructor creates DNS messages for blocked or modified responses.
type Constructor struct {
- // FilteredResponseTTL is the time-to-live value used for responses created
- // by this message constructor.
- FilteredResponseTTL time.Duration
+ blockingMode BlockingMode
+ fltRespTTL time.Duration
}
-// NewBlockedRespMsg returns a blocked DNS response message. For A and AAAA
-// requests, it returns a response with an unspecified (aka null) IP (0.0.0.0
-// for IPv4, [::] for IPv6). For all other types of requests, it returns
-// a response with no answers.
+// NewConstructor returns a properly initialized constructor with the given
+// options. respTTL is the time-to-live value used for responses created by
+// this message constructor. bm is the blocking mode to use in
+// [Constructor.NewBlockedRespMsg].
+func NewConstructor(bm BlockingMode, respTTL time.Duration) (c *Constructor) {
+ return &Constructor{
+ blockingMode: bm,
+ fltRespTTL: respTTL,
+ }
+}
+
+// NewBlockedRespMsg returns a blocked DNS response message based on the
+// constructor's blocking mode.
func (c *Constructor) NewBlockedRespMsg(req *dns.Msg) (msg *dns.Msg, err error) {
- if qt := req.Question[0].Qtype; qt == dns.TypeA || qt == dns.TypeAAAA {
- msg, err = c.NewIPRespMsg(req, nil)
- if err != nil {
- // Technically should never happen.
- return nil, err
+ switch m := c.blockingMode.(type) {
+ case *BlockingModeCustomIP:
+ return c.newBlockedCustomIPRespMsg(req, m)
+ case *BlockingModeNullIP:
+ switch qt := req.Question[0].Qtype; qt {
+ case dns.TypeA, dns.TypeAAAA:
+ return c.NewIPRespMsg(req, nil)
+ default:
+ return c.NewMsgNODATA(req), nil
}
- } else {
- msg = c.NewRespMsg(req)
- msg.Ns = c.newSOARecords(req)
+ case *BlockingModeNXDOMAIN:
+ return c.NewMsgNXDOMAIN(req), nil
+ case *BlockingModeREFUSED:
+ return c.NewMsgREFUSED(req), nil
+ default:
+ // Consider unhandled sum type members as unrecoverable programmer
+ // errors.
+ panic(fmt.Errorf("unexpected type %T", c.blockingMode))
+ }
+}
+
+// newBlockedCustomIPRespMsg returns a blocked DNS response message with either
+// the custom IPs from the blocking mode options or a NODATA one.
+func (c *Constructor) newBlockedCustomIPRespMsg(
+ req *dns.Msg,
+ m *BlockingModeCustomIP,
+) (msg *dns.Msg, err error) {
+ switch qt := req.Question[0].Qtype; qt {
+ case dns.TypeA:
+ if m.IPv4.IsValid() {
+ return c.NewIPRespMsg(req, m.IPv4.AsSlice())
+ }
+ case dns.TypeAAAA:
+ if m.IPv6.IsValid() {
+ return c.NewIPRespMsg(req, m.IPv6.AsSlice())
+ }
+ default:
+ // Go on.
}
- return msg, nil
+ return c.NewMsgNODATA(req), nil
}
// NewIPRespMsg returns a DNS A or AAAA response message with the given IP
@@ -152,7 +189,7 @@ func (c *Constructor) newHdr(req *dns.Msg, rrType RRType) (hdr dns.RR_Header) {
return dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: rrType,
- Ttl: uint32(c.FilteredResponseTTL.Seconds()),
+ Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET,
}
}
@@ -162,7 +199,7 @@ func (c *Constructor) newHdrWithClass(req *dns.Msg, rrType RRType, class dns.Cla
return dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: rrType,
- Ttl: uint32(c.FilteredResponseTTL.Seconds()),
+ Ttl: uint32(c.fltRespTTL.Seconds()),
Class: uint16(class),
}
}
@@ -291,7 +328,7 @@ func (c *Constructor) newSOARecords(req *dns.Msg) (soaRecs []dns.RR) {
Hdr: dns.RR_Header{
Name: zone,
Rrtype: dns.TypeSOA,
- Ttl: uint32(c.FilteredResponseTTL.Seconds()),
+ Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET,
},
Mbox: "hostmaster.", // zone will be appended later if it's not empty or "."
diff --git a/internal/dnsmsg/constructor_test.go b/internal/dnsmsg/constructor_test.go
index c6e378b..7aa553b 100644
--- a/internal/dnsmsg/constructor_test.go
+++ b/internal/dnsmsg/constructor_test.go
@@ -1,6 +1,8 @@
package dnsmsg_test
import (
+ "net"
+ "net/netip"
"strings"
"testing"
@@ -24,15 +26,13 @@ func newTXTExtra(ttl uint32, strs ...string) (extra []dns.RR) {
}}
}
-func TestConstructor_NewBlockedRespMsg(t *testing.T) {
- mc := dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
+func TestConstructor_NewBlockedRespMsg_nullIP(t *testing.T) {
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
testCases := []struct {
name string
wantAnsNum int
- qt uint16
+ qt dnsmsg.RRType
}{{
name: "a",
wantAnsNum: 1,
@@ -49,38 +49,137 @@ func TestConstructor_NewBlockedRespMsg(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- req := dnsservertest.NewReq("example.com", tc.qt, dns.ClassINET)
+ req := dnsservertest.NewReq(testFQDN, tc.qt, dns.ClassINET)
resp, err := mc.NewBlockedRespMsg(req)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, dns.RcodeSuccess, resp.Rcode)
- ttl := uint32(testFltRespTTL.Seconds())
+ const wantTTL = testFltRespTTLSec
if tc.wantAnsNum == 0 {
assert.Empty(t, resp.Answer)
require.Len(t, resp.Ns, 1)
- ns := resp.Ns[0]
- assert.Equal(t, ttl, ns.Header().Ttl)
+ nsTTL := resp.Ns[0].Header().Ttl
+ assert.Equal(t, wantTTL, nsTTL)
} else {
require.Len(t, resp.Answer, 1)
- ans := resp.Answer[0]
- assert.Equal(t, ttl, ans.Header().Ttl)
+ ansTTL := resp.Answer[0].Header().Ttl
+ assert.Equal(t, wantTTL, ansTTL)
}
})
}
}
-func TestConstructor_noAnswerMethods(t *testing.T) {
- mc := dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
+func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) {
+ wantIPv4 := netip.MustParseAddr("1.2.3.4")
+ wantIPv6 := netip.MustParseAddr("1234::cdef")
- req := dnsservertest.NewReq("example.com", dns.TypeA, dns.ClassINET)
- ttl := uint32(testFltRespTTL.Seconds())
+ testCases := []struct {
+ messages *dnsmsg.Constructor
+ name string
+ wantA bool
+ wantAAAA bool
+ }{{
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ IPv4: wantIPv4,
+ IPv6: wantIPv6,
+ }, testFltRespTTL),
+ name: "both",
+ wantA: true,
+ wantAAAA: true,
+ }, {
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ IPv4: wantIPv4,
+ }, testFltRespTTL),
+ name: "ipv4_only",
+ wantA: true,
+ wantAAAA: false,
+ }, {
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeCustomIP{
+ IPv6: wantIPv6,
+ }, testFltRespTTL),
+ name: "ipv6_only",
+ wantA: false,
+ wantAAAA: true,
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ reqA := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
+ respA, err := tc.messages.NewBlockedRespMsg(reqA)
+ require.NoError(t, err)
+ require.NotNil(t, respA)
+
+ assert.Equal(t, dns.RcodeSuccess, respA.Rcode)
+
+ if tc.wantA {
+ require.Len(t, respA.Answer, 1)
+
+ a := testutil.RequireTypeAssert[*dns.A](t, respA.Answer[0])
+ assert.Equal(t, net.IP(wantIPv4.AsSlice()), a.A)
+ } else {
+ assert.Empty(t, respA.Answer)
+ }
+
+ reqAAAA := dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET)
+ respAAAA, err := tc.messages.NewBlockedRespMsg(reqAAAA)
+ require.NoError(t, err)
+ require.NotNil(t, respAAAA)
+
+ assert.Equal(t, dns.RcodeSuccess, respAAAA.Rcode)
+
+ if tc.wantAAAA {
+ require.Len(t, respAAAA.Answer, 1)
+
+ aaaa := testutil.RequireTypeAssert[*dns.AAAA](t, respAAAA.Answer[0])
+ assert.Equal(t, net.IP(wantIPv6.AsSlice()), aaaa.AAAA)
+ } else {
+ assert.Empty(t, respAAAA.Answer)
+ }
+ })
+ }
+}
+
+func TestConstructor_NewBlockedRespMsg_noAnswer(t *testing.T) {
+ req := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
+
+ testCases := []struct {
+ messages *dnsmsg.Constructor
+ name string
+ rcode dnsmsg.RCode
+ }{{
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNXDOMAIN{}, testFltRespTTL),
+ name: "nxdomain",
+ rcode: dns.RcodeNameError,
+ }, {
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeREFUSED{}, testFltRespTTL),
+ name: "refused",
+ rcode: dns.RcodeRefused,
+ }}
+
+ const wantTTL = testFltRespTTLSec
+ for _, tc := range testCases {
+ resp, err := tc.messages.NewBlockedRespMsg(req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+
+ assert.Equal(t, tc.rcode, dnsmsg.RCode(resp.Rcode))
+ assert.Empty(t, resp.Answer)
+
+ require.Len(t, resp.Ns, 1)
+
+ nsTTL := resp.Ns[0].Header().Ttl
+ assert.Equal(t, wantTTL, nsTTL)
+ }
+}
+
+func TestConstructor_noAnswerMethods(t *testing.T) {
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ req := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
testCases := []struct {
method func(req *dns.Msg) (resp *dns.Msg)
@@ -108,6 +207,7 @@ func TestConstructor_noAnswerMethods(t *testing.T) {
want: dns.RcodeSuccess,
}}
+ const wantTTL = testFltRespTTLSec
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp := tc.method(req)
@@ -117,18 +217,15 @@ func TestConstructor_noAnswerMethods(t *testing.T) {
assert.Empty(t, resp.Answer)
assert.Equal(t, tc.want, dnsmsg.RCode(resp.Rcode))
- ns := resp.Ns[0]
- assert.Equal(t, ttl, ns.Header().Ttl)
+ nsTTL := resp.Ns[0].Header().Ttl
+ assert.Equal(t, wantTTL, nsTTL)
})
}
}
func TestConstructor_NewTXTRespMsg(t *testing.T) {
- mc := dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
-
- req := dnsservertest.NewReq("example.com.", dns.TypeTXT, dns.ClassINET)
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
+ req := dnsservertest.NewReq(testFQDN, dns.TypeTXT, dns.ClassINET)
tooLong := strings.Repeat("1", dnsmsg.MaxTXTStringLen+1)
testCases := []struct {
@@ -157,6 +254,7 @@ func TestConstructor_NewTXTRespMsg(t *testing.T) {
strs: []string{tooLong},
}}
+ const wantTTL = testFltRespTTLSec
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp, err := mc.NewTXTRespMsg(req, tc.strs...)
@@ -173,20 +271,17 @@ func TestConstructor_NewTXTRespMsg(t *testing.T) {
require.Len(t, resp.Answer, 1)
ans := resp.Answer[0]
- ttl := uint32(testFltRespTTL.Seconds())
- assert.Equal(t, ttl, ans.Header().Ttl)
+ ansTTL := ans.Header().Ttl
+ assert.Equal(t, wantTTL, ansTTL)
- txt := ans.(*dns.TXT)
+ txt := testutil.RequireTypeAssert[*dns.TXT](t, ans)
assert.Equal(t, tc.strs, txt.Txt)
})
}
}
func TestConstructor_AppendDebugExtra(t *testing.T) {
- mc := dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
-
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
shortText := "This is a short test text"
longText := strings.Repeat("a", 2*dnsmsg.MaxTXTStringLen)
@@ -200,14 +295,14 @@ func TestConstructor_AppendDebugExtra(t *testing.T) {
name: "short_text",
text: shortText,
qt: dns.TypeTXT,
- wantExtra: newTXTExtra(uint32(mc.FilteredResponseTTL.Seconds()), shortText),
+ wantExtra: newTXTExtra(testFltRespTTLSec, shortText),
wantErrMsg: "",
}, {
name: "long_text",
text: longText,
qt: dns.TypeTXT,
wantExtra: newTXTExtra(
- uint32(mc.FilteredResponseTTL.Seconds()),
+ testFltRespTTLSec,
longText[:dnsmsg.MaxTXTStringLen],
longText[dnsmsg.MaxTXTStringLen:],
),
@@ -222,11 +317,11 @@ func TestConstructor_AppendDebugExtra(t *testing.T) {
name: "empty_text",
text: "",
qt: dns.TypeTXT,
- wantExtra: newTXTExtra(uint32(mc.FilteredResponseTTL.Seconds()), ""),
+ wantExtra: newTXTExtra(testFltRespTTLSec, ""),
wantErrMsg: "",
}}
- const fqdn = "example.com."
+ const fqdn = testFQDN
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/dnsmsg/dnsmsg_test.go b/internal/dnsmsg/dnsmsg_test.go
index 06be934..cf1e0d6 100644
--- a/internal/dnsmsg/dnsmsg_test.go
+++ b/internal/dnsmsg/dnsmsg_test.go
@@ -19,8 +19,17 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
-// testFltRespTTL is the common filtered response TTL.
-const testFltRespTTL = 10 * time.Second
+// Common filtered response TTL constants.
+const (
+ testFltRespTTL = 10 * time.Second
+ testFltRespTTLSec = uint32(testFltRespTTL / time.Second)
+)
+
+// Common domain names for tests.
+const (
+ testDomain = "test.example"
+ testFQDN = testDomain + "."
+)
// Common test constants.
const (
@@ -42,7 +51,7 @@ func newECSExtraMsg(ip netip.Addr, ecsFam netutil.AddrFamily, mask uint8) (msg *
panic(fmt.Errorf("unsupported ecs addr fam %s", ecsFam))
}
- msg = dnsservertest.NewReq("example.com.", dns.TypeA, dns.ClassINET)
+ msg = dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
msg.SetEdns0(dnsmsg.DefaultEDNSUDPSize, true)
msg.Extra = append(msg.Extra, dnsservertest.NewECSExtra(
ip.AsSlice(),
@@ -70,7 +79,7 @@ func TestClone(t *testing.T) {
},
name: "empty_slice_ans",
}, {
- msg: dnsservertest.NewReq("example.com.", dns.TypeA, dns.ClassINET),
+ msg: dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET),
name: "a",
}}
@@ -86,7 +95,7 @@ func TestECSFromMsg(t *testing.T) {
ipv4Net := netip.MustParsePrefix("1.2.3.0/24")
ipv6Net := netip.MustParsePrefix("2001:0:0102:0304::/64")
- msgNoOpt := dnsservertest.NewReq("example.com.", dns.TypeA, dns.ClassINET)
+ msgNoOpt := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET)
testCases := []struct {
msg *dns.Msg
diff --git a/internal/dnsmsg/svcbmsg.go b/internal/dnsmsg/svcbmsg.go
index 6e963f3..ed33288 100644
--- a/internal/dnsmsg/svcbmsg.go
+++ b/internal/dnsmsg/svcbmsg.go
@@ -237,7 +237,7 @@ func (c *Constructor) NewDDRTemplate(
// Keep the name empty for the client of the API to fill it.
Name: "",
Rrtype: dns.TypeSVCB,
- Ttl: uint32(c.FilteredResponseTTL.Seconds()),
+ Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET,
},
Priority: prio,
diff --git a/internal/dnsmsg/svcbmsg_test.go b/internal/dnsmsg/svcbmsg_test.go
index b99237f..3a80ac6 100644
--- a/internal/dnsmsg/svcbmsg_test.go
+++ b/internal/dnsmsg/svcbmsg_test.go
@@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
+ "github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -24,10 +25,7 @@ var (
func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
// Preconditions.
- mc := &dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
-
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
req := &dns.Msg{
Question: []dns.Question{{
Name: "abcd",
@@ -36,14 +34,13 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
// Constants and helper values.
- const host = "example.com"
const prio = 32
// Helper functions.
dnssvcb := func(key, value string) (svcb *rules.DNSSVCB) {
svcb = &rules.DNSSVCB{
- Target: host,
+ Target: testFQDN,
Priority: prio,
}
@@ -63,11 +60,11 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
Hdr: dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeSVCB,
- Ttl: uint32(mc.FilteredResponseTTL.Seconds()),
+ Ttl: testFltRespTTLSec,
Class: dns.ClassINET,
},
Priority: prio,
- Target: dns.Fqdn(host),
+ Target: testFQDN,
}
if kv != nil {
@@ -92,8 +89,8 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
want: wantsvcb(nil),
name: "invalid",
}, {
- svcb: dnssvcb("alpn", "h3"),
- want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}),
+ svcb: dnssvcb("alpn", http3.NextProtoH3),
+ want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{http3.NextProtoH3}}),
name: "alpn",
}, {
svcb: dnssvcb("dohpath", "/some/url/path"),
@@ -170,9 +167,7 @@ func TestConstructor_NewDDR(t *testing.T) {
dohPath = "/dns-query"
)
- mc := &dnsmsg.Constructor{
- FilteredResponseTTL: testFltRespTTL,
- }
+ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
testCases := []struct {
name string
@@ -248,7 +243,7 @@ func TestConstructor_NewDDR(t *testing.T) {
assert.Equal(t, targetFQDN, svcb.Target)
assert.Equal(t, prio, svcb.Priority)
- assert.Equal(t, uint32(mc.FilteredResponseTTL.Seconds()), svcb.Hdr.Ttl)
+ assert.Equal(t, testFltRespTTLSec, svcb.Hdr.Ttl)
assert.ElementsMatch(t, tc.wantVals, svcb.Value)
})
}
diff --git a/internal/dnsserver/dnsserver.go b/internal/dnsserver/dnsserver.go
index 5ef5883..36ade4d 100644
--- a/internal/dnsserver/dnsserver.go
+++ b/internal/dnsserver/dnsserver.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 AdGuard Software Ltd.
+// Copyright (C) 2022-2023 AdGuard Software Ltd.
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
diff --git a/internal/dnsserver/dnsservertest/msg.go b/internal/dnsserver/dnsservertest/msg.go
index b9aba02..6feac03 100644
--- a/internal/dnsserver/dnsservertest/msg.go
+++ b/internal/dnsserver/dnsservertest/msg.go
@@ -103,6 +103,9 @@ const (
// RRSection is the slice of resource records to be appended to a new message
// created by NewReq and NewResp.
+//
+// TODO(e.burkov): Use separate types for different sections of DNS message
+// instead of constants.
type RRSection struct {
RRs []dns.RR
Sec MsgSection
@@ -140,7 +143,8 @@ func NewCNAME(name string, ttl uint32, target string) (rr dns.RR) {
}
}
-// NewA constructs the new resource record of type A.
+// 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{
@@ -153,6 +157,20 @@ func NewA(name string, ttl uint32, a net.IP) (rr dns.RR) {
}
}
+// 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,
+ }
+}
+
// 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/quictracer.go b/internal/dnsserver/dnsservertest/quictracer.go
new file mode 100644
index 0000000..543f0e9
--- /dev/null
+++ b/internal/dnsserver/dnsservertest/quictracer.go
@@ -0,0 +1,97 @@
+package dnsservertest
+
+import (
+ "context"
+ "sync"
+
+ "github.com/quic-go/quic-go/logging"
+)
+
+// QUICTracer implements the logging.Tracer interface.
+type QUICTracer struct {
+ logging.NullTracer
+ tracers []*quicConnTracer
+
+ // mu protects fields of *QUICTracer and also protects fields of every
+ // nested *quicConnTracer.
+ mu sync.Mutex
+}
+
+// type check
+var _ logging.Tracer = (*QUICTracer)(nil)
+
+// TracerForConnection implements the logging.Tracer interface for *quicTracer.
+func (q *QUICTracer) TracerForConnection(
+ _ context.Context,
+ _ logging.Perspective,
+ odcid logging.ConnectionID,
+) (connTracer logging.ConnectionTracer) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ tracer := &quicConnTracer{id: odcid, parent: q}
+ q.tracers = append(q.tracers, tracer)
+
+ return tracer
+}
+
+// QUICConnInfo contains information about packets that were recorded by
+// *QUICTracer.
+type QUICConnInfo struct {
+ id logging.ConnectionID
+ packets []logging.Header
+}
+
+// Is0RTT returns true if this connection's packets contain 0-RTT packets.
+func (c *QUICConnInfo) Is0RTT() (ok bool) {
+ for _, packet := range c.packets {
+ hdr := packet
+ packetType := logging.PacketTypeFromHeader(&hdr)
+ if packetType == logging.PacketType0RTT {
+ return true
+ }
+ }
+
+ return false
+}
+
+// ConnectionsInfo returns the traced connections' information.
+func (q *QUICTracer) ConnectionsInfo() (conns []QUICConnInfo) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+
+ for _, tracer := range q.tracers {
+ conns = append(conns, QUICConnInfo{
+ id: tracer.id,
+ packets: tracer.packets,
+ })
+ }
+
+ return conns
+}
+
+// quicConnTracer implements the [logging.ConnectionTracer] interface.
+type quicConnTracer struct {
+ id logging.ConnectionID
+ parent *QUICTracer
+ packets []logging.Header
+
+ logging.NullConnectionTracer
+}
+
+// type check
+var _ logging.ConnectionTracer = (*quicConnTracer)(nil)
+
+// SentLongHeaderPacket implements the [logging.ConnectionTracer] interface for
+// *quicConnTracer.
+func (q *quicConnTracer) SentLongHeaderPacket(
+ hdr *logging.ExtendedHeader,
+ _ logging.ByteCount,
+ _ *logging.AckFrame,
+ _ []logging.Frame,
+) {
+ q.parent.mu.Lock()
+ defer q.parent.mu.Unlock()
+
+ q.packets = append(q.packets, hdr.Header)
+}
diff --git a/internal/dnsserver/doc.go b/internal/dnsserver/doc.go
index e9070a4..7de2088 100644
--- a/internal/dnsserver/doc.go
+++ b/internal/dnsserver/doc.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 AdGuard Software Ltd.
+// Copyright (C) 2022-2023 AdGuard Software Ltd.
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
@@ -166,7 +166,7 @@ them, you need to implement the [dnsserver.MetricsListener] interface and set it
in the server configuration. For instance, you can use
[prometheus.ServerMetricsListener] to make it record prometheus metrics.
-[quic-go module]: https://github.com/lucas-clemente/quic-go
+[quic-go module]: https://github.com/quic-go/quic-go
[dnscrypt module]: https://github.com/ameshkov/dnscrypt
[module documentation]: https://github.com/ameshkov/dnscrypt#server
*/
diff --git a/internal/dnsserver/error.go b/internal/dnsserver/error.go
index 688087b..a1341ee 100644
--- a/internal/dnsserver/error.go
+++ b/internal/dnsserver/error.go
@@ -3,6 +3,7 @@ package dnsserver
import (
"fmt"
"net"
+ "os"
"github.com/AdguardTeam/golibs/errors"
)
@@ -58,8 +59,11 @@ func (err *WriteError) Unwrap() (unwrapped error) {
// case. It seems like all places where this function is used should detect
// precise error conditions for exiting a loop instead of this.
func isNonCriticalNetError(err error) (ok bool) {
+ if errors.Is(os.ErrDeadlineExceeded, err) {
+ return true
+ }
+
var netErr net.Error
- //lint:ignore SA1019 See TODO in the function documentation.
- return errors.As(err, &netErr) && netErr.Temporary()
+ return errors.As(err, &netErr) && netErr.Timeout()
}
diff --git a/internal/dnsserver/forward/forward.go b/internal/dnsserver/forward/forward.go
index 575848e..a683ccc 100644
--- a/internal/dnsserver/forward/forward.go
+++ b/internal/dnsserver/forward/forward.go
@@ -151,22 +151,15 @@ var _ io.Closer = &Handler{}
// Close implements the [io.Closer] interface for *Handler.
func (h *Handler) Close() (err error) {
- var errs []error
-
- cErr := h.upstream.Close()
- if cErr != nil {
- errs = append(errs, cErr)
+ errs := make([]error, len(h.fallbacks)+1)
+ errs[0] = h.upstream.Close()
+ for i, f := range h.fallbacks {
+ errs[i+1] = f.Close()
}
- for _, f := range h.fallbacks {
- cErr = f.Close()
- if cErr != nil {
- errs = append(errs, cErr)
- }
- }
-
- if len(errs) > 0 {
- return errors.List("closing forward handler", errs...)
+ err = errors.Join(errs...)
+ if err != nil {
+ return fmt.Errorf("closing forward handler: %w", err)
}
return nil
diff --git a/internal/dnsserver/forward/upstreamplain.go b/internal/dnsserver/forward/upstreamplain.go
index ea01e98..97d1a32 100644
--- a/internal/dnsserver/forward/upstreamplain.go
+++ b/internal/dnsserver/forward/upstreamplain.go
@@ -109,23 +109,10 @@ func (u *UpstreamPlain) Exchange(ctx context.Context, req *dns.Msg) (resp *dns.M
// Close implements the io.Closer interface for *UpstreamPlain.
func (u *UpstreamPlain) Close() (err error) {
- var errs []error
+ udpErr := u.connsPoolUDP.Close()
+ tcpErr := u.connsPoolTCP.Close()
- err = u.connsPoolUDP.Close()
- if err != nil {
- errs = append(errs, err)
- }
-
- err = u.connsPoolTCP.Close()
- if err != nil {
- errs = append(errs, err)
- }
-
- if len(errs) > 0 {
- return errors.List("closing upstream", errs...)
- }
-
- return nil
+ return errors.Annotate(errors.Join(udpErr, tcpErr), "closing upstream: %w")
}
// String implements the fmt.Stringer interface for *UpstreamPlain.
diff --git a/internal/dnsserver/go.mod b/internal/dnsserver/go.mod
index af68597..3544026 100644
--- a/internal/dnsserver/go.mod
+++ b/internal/dnsserver/go.mod
@@ -1,49 +1,48 @@
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
-go 1.19
+go 1.20
require (
- github.com/AdguardTeam/golibs v0.11.4
+ github.com/AdguardTeam/golibs v0.12.1
github.com/ameshkov/dnscrypt/v2 v2.2.5
github.com/ameshkov/dnsstamps v1.0.3
github.com/bluele/gcache v0.0.2
- github.com/lucas-clemente/quic-go v0.31.0
- github.com/miekg/dns v1.1.50
+ github.com/miekg/dns v1.1.52
github.com/panjf2000/ants/v2 v2.7.1
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.14.0
- github.com/stretchr/testify v1.8.1
- golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
- golang.org/x/net v0.4.0
- golang.org/x/sys v0.3.0
+ github.com/quic-go/quic-go v0.33.0
+ github.com/stretchr/testify v1.8.2
+ golang.org/x/exp v0.0.0-20230307190834-24139beb5833
+ golang.org/x/net v0.8.0
+ golang.org/x/sys v0.6.0
)
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
- github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
+ github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
+ github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
- github.com/marten-seemann/qpack v0.3.0 // indirect
- github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
- github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
- github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
- github.com/onsi/ginkgo/v2 v2.5.1 // indirect
+ github.com/onsi/ginkgo/v2 v2.9.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
- github.com/prometheus/common v0.37.0 // indirect
- github.com/prometheus/procfs v0.8.0 // indirect
- golang.org/x/crypto v0.3.0 // indirect
- golang.org/x/mod v0.7.0 // indirect
- golang.org/x/text v0.5.0 // indirect
- golang.org/x/tools v0.3.0 // indirect
+ github.com/prometheus/common v0.41.0 // indirect
+ github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/quic-go/qpack v0.4.0 // indirect
+ github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
+ github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
+ golang.org/x/crypto v0.7.0 // indirect
+ golang.org/x/mod v0.9.0 // indirect
+ golang.org/x/text v0.8.0 // indirect
+ golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
- gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/internal/dnsserver/go.sum b/internal/dnsserver/go.sum
index 01acb1f..a0d5590 100644
--- a/internal/dnsserver/go.sum
+++ b/internal/dnsserver/go.sum
@@ -1,556 +1,128 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/AdguardTeam/golibs v0.11.4 h1:IltyvxwCTN+xxJF5sh6VadF8Zfbf8elgCm9dgijSVzM=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/AdguardTeam/golibs v0.12.1 h1:bJfFzCnUCl+QsP6prUltM2Sjt0fTiDBPlxuAwfKP3g8=
+github.com/AdguardTeam/golibs v0.12.1/go.mod h1:rIglKDHdLvFT1UbhumBLHO9S4cvWS9MEyT1njommI/Y=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/ameshkov/dnscrypt/v2 v2.2.5 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY=
github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+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=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
+github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
-github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
-github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
-github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
-github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
-github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
-github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
-github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
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.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
-github.com/miekg/dns v1.1.50/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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw=
-github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
-github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg=
+github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
+github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
+github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
+github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M=
github.com/panjf2000/ants/v2 v2.7.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
-github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
-github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/prometheus/common v0.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw=
+github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
+github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
+github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
+github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
+github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
+golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
+golang.org/x/mod v0.9.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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
-golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/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-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
-golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
+golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/dnsserver/netext/listenconfig_unix.go b/internal/dnsserver/netext/listenconfig_unix.go
index 09acf9b..3435b0c 100644
--- a/internal/dnsserver/netext/listenconfig_unix.go
+++ b/internal/dnsserver/netext/listenconfig_unix.go
@@ -3,6 +3,7 @@
package netext
import (
+ "fmt"
"net"
"syscall"
@@ -37,7 +38,7 @@ func setIPOpts(c net.PacketConn) (err error) {
err6 := ipv6.NewPacketConn(c).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true)
err4 := ipv4.NewPacketConn(c).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true)
if err4 != nil && err6 != nil {
- return errors.List("setting ipv4 and ipv6 options", err4, err6)
+ return fmt.Errorf("setting ipv4 and ipv6 options: %w", errors.Join(err4, err6))
}
return nil
diff --git a/internal/dnsserver/pool/pool.go b/internal/dnsserver/pool/pool.go
index a2399f1..298befe 100644
--- a/internal/dnsserver/pool/pool.go
+++ b/internal/dnsserver/pool/pool.go
@@ -3,6 +3,7 @@ package pool
import (
"context"
+ "fmt"
"net"
"sync"
"time"
@@ -118,14 +119,11 @@ func (p *Pool) Close() (err error) {
errs = append(errs, err)
}
}
+
// This marks the pool as closed.
p.connsChan = nil
- if len(errs) > 0 {
- return errors.List("errors when closing the pool", errs...)
- }
-
- return nil
+ return errors.Annotate(errors.Join(errs...), "closing pool: %w")
}
// closeConn is used when the pool is closed. In this case we attempt to close
@@ -133,11 +131,7 @@ func (p *Pool) Close() (err error) {
func (p *Pool) closeConn(conn *Conn) (err error) {
err = conn.Close()
if err != nil {
- return errors.List(
- "error while closing a pool connection",
- err,
- ErrClosed,
- )
+ return errors.WithDeferred(fmt.Errorf("closing pool connection: %w", err), ErrClosed)
}
return ErrClosed
diff --git a/internal/dnsserver/serverbench_test.go b/internal/dnsserver/serverbench_test.go
index 3b6e397..ad59fe7 100644
--- a/internal/dnsserver/serverbench_test.go
+++ b/internal/dnsserver/serverbench_test.go
@@ -16,8 +16,8 @@ import (
"github.com/AdguardTeam/golibs/testutil"
"github.com/ameshkov/dnscrypt/v2"
"github.com/ameshkov/dnsstamps"
- "github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
+ "github.com/quic-go/quic-go"
"github.com/stretchr/testify/require"
)
diff --git a/internal/dnsserver/serverhttps.go b/internal/dnsserver/serverhttps.go
index ba9c5d5..7c45652 100644
--- a/internal/dnsserver/serverhttps.go
+++ b/internal/dnsserver/serverhttps.go
@@ -18,9 +18,9 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
- "github.com/lucas-clemente/quic-go"
- "github.com/lucas-clemente/quic-go/http3"
"github.com/miekg/dns"
+ "github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2"
)
@@ -49,7 +49,7 @@ var nextProtoDoH = []string{http2.NextProtoTLS, "http/1.1"}
// nextProtoDoH3 is a list of ALPN that we should add by default to the server's
// *tls.Config if no NextProto is specified there and DoH3 is supposed to be
// used.
-var nextProtoDoH3 = []string{"h3", http2.NextProtoTLS, "http/1.1"}
+var nextProtoDoH3 = []string{http3.NextProtoH3, http2.NextProtoTLS, "http/1.1"}
// ConfigHTTPS is a struct that needs to be passed to NewServerHTTPS to
// initialize a new ServerHTTPS instance. You can choose whether HTTP/3 is
@@ -297,7 +297,7 @@ func (s *ServerHTTPS) serveH3(ctx context.Context, hs *http3.Server, ql quic.Ear
defer s.handlePanicAndExit(ctx)
u := &url.URL{
- Scheme: "h3",
+ Scheme: http3.NextProtoH3,
Host: s.addr,
}
log.Info("[%s]: Start listening to %s", s.name, u)
@@ -393,15 +393,7 @@ func (h *httpHandler) serveDoH(ctx context.Context, w http.ResponseWriter, r *ht
rAddr := h.remoteAddr(r)
lAddr := h.localAddr
rw := NewNonWriterResponseWriter(lAddr, rAddr)
-
- ctx, err = httpContextWithClientInfo(ctx, r)
- if err != nil {
- log.Debug("Failed to enrich DoH context: %v", err)
- h.srv.metrics.OnInvalidMsg(ctx)
- http.Error(w, err.Error(), http.StatusBadRequest)
-
- return
- }
+ ctx = httpContextWithClientInfo(ctx, r)
// Serve the query
written := h.srv.serveDNS(ctx, m, rw)
@@ -524,36 +516,19 @@ func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) {
return nil
}
-// httpContextWithClientInfo adds client info to the context. ctx is never nil,
-// even when there is an error.
-func httpContextWithClientInfo(
- parent context.Context,
- r *http.Request,
-) (ctx context.Context, err error) {
+// httpContextWithClientInfo adds client info to the context.
+func httpContextWithClientInfo(parent context.Context, r *http.Request) (ctx context.Context) {
ctx = parent
ci := ClientInfo{
URL: netutil.CloneURL(r.URL),
}
- // Due to the quic-go bug we should use Host instead of r.TLS. See
- // https://github.com/quic-go/quic-go/issues/2879 and
- // https://github.com/lucas-clemente/quic-go/issues/3596.
- //
- // TODO(ameshkov): Remove when quic-go is fixed, likely in v0.32.0.
- if r.ProtoAtLeast(3, 0) {
- var host string
- host, err = netutil.SplitHost(r.Host)
- if err != nil {
- return ctx, fmt.Errorf("failed to parse Host: %w", err)
- }
-
- ci.TLSServerName = host
- } else if r.TLS != nil {
+ if r.TLS != nil {
ci.TLSServerName = strings.ToLower(r.TLS.ServerName)
}
- return ContextWithClientInfo(ctx, ci), nil
+ return ContextWithClientInfo(ctx, ci)
}
// httpRequestToMsg reads the DNS message from http.Request.
diff --git a/internal/dnsserver/serverhttps_test.go b/internal/dnsserver/serverhttps_test.go
index f927798..fd9803e 100644
--- a/internal/dnsserver/serverhttps_test.go
+++ b/internal/dnsserver/serverhttps_test.go
@@ -19,9 +19,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil"
- "github.com/lucas-clemente/quic-go"
- "github.com/lucas-clemente/quic-go/http3"
"github.com/miekg/dns"
+ "github.com/quic-go/quic-go"
+ "github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)
@@ -114,14 +114,9 @@ func TestServerHTTPS_integration_serveRequests(t *testing.T) {
return srv.Shutdown(context.Background())
})
- // Create a test message
- req := new(dns.Msg)
- req.Id = dns.Id()
+ // Create a test message.
+ req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
req.RecursionDesired = true
- name := "example.org."
- req.Question = []dns.Question{
- {Name: name, Qtype: dns.TypeA, Qclass: dns.ClassINET},
- }
var resp *dns.Msg
addr := srv.LocalTCPAddr()
@@ -232,7 +227,7 @@ func TestDNSMsgToJSONMsg(t *testing.T) {
Target: "example.com",
Value: []dns.SVCBKeyValue{
&dns.SVCBAlpn{
- Alpn: []string{"h2", "h3"},
+ Alpn: []string{http2.NextProtoTLS, http3.NextProtoH3},
},
&dns.SVCBECHConfig{
ECH: []byte{1, 2},
@@ -343,6 +338,80 @@ func TestServerHTTPS_integration_ENDS0Padding(t *testing.T) {
require.NotEmpty(t, paddingOpt.Padding)
}
+func TestServerHTTPS_0RTT(t *testing.T) {
+ tlsConfig := dnsservertest.CreateServerTLSConfig("example.org")
+ srv, err := dnsservertest.RunLocalHTTPSServer(
+ dnsservertest.DefaultHandler(),
+ tlsConfig,
+ nil,
+ )
+ require.NoError(t, err)
+
+ testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return srv.Shutdown(context.Background())
+ })
+
+ quicTracer := &dnsservertest.QUICTracer{}
+
+ // quicConfig with TokenStore set so that 0-RTT was enabled.
+ quicConfig := &quic.Config{
+ TokenStore: quic.NewLRUTokenStore(1, 10),
+ Tracer: quicTracer,
+ }
+
+ // ClientSessionCache in the tls.Config must also be set for 0-RTT to work.
+ clientTLSConfig := tlsConfig.Clone()
+ clientTLSConfig.ClientSessionCache = tls.NewLRUClientSessionCache(10)
+
+ // Use the first connection (no 0-RTT).
+ testDoH3Exchange(t, srv.LocalUDPAddr(), clientTLSConfig, quicConfig)
+
+ // Use the second connection (now 0-RTT should kick in).
+ testDoH3Exchange(t, srv.LocalUDPAddr(), clientTLSConfig, quicConfig)
+
+ // Verify how 0-RTT was used.
+ conns := quicTracer.ConnectionsInfo()
+
+ require.Len(t, conns, 2)
+ require.False(t, conns[0].Is0RTT())
+ require.True(t, conns[1].Is0RTT())
+}
+
+// testDoH3Exchange initializes a new DoH3 client and sends one DNS query
+// through it.
+func testDoH3Exchange(
+ t *testing.T,
+ addr net.Addr,
+ tlsConfig *tls.Config,
+ quicConfig *quic.Config,
+) {
+ client, err := createDoH3Client(addr, tlsConfig, quicConfig)
+ require.NoError(t, err)
+
+ // Create a test message.
+ req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
+ req.RecursionDesired = true
+
+ httpReq, err := createDoHRequest("https", http.MethodGet, req)
+ require.NoError(t, err)
+
+ // Send the request and check the response.
+ httpResp, err := client.Do(httpReq)
+ require.NoError(t, err)
+ defer log.OnCloserError(httpResp.Body, log.DEBUG)
+
+ body, err := io.ReadAll(httpResp.Body)
+ require.NoError(t, err)
+
+ resp, err := unpackDoHMsg(body)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ require.True(t, resp.Response)
+
+ // Close connections.
+ client.CloseIdleConnections()
+}
+
func mustDoHReq(
t testing.TB,
httpsAddr net.Addr,
@@ -394,7 +463,7 @@ func mustDoHReq(
func createDoHClient(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) {
if dnsserver.NetworkFromAddr(httpsAddr) == dnsserver.NetworkUDP {
- return createDoH3Client(httpsAddr, tlsConfig)
+ return createDoH3Client(httpsAddr, tlsConfig, nil)
}
return createDoH2Client(httpsAddr, tlsConfig)
@@ -431,9 +500,13 @@ func createDoH2Client(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.C
}, nil
}
-func createDoH3Client(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) {
+func createDoH3Client(
+ httpsAddr net.Addr,
+ tlsConfig *tls.Config,
+ quicConfig *quic.Config,
+) (client *http.Client, err error) {
tlsConfig = tlsConfig.Clone()
- tlsConfig.NextProtos = []string{"h3"}
+ tlsConfig.NextProtos = []string{http3.NextProtoH3}
transport := &http3.RoundTripper{
DisableCompression: true,
@@ -445,6 +518,7 @@ func createDoH3Client(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.C
) (c quic.EarlyConnection, e error) {
return quic.DialAddrEarlyContext(ctx, httpsAddr.String(), tlsCfg, cfg)
},
+ QuicConfig: quicConfig,
TLSClientConfig: tlsConfig,
}
diff --git a/internal/dnsserver/serverquic.go b/internal/dnsserver/serverquic.go
index fbca4ee..016f4cc 100644
--- a/internal/dnsserver/serverquic.go
+++ b/internal/dnsserver/serverquic.go
@@ -16,9 +16,9 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/bluele/gcache"
- "github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
+ "github.com/quic-go/quic-go"
)
const (
@@ -438,10 +438,11 @@ func (s *ServerQUIC) readQUICMsg(
// The client MUST send the DNS query over the selected stream, and MUST
// indicate through the STREAM FIN mechanism that no further data will
// be sent on that stream.
-
_ = stream.SetReadDeadline(time.Now().Add(DefaultReadTimeout))
+
+ // Read the stream data until io.EOF, i.e. until FIN is received.
var n int
- n, err = stream.Read(buf)
+ n, err = readAll(stream, buf)
// err is not checked here because STREAM FIN sent by the client is
// indicated as an error here. instead, we should check the number of bytes
@@ -479,6 +480,32 @@ func (s *ServerQUIC) readQUICMsg(
return m, doqDraft, nil
}
+// readAll reads from r until an error or io.EOF into the specified buffer buf.
+// A successful call returns err == nil, not err == io.EOF. If the buffer is
+// too small, it returns error io.ErrShortBuffer. This function has some
+// similarities to io.ReadAll, but it reads to the specified buffer and not
+// allocates (and grows) a new one. Also, it is completely different from
+// io.ReadFull as that one reads the exact number of bytes (buffer length) and
+// readAll reads until io.EOF or until the buffer is filled.
+func readAll(r io.Reader, buf []byte) (n int, err error) {
+ for {
+ if n == len(buf) {
+ return n, io.ErrShortBuffer
+ }
+
+ var read int
+ read, err = r.Read(buf[n:])
+ n += read
+
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return n, err
+ }
+ }
+}
+
// listenQUIC creates the UDP listener for the ServerQUIC.addr and also starts
// the QUIC listener.
func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) {
@@ -630,9 +657,14 @@ func newServerQUICConfig(metrics MetricsListener) (conf *quic.Config) {
return &quic.Config{
MaxIdleTimeout: maxQUICIdleTimeout,
- RequireAddressValidation: v.requiresValidation,
MaxIncomingStreams: math.MaxUint16,
MaxIncomingUniStreams: math.MaxUint16,
+ RequireAddressValidation: v.requiresValidation,
+ // Enable 0-RTT by default for all addresses, it's beneficial for the
+ // performance.
+ Allow0RTT: func(net.Addr) (ok bool) {
+ return true
+ },
}
}
diff --git a/internal/dnsserver/serverquic_test.go b/internal/dnsserver/serverquic_test.go
index 600c975..cc0a86d 100644
--- a/internal/dnsserver/serverquic_test.go
+++ b/internal/dnsserver/serverquic_test.go
@@ -2,18 +2,21 @@ package dnsserver_test
import (
"context"
+ "crypto/tls"
"encoding/binary"
"io"
+ "net"
"sync"
"testing"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil"
- "github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
+ "github.com/quic-go/quic-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -34,9 +37,9 @@ func TestServerQUIC_integration_query(t *testing.T) {
conn, err := quic.DialAddr(addr.String(), tlsConfig, nil)
require.NoError(t, err)
- defer func(conn quic.Connection, code quic.ApplicationErrorCode, s string) {
- _ = conn.CloseWithError(code, s)
- }(conn, 0, "")
+ defer testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return conn.CloseWithError(0, "")
+ })
// Send multiple queries to the DNS server in parallel
wg := &sync.WaitGroup{}
@@ -102,6 +105,111 @@ func TestServerQUIC_integration_ENDS0Padding(t *testing.T) {
require.NotEmpty(t, paddingOpt.Padding)
}
+func TestServerQUIC_integration_0RTT(t *testing.T) {
+ tlsConfig := dnsservertest.CreateServerTLSConfig("example.org")
+ srv, addr, err := dnsservertest.RunLocalQUICServer(
+ dnsservertest.DefaultHandler(),
+ tlsConfig,
+ )
+ require.NoError(t, err)
+
+ testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return srv.Shutdown(context.Background())
+ })
+
+ quicTracer := &dnsservertest.QUICTracer{}
+
+ // quicConfig with TokenStore set so that 0-RTT was enabled.
+ quicConfig := &quic.Config{
+ TokenStore: quic.NewLRUTokenStore(1, 10),
+ Tracer: quicTracer,
+ }
+
+ // ClientSessionCache in the tls.Config must also be set for 0-RTT to work.
+ clientTLSConfig := tlsConfig.Clone()
+ clientTLSConfig.ClientSessionCache = tls.NewLRUClientSessionCache(10)
+
+ // Use the first connection (no 0-RTT).
+ testQUICExchange(t, addr, clientTLSConfig, quicConfig)
+
+ // Use the second connection (now 0-RTT should kick in).
+ testQUICExchange(t, addr, clientTLSConfig, quicConfig)
+
+ // Verify how 0-RTT was used.
+ conns := quicTracer.ConnectionsInfo()
+
+ require.Len(t, conns, 2)
+ require.False(t, conns[0].Is0RTT())
+ require.True(t, conns[1].Is0RTT())
+}
+
+func TestServerQUIC_integration_largeQuery(t *testing.T) {
+ tlsConfig := dnsservertest.CreateServerTLSConfig("example.org")
+ srv, addr, err := dnsservertest.RunLocalQUICServer(
+ dnsservertest.DefaultHandler(),
+ tlsConfig,
+ )
+ require.NoError(t, err)
+
+ testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return srv.Shutdown(context.Background())
+ })
+
+ // Open a QUIC connection.
+ conn, err := quic.DialAddr(addr.String(), tlsConfig, nil)
+ require.NoError(t, err)
+
+ defer testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return conn.CloseWithError(0, "")
+ })
+
+ // Create a test message large enough so that it was sent using multiple
+ // QUIC frames.
+ req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
+ req.RecursionDesired = true
+ req.Extra = []dns.RR{
+ &dns.OPT{
+ Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT, Class: 4096},
+ Option: []dns.EDNS0{
+ &dns.EDNS0_PADDING{Padding: make([]byte, 4096)},
+ },
+ },
+ }
+
+ resp, err := sendQUICMessage(conn, req, false)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ require.True(t, resp.Response)
+}
+
+// testQUICExchange initializes a new QUIC connection and sends one test DNS
+// query through it.
+func testQUICExchange(
+ t *testing.T,
+ addr *net.UDPAddr,
+ tlsConfig *tls.Config,
+ quicConfig *quic.Config,
+) {
+ conn, err := quic.DialAddrEarly(addr.String(), tlsConfig, quicConfig)
+ require.NoError(t, err)
+
+ defer testutil.CleanupAndRequireSuccess(t, func() (err error) {
+ return conn.CloseWithError(0, "")
+ })
+
+ defer func(conn quic.Connection, code quic.ApplicationErrorCode, s string) {
+ _ = conn.CloseWithError(code, s)
+ }(conn, 0, "")
+
+ // Create a test message.
+ req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
+ req.RecursionDesired = true
+
+ resp, err := sendQUICMessage(conn, req, false)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+}
+
// sendQUICMessage sends a test QUIC message.
func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Msg, error) {
// Open stream.
@@ -126,14 +234,14 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
copy(buf[2:], data)
}
- // Send the DNS query to the stream.
- _, err = stream.Write(buf)
+ err = writeQUICStream(buf, stream)
if err != nil {
return nil, err
}
- // Close closes the write-direction of the stream
- // and sends a STREAM FIN packet.
+ // Closes the write-direction of the stream and sends a STREAM FIN packet.
+ // A DoQ client MUST send a FIN packet to indicate that the query is
+ // finished.
_ = stream.Close()
// Now read the response.
@@ -161,3 +269,30 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
return reply, nil
}
+
+// writeQUICStream writes buf to the specified QUIC stream in chunks. This way
+// it is possible to test how the server deals with chunked DNS messages.
+func writeQUICStream(buf []byte, stream quic.Stream) (err error) {
+ // Send the DNS query to the stream and split it into chunks of up
+ // to 400 bytes. 400 is an arbitrary chosen value.
+ chunkSize := 400
+ for i := 0; i < len(buf); i += chunkSize {
+ chunkStart := i
+ chunkEnd := i + chunkSize
+ if chunkEnd > len(buf) {
+ chunkEnd = len(buf)
+ }
+
+ _, err = stream.Write(buf[chunkStart:chunkEnd])
+ if err != nil {
+ return err
+ }
+
+ if len(buf) > chunkSize {
+ // Emulate network latency.
+ time.Sleep(time.Millisecond)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/dnssvc/debug_internal_test.go b/internal/dnssvc/debug_internal_test.go
index 24d1c20..91c5eb5 100644
--- a/internal/dnssvc/debug_internal_test.go
+++ b/internal/dnssvc/debug_internal_test.go
@@ -3,10 +3,9 @@ package dnssvc
import (
"context"
"testing"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
@@ -21,11 +20,13 @@ import (
func newTXTExtra(strs [][2]string) (extra []dns.RR) {
for _, v := range strs {
extra = append(extra, &dns.TXT{
+ // TODO(a.garipov): Consider exporting dnsmsg.Constructor.newHdr and
+ // using it here.
Hdr: dns.RR_Header{
Name: v[0],
Rrtype: dns.TypeTXT,
Class: dns.ClassCHAOS,
- Ttl: 1,
+ Ttl: uint32(agdtest.FilteredResponseTTL.Seconds()),
},
Txt: []string{v[1]},
})
@@ -35,7 +36,9 @@ func newTXTExtra(strs [][2]string) (extra []dns.RR) {
}
func TestService_writeDebugResponse(t *testing.T) {
- svc := &Service{messages: &dnsmsg.Constructor{FilteredResponseTTL: time.Second}}
+ svc := &Service{
+ messages: agdtest.NewConstructor(),
+ }
const (
fltListID1 agd.FilterListID = "fl1"
diff --git a/internal/dnssvc/deviceid_internal_test.go b/internal/dnssvc/deviceid_internal_test.go
index 2058ae8..f70ebe0 100644
--- a/internal/dnssvc/deviceid_internal_test.go
+++ b/internal/dnssvc/deviceid_internal_test.go
@@ -56,7 +56,7 @@ func TestService_Wrap_deviceID(t *testing.T) {
cliSrvName: "!!!.dns.example.com",
wantDeviceID: "",
wantErrMsg: `tls server name device id check: bad device id "!!!": ` +
- `bad domain name label rune '!'`,
+ `bad hostname label rune '!'`,
wildcards: []string{"*.dns.example.com"},
proto: agd.ProtoDoT,
}, {
@@ -159,7 +159,7 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) {
path: "/dns-query/!!!",
wantDeviceID: "",
wantErrMsg: `http url device id check: bad device id "!!!": ` +
- `bad domain name label rune '!'`,
+ `bad hostname label rune '!'`,
}}
const proto = agd.ProtoDoH
diff --git a/internal/dnssvc/dnssvc.go b/internal/dnssvc/dnssvc.go
index d3ba3da..a5872ad 100644
--- a/internal/dnssvc/dnssvc.go
+++ b/internal/dnssvc/dnssvc.go
@@ -8,7 +8,6 @@ import (
"context"
"fmt"
"net/http"
- "net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
@@ -16,6 +15,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
+ "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/filter"
@@ -81,9 +81,8 @@ type Config struct {
// NewListener, when set, is used instead of the package-level function
// NewListener when creating a DNS listener.
//
- // TODO(a.garipov, ameshkov): Consider creating a single dnsserver.Dialer
- // interface/builder function to use instead of the many
- // dnsserver.NewServerFoo constructors.
+ // TODO(a.garipov): The handler and service logic should really not be
+ // internwined in this way. See AGDNS-1327.
NewListener NewListenerFunc
// Handler is used as the main DNS handler instead of a simple forwarder.
@@ -270,14 +269,14 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) {
for _, s := range g.servers {
err = shutdownListeners(ctx, s.listeners)
if err != nil {
- err = fmt.Errorf("group %q: server %q: %w", g.name, s.name, err)
- errs = append(errs, err)
+ errs = append(errs, fmt.Errorf("group %q: server %q: %w", g.name, s.name, err))
}
}
}
- if len(errs) > 0 {
- return errors.List("shutting down dns service", errs...)
+ err = errors.Join(errs...)
+ if err != nil {
+ return fmt.Errorf("shutting down dns service: %w", err)
}
return nil
@@ -328,10 +327,11 @@ type Listener = dnsserver.Server
type NewListenerFunc func(
s *agd.Server,
name string,
- addr netip.AddrPort,
+ addr string,
h dnsserver.Handler,
nonDNS http.Handler,
errColl agd.ErrorCollector,
+ lc netext.ListenConfig,
) (l Listener, err error)
// listener is a Listener along with some of its associated data.
@@ -342,8 +342,8 @@ type listener struct {
}
// listenerName returns a standard name for a listener.
-func listenerName(srvName agd.ServerName, addr netip.AddrPort, p agd.Protocol) (name string) {
- return fmt.Sprintf("%s/%s/%s", srvName, p, addr)
+func listenerName(srvName agd.ServerName, addr string, proto agd.Protocol) (name string) {
+ return fmt.Sprintf("%s/%s/%s", srvName, proto, addr)
}
// NewListener returns a new Listener. It is the default DNS listener
@@ -351,15 +351,15 @@ func listenerName(srvName agd.ServerName, addr netip.AddrPort, p agd.Protocol) (
func NewListener(
s *agd.Server,
name string,
- addr netip.AddrPort,
+ addr string,
h dnsserver.Handler,
nonDNS http.Handler,
errColl agd.ErrorCollector,
+ lc netext.ListenConfig,
) (l Listener, err error) {
defer func() { err = errors.Annotate(err, "listener %q: %w", name) }()
dcConf := s.DNSCrypt
- addrStr := addr.String()
metricsListener := &errCollMetricsListener{
errColl: errColl,
@@ -367,11 +367,13 @@ func NewListener(
}
confBase := dnsserver.ConfigBase{
- Name: name,
- Addr: addrStr,
- Handler: h,
- BaseContext: ctxWithReqID,
- Metrics: metricsListener,
+ Name: name,
+ Addr: addr,
+ Network: dnsserver.NetworkAny,
+ Handler: h,
+ Metrics: metricsListener,
+ BaseContext: ctxWithReqID,
+ ListenConfig: lc,
}
switch p := s.Protocol; p {
@@ -464,12 +466,17 @@ func newServers(
imw,
)
- listeners := make([]*listener, 0, len(s.BindAddresses))
- for _, addr := range s.BindAddresses {
+ 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)
var l Listener
- l, err = newListener(s, name, addr, h, c.NonDNS, c.ErrColl)
+ l, err = newListener(s, name, addr, h, c.NonDNS, c.ErrColl, bindData.ListenConfig)
if err != nil {
return nil, fmt.Errorf("server %q: %w", s.Name, err)
}
diff --git a/internal/dnssvc/dnssvc_test.go b/internal/dnssvc/dnssvc_test.go
index 3335e09..c98aa78 100644
--- a/internal/dnssvc/dnssvc_test.go
+++ b/internal/dnssvc/dnssvc_test.go
@@ -13,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
@@ -105,10 +106,11 @@ func newTestListenerFunc(tl *testListener) (f dnssvc.NewListenerFunc) {
return func(
_ *agd.Server,
_ string,
- _ netip.AddrPort,
+ _ string,
_ dnsserver.Handler,
_ http.Handler,
_ agd.ErrorCollector,
+ _ netext.ListenConfig,
) (l dnssvc.Listener, err error) {
return tl, nil
}
@@ -157,9 +159,11 @@ func TestService_Start(t *testing.T) {
}
srv := &agd.Server{
- Name: "test_server",
- Protocol: agd.ProtoDNS,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
+ Name: "test_server",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
+ }},
+ Protocol: agd.ProtoDNS,
}
c := &dnssvc.Config{
@@ -195,35 +199,45 @@ func TestService_Start(t *testing.T) {
func TestNew(t *testing.T) {
srvs := []*agd.Server{{
- DNSCrypt: nil,
- TLS: nil,
- Name: "test_server_dns",
- Protocol: agd.ProtoDNS,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
+ DNSCrypt: nil,
+ TLS: nil,
+ Name: "test_server_dns",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
+ }},
+ Protocol: agd.ProtoDNS,
}, {
- DNSCrypt: &agd.DNSCryptConfig{},
- TLS: nil,
- Name: "test_server_dnscrypt_tcp",
- Protocol: agd.ProtoDNSCrypt,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:8853")},
+ DNSCrypt: &agd.DNSCryptConfig{},
+ TLS: nil,
+ Name: "test_server_dnscrypt_tcp",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:8853"),
+ }},
+ Protocol: agd.ProtoDNSCrypt,
}, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_doh",
- Protocol: agd.ProtoDoH,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
+ DNSCrypt: nil,
+ TLS: &tls.Config{},
+ Name: "test_server_doh",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:443"),
+ }},
+ Protocol: agd.ProtoDoH,
}, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_doq",
- Protocol: agd.ProtoDoQ,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:853")},
+ DNSCrypt: nil,
+ TLS: &tls.Config{},
+ Name: "test_server_doq",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
+ }},
+ Protocol: agd.ProtoDoQ,
}, {
- DNSCrypt: nil,
- TLS: &tls.Config{},
- Name: "test_server_dot",
- Protocol: agd.ProtoDoT,
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:853")},
+ DNSCrypt: nil,
+ TLS: &tls.Config{},
+ Name: "test_server_dot",
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
+ }},
+ Protocol: agd.ProtoDoT,
}}
c := &dnssvc.Config{
diff --git a/internal/dnssvc/initmw.go b/internal/dnssvc/initmw.go
index 87d5c27..c4dbd36 100644
--- a/internal/dnssvc/initmw.go
+++ b/internal/dnssvc/initmw.go
@@ -187,9 +187,7 @@ func (mw *initMw) addProfile(ctx context.Context, ri *agd.RequestInfo, req *dns.
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.Constructor{
- FilteredResponseTTL: prof.FilteredResponseTTL,
- }
+ ri.Messages = dnsmsg.NewConstructor(prof.BlockingMode.Mode, prof.FilteredResponseTTL)
}
return nil
diff --git a/internal/dnssvc/initmw_internal_test.go b/internal/dnssvc/initmw_internal_test.go
index f5cb8b7..74ffb13 100644
--- a/internal/dnssvc/initmw_internal_test.go
+++ b/internal/dnssvc/initmw_internal_test.go
@@ -6,7 +6,6 @@ import (
"net"
"net/netip"
"testing"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
@@ -45,23 +44,31 @@ func TestInitMw_ServeDNS_ddr(t *testing.T) {
srvs := map[agd.ServerName]*agd.Server{
"dot": {
- TLS: &tls.Config{},
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("1.2.3.4:12345")},
- Protocol: agd.ProtoDoT,
+ TLS: &tls.Config{},
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
+ }},
+ Protocol: agd.ProtoDoT,
},
"doh": {
- TLS: &tls.Config{},
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("5.6.7.8:54321")},
- Protocol: agd.ProtoDoH,
+ TLS: &tls.Config{},
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("5.6.7.8:54321"),
+ }},
+ Protocol: agd.ProtoDoH,
},
"dns": {
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("2.4.6.8:53")},
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
+ }},
Protocol: agd.ProtoDNS,
LinkedIPEnabled: true,
},
"dns_nolink": {
- BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("2.4.6.8:53")},
- Protocol: agd.ProtoDNS,
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("2.4.6.8:53"),
+ }},
+ Protocol: agd.ProtoDNS,
},
}
@@ -83,11 +90,9 @@ func TestInitMw_ServeDNS_ddr(t *testing.T) {
var dev *agd.Device
mw := &initMw{
- messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
- fltGrp: &agd.FilteringGroup{},
- srvGrp: srvGrp,
+ messages: agdtest.NewConstructor(),
+ fltGrp: &agd.FilteringGroup{},
+ srvGrp: srvGrp,
db: &agdtest.ProfileDB{
OnProfileByDeviceID: func(
_ context.Context,
@@ -437,9 +442,7 @@ func TestInitMw_ServeDNS_specialDomain(t *testing.T) {
}
mw := &initMw{
- messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
+ messages: agdtest.NewConstructor(),
fltGrp: &agd.FilteringGroup{
BlockPrivateRelay: tc.fltGrpBlocked,
BlockFirefoxCanary: tc.fltGrpBlocked,
@@ -492,19 +495,18 @@ func BenchmarkInitMw_Wrap(b *testing.B) {
},
Name: agd.ServerGroupName("test_server_group"),
Servers: []*agd.Server{{
- BindAddresses: []netip.AddrPort{
- netip.MustParseAddrPort("1.2.3.4:12345"),
- netip.MustParseAddrPort("4.3.2.1:12345"),
- },
+ BindData: []*agd.ServerBindData{{
+ AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
+ }, {
+ AddrPort: netip.MustParseAddrPort("4.3.2.1:12345"),
+ }},
Protocol: agd.ProtoDoT,
}},
}
- messages := &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- }
+ messages := agdtest.NewConstructor()
- ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindAddresses[0].Addr()}
+ ipv4Hints := []netip.Addr{srvGrp.Servers[0].BindData[0].AddrPort.Addr()}
ipv6Hints := []netip.Addr{netip.MustParseAddr("2001::1234")}
srvGrp.DDR.DeviceTargets.Add(devIDTarget)
diff --git a/internal/dnssvc/middleware_test.go b/internal/dnssvc/middleware_test.go
index a93f251..7615556 100644
--- a/internal/dnssvc/middleware_test.go
+++ b/internal/dnssvc/middleware_test.go
@@ -148,11 +148,13 @@ func TestService_Wrap_withClient(t *testing.T) {
srvAddr := netip.MustParseAddrPort("94.149.14.14:853")
srvName := agd.ServerName("test_server_dns_tls")
srvs := []*agd.Server{{
- DNSCrypt: nil,
- TLS: nil,
- Name: srvName,
- Protocol: agd.ProtoDoT,
- BindAddresses: []netip.AddrPort{srvAddr},
+ DNSCrypt: nil,
+ TLS: nil,
+ Name: srvName,
+ BindData: []*agd.ServerBindData{{
+ AddrPort: srvAddr,
+ }},
+ Protocol: agd.ProtoDoT,
}}
tl := newTestListener()
@@ -211,9 +213,7 @@ func TestService_Wrap_withClient(t *testing.T) {
fltGrpID := agd.FilteringGroupID("1234")
srvGrpName := agd.ServerGroupName("test_group")
c := &dnssvc.Config{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
+ Messages: agdtest.NewConstructor(),
BillStat: &agdtest.BillStatRecorder{
OnRecord: func(
_ context.Context,
diff --git a/internal/dnssvc/presvcmw_test.go b/internal/dnssvc/presvcmw_test.go
index 345dac6..7f2071f 100644
--- a/internal/dnssvc/presvcmw_test.go
+++ b/internal/dnssvc/presvcmw_test.go
@@ -114,11 +114,9 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
}
mw := &preServiceMw{
- messages: &dnsmsg.Constructor{
- FilteredResponseTTL: ttl * time.Second,
- },
- filter: srv,
- checker: dnsCk,
+ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
+ filter: srv,
+ checker: dnsCk,
}
handler := dnsservertest.DefaultHandler()
h := mw.Wrap(handler)
diff --git a/internal/dnssvc/record.go b/internal/dnssvc/record.go
index 84fb062..f7adcb3 100644
--- a/internal/dnssvc/record.go
+++ b/internal/dnssvc/record.go
@@ -174,15 +174,7 @@ func (svc *Service) responseData(
}
if netIP != nil {
- // Make sure to validate the IP address, because it's not guaranteed to
- // actually be valid.
- //
- // See https://github.com/miekg/dns/issues/1381.
- err := netutil.ValidateIP(netIP)
- if err != nil {
- svc.reportf(ctx, "reading %s resp data: %w", rrType, err)
- }
-
+ var err error
ip, err = netutil.IPToAddr(netIP, fam)
if err != nil {
svc.reportf(ctx, "converting %s resp data: %w", rrType, err)
diff --git a/internal/dnssvc/resp_internal_test.go b/internal/dnssvc/resp_internal_test.go
index 44b80b9..d8d65e4 100644
--- a/internal/dnssvc/resp_internal_test.go
+++ b/internal/dnssvc/resp_internal_test.go
@@ -85,9 +85,7 @@ func TestWriteFilteredResp(t *testing.T) {
ctx := context.Background()
ri := &agd.RequestInfo{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: fltRespTTL * time.Second,
- },
+ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, fltRespTTL*time.Second),
}
for _, tc := range testCases {
diff --git a/internal/ecscache/cache.go b/internal/ecscache/cache.go
index 860a504..883a74c 100644
--- a/internal/ecscache/cache.go
+++ b/internal/ecscache/cache.go
@@ -20,23 +20,46 @@ import (
// cacheRequest contains data necessary to get a value from the cache. It is
// used to optimize goroutine stack usage.
type cacheRequest struct {
- host string
+ // host is a non-FQDN version of a cached hostname.
+ host string
+
+ // subnet is the network of the country the DNS request came from determined
+ // with GeoIP.
subnet netip.Prefix
- qType uint16
+
+ // qType is the question type of the DNS request.
+ qType uint16
+
+ // qClass is the class of the DNS request.
qClass uint16
- reqDO bool
+
+ // reqDO is the state of DNSSEC OK bit from the DNS request.
+ reqDO bool
+
+ // isECSDeclined reflects if the client explicitly restricts using its
+ // information in EDNS client subnet option as per RFC 7871.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.1.2.
+ isECSDeclined bool
}
// get retrieves a DNS message for the specified request from the cache, if
// there is one. If the host was found in the cache for domain names that
-// support ECS, hostHasECS is true. cr, cr.req, and cr.subnet must not be nil.
-func (mw *Middleware) get(req *dns.Msg, cr *cacheRequest) (resp *dns.Msg, found, hostHasECS bool) {
+// support ECS, isECSDependent is true. cr, cr.req, and cr.subnet must not be
+// nil.
+func (mw *Middleware) get(
+ req *dns.Msg,
+ cr *cacheRequest,
+) (resp *dns.Msg, found, isECSDependent bool) {
key := mw.toCacheKey(cr, false)
item, ok := itemFromCache(mw.cache, key, cr)
if ok {
return fromCacheItem(item, req, cr.reqDO), true, false
+ } else if cr.isECSDeclined {
+ return nil, false, false
}
+ // Try ECS-aware cache.
key = mw.toCacheKey(cr, true)
item, ok = itemFromCache(mw.ecsCache, key, cr)
if ok {
@@ -82,9 +105,11 @@ var hashSeed = maphash.MakeSeed()
// toCacheKey returns the appropriate cache key for msg. msg must have one
// question record. subnet must not be nil.
-func (mw *Middleware) toCacheKey(cr *cacheRequest, hostHasECS bool) (key uint64) {
+func (mw *Middleware) toCacheKey(cr *cacheRequest, respIsECSDependent bool) (key uint64) {
// Use maphash explicitly instead of using a key structure to reduce
// allocations and optimize interface conversion up the stack.
+ //
+ // TODO(a.garipov, e.burkov): Consider just using struct as a key.
h := &maphash.Hash{}
h.SetSeed(hashSeed)
@@ -102,9 +127,11 @@ func (mw *Middleware) toCacheKey(cr *cacheRequest, hostHasECS bool) (key uint64)
_, _ = h.Write(buf[:])
- if hostHasECS {
+ if respIsECSDependent {
_, _ = h.Write(addr.AsSlice())
_ = h.WriteByte(byte(cr.subnet.Bits()))
+ } else {
+ _ = h.WriteByte(mathutil.BoolToNumber[byte](cr.isECSDeclined))
}
return h.Sum64()
@@ -112,18 +139,18 @@ func (mw *Middleware) toCacheKey(cr *cacheRequest, hostHasECS bool) (key uint64)
// set saves resp to the cache if it's cacheable. If msg cannot be cached, it
// is ignored.
-func (mw *Middleware) set(resp *dns.Msg, cr *cacheRequest, hostHasECS bool) {
+func (mw *Middleware) set(resp *dns.Msg, cr *cacheRequest, respIsECSDependent bool) {
ttl := findLowestTTL(resp)
if ttl == 0 || !isCacheable(resp) {
return
}
cache := mw.cache
- if hostHasECS {
+ if respIsECSDependent {
cache = mw.ecsCache
}
- key := mw.toCacheKey(cr, hostHasECS)
+ key := mw.toCacheKey(cr, respIsECSDependent)
item := toCacheItem(resp, cr.host)
err := cache.SetWithExpire(key, item, time.Duration(ttl)*time.Second)
diff --git a/internal/ecscache/ecscache.go b/internal/ecscache/ecscache.go
index 79612b9..173e6f0 100644
--- a/internal/ecscache/ecscache.go
+++ b/internal/ecscache/ecscache.go
@@ -5,7 +5,6 @@ package ecscache
import (
"context"
"fmt"
- "net/netip"
"sync"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -15,6 +14,7 @@ import (
"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/bluele/gcache"
"github.com/miekg/dns"
@@ -87,27 +87,30 @@ func writeCachedResponse(
resp *dns.Msg,
ecs *agd.ECS,
ecsFam netutil.AddrFamily,
- hostHasECS bool,
+ respIsECSDependent bool,
) (err error) {
// Increment the hits metrics here, since we already know if the domain name
// supports ECS or not from the cache data. Increment the misses metrics in
// writeResponse, where this information is retrieved from the upstream
metrics.ECSCacheLookupTotalHits.Inc()
- if hostHasECS {
+ if respIsECSDependent {
metrics.ECSCacheLookupHasSupportHits.Inc()
-
- // Only set the ECS info if the request had it originally.
- if ecs != nil {
- err = setECS(resp, ecs, ecsFam, true)
- if err != nil {
- return fmt.Errorf("setting ecs for cached resp: %w", err)
- }
- }
} else {
metrics.ECSCacheLookupNoSupportHits.Inc()
}
+ // If the client query did include the ECS option, the server MUST include
+ // one in its response.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.2.2.
+ if ecs != nil {
+ err = setECS(resp, ecs, ecsFam, true)
+ if err != nil {
+ return fmt.Errorf("setting ecs for cached resp: %w", err)
+ }
+ }
+
// resp doesn't need additional filtering, since all hop-to-hop data has
// been filtered when setting the cache, and the AD bit was set when resp
// was being retrieved from the cache.
@@ -123,21 +126,21 @@ func writeCachedResponse(
// the request information using either the contents of the EDNS Client Subnet
// option or the real remote IP address.
func ecsFamFromReq(ri *agd.RequestInfo) (ecsFam netutil.AddrFamily) {
- if ecs := ri.ECS; ecs != nil {
- if ecs.Subnet.Addr().Is4() {
- return netutil.AddrFamilyIPv4
- }
+ // Assume that families other than IPv4 and IPv6 have been filtered out
+ // by dnsmsg.ECSFromMsg.
+ var is4 func() (ok bool)
- // Assume that families other than IPv4 and IPv6 have been filtered out
- // by dnsmsg.ECSFromMsg.
- return netutil.AddrFamilyIPv6
+ if ecs := ri.ECS; ecs != nil {
+ is4 = ecs.Subnet.Addr().Is4
+ } else {
+ // Set the address family parameter to the one of the client's address
+ // as per RFC 7871.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.1.1.
+ is4 = ri.RemoteIP.Is4
}
- // Set the address family parameter to the one of the client's address as
- // per RFC 7871.
- //
- // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.1.1.
- if ri.RemoteIP.Is4() {
+ if is4() {
return netutil.AddrFamilyIPv4
}
@@ -184,8 +187,12 @@ func (mw *Middleware) writeUpstreamResponse(
metrics.ECSCacheLookupTotalMisses.Inc()
- hostHasECS := scope != 0 && subnet != (netip.Prefix{})
- if hostHasECS {
+ // TODO(e.burkov, a.garipov): Think about ways to mitigate the situation
+ // where an authoritative nameserver incorrectly echoes our ECS data.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.2.1.
+ respIsECSDependent := scope != 0
+ if respIsECSDependent {
metrics.ECSCacheLookupHasSupportMisses.Inc()
metrics.ECSHasSupportCacheSize.Set(float64(mw.ecsCache.Len(false)))
} else {
@@ -193,15 +200,19 @@ func (mw *Middleware) writeUpstreamResponse(
metrics.ECSNoSupportCacheSize.Set(float64(mw.cache.Len(false)))
cr.subnet = netutil.ZeroPrefix(ecsFam)
- ecsFam = netutil.AddrFamilyNone
}
- mw.set(resp, cr, hostHasECS)
+ mw.set(resp, cr, respIsECSDependent)
// Set the AD bit and ECS information here, where it is safe to do so, since
// a clone of the otherwise filtered response has already been set to cache.
setRespAD(resp, req.AuthenticatedData, reqDO)
- if hostHasECS && ri.ECS != nil {
+
+ // If the client query did include the ECS option, the server MUST include
+ // one in its response.
+ //
+ // See https://datatracker.ietf.org/doc/html/rfc7871#section-7.2.2.
+ if ri.ECS != nil {
err = setECS(resp, ri.ECS, ecsFam, true)
if err != nil {
return fmt.Errorf("responding with ecs: %w", err)
@@ -240,37 +251,52 @@ func (mh *mwHandler) ServeDNS(
}()
ri := agd.MustRequestInfoFromContext(ctx)
+
cr.host, cr.qType, cr.qClass = ri.Host, ri.QType, ri.QClass
-
- // Try getting a cached result using the data from the request and the
- // subnet of the location. If there is one, write, increment the metrics,
- // and return. See also writeCachedResponse.
- ecsFam := ecsFamFromReq(ri)
- ctry, asn := locFromReq(ri)
- cr.subnet, err = mw.geoIP.SubnetByLocation(ctry, asn, ecsFam)
- if err != nil {
- return fmt.Errorf("getting subnet for country %s (family: %d): %w", ctry, ecsFam, err)
- }
-
- optlog.Debug3("ecscache: got ctry %s, asn %d, subnet %s", ctry, asn, cr.subnet)
-
cr.reqDO = dnsmsg.IsDO(req)
- resp, found, hostHasECS := mw.get(req, cr)
- if found {
- // Don't wrap the error, because it's informative enough as is.
- return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, hostHasECS)
+
+ ecsFam := ecsFamFromReq(ri)
+
+ cr.isECSDeclined = ri.ECS != nil && ri.ECS.Subnet.Bits() == 0
+ if cr.isECSDeclined {
+ // Don't perform subnet lookup when ECS contains zero-length prefix.
+ // Cache key calculation shouldn't consider the subnet of the cache
+ // request in this case, but the actual DNS request generated on cache
+ // miss will use this data.
+ log.Debug("ecscache: explicitly declined ecs")
+
+ cr.subnet = netutil.ZeroPrefix(ecsFam)
+ } else {
+ ctry, asn := locFromReq(ri)
+ cr.subnet, err = mw.geoIP.SubnetByLocation(ctry, asn, ecsFam)
+ if err != nil {
+ return fmt.Errorf("getting subnet for country %s (family: %d): %w", ctry, ecsFam, err)
+ }
+
+ optlog.Debug3("ecscache: got ctry %s, asn %d, subnet %s", ctry, asn, cr.subnet)
}
- // Perform an upstream request with the ECS data for the location. If
- // successful, write, increment the metrics, and return. See also
- // writeUpstreamResponse.
- reqECS := &agd.ECS{
+ // Try getting a cached result using the subnet of the location or zero one
+ // when explicitly requested by user. If there is one, write, increment the
+ // metrics, and return. See also [writeCachedResponse].
+ resp, found, respIsECSDependent := mw.get(req, cr)
+ if found {
+ optlog.Debug1("ecscache: using cached response (ecs-aware: %t)", respIsECSDependent)
+
+ // Don't wrap the error, because it's informative enough as is.
+ return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, respIsECSDependent)
+ }
+
+ log.Debug("ecscache: no cached response")
+
+ // Perform an upstream request with the ECS data for the location or zero
+ // one on circumstances described above. If successful, write, increment
+ // the metrics, and return. See also [writeUpstreamResponse].
+ ecsReq := dnsmsg.Clone(req)
+ err = setECS(ecsReq, &agd.ECS{
Subnet: cr.subnet,
Scope: 0,
- }
-
- ecsReq := dnsmsg.Clone(req)
- err = setECS(ecsReq, reqECS, ecsFam, false)
+ }, ecsFam, false)
if err != nil {
return fmt.Errorf("setting ecs for upstream req: %w", err)
}
diff --git a/internal/ecscache/ecscache_test.go b/internal/ecscache/ecscache_test.go
index 4a192e1..26ae13b 100644
--- a/internal/ecscache/ecscache_test.go
+++ b/internal/ecscache/ecscache_test.go
@@ -2,9 +2,11 @@ package ecscache_test
import (
"context"
+ "fmt"
"net"
"net/netip"
"testing"
+ "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
@@ -32,7 +34,7 @@ const (
)
// defaultTTL is the default TTL to use in tests.
-const defaultTTL = 3600
+const defaultTTL uint32 = 3600
// remoteIP is the IP to use for tests.
var remoteIP = netip.MustParseAddr("1.2.3.4")
@@ -151,23 +153,16 @@ func TestMiddleware_Wrap_noECS(t *testing.T) {
agd.CountryNone,
netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
)
- ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{
+ ri := &agd.RequestInfo{
Host: tc.req.Question[0].Name,
RemoteIP: remoteIP,
- })
-
- var err error
- var nrw *dnsserver.NonWriterResponseWriter
- for i := 0; i < N; i++ {
- // TODO(a.garipov): Propose netip.Addr.WithPort.
- addr := &net.UDPAddr{IP: remoteIP.AsSlice(), Port: 53}
- nrw = dnsserver.NewNonWriterResponseWriter(addr, addr)
- err = withCache.ServeDNS(ctx, nrw, tc.req)
}
- require.NoError(t, err)
+ var msg *dns.Msg
+ for i := 0; i < N; i++ {
+ msg = exchange(t, ri, withCache, tc.req)
+ }
- msg := nrw.Msg()
assert.Equal(t, tc.resp, msg)
assert.Equal(t, tc.wantNumReq, numReq)
@@ -188,7 +183,7 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
const prefixLen = 24
ip := net.IP{1, 2, 3, 0}
- subnet := netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ip)), prefixLen)
+ subnet := netip.PrefixFrom(netip.AddrFrom4([4]byte(ip)), prefixLen)
opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
Family: uint16(netutil.AddrFamilyIPv4),
@@ -198,31 +193,24 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
})
const ctry = agd.CountryAD
+
defaultCtrySubnet := netip.MustParsePrefix("1.2.0.0/16")
- ecsExtra := dnsservertest.NewECSExtra(net.IP{1, 2, 0, 0}, uint16(netutil.AddrFamilyIPv4), 20, 20)
+ ecsExtra := dnsservertest.NewECSExtra(
+ net.IP{1, 2, 0, 0},
+ uint16(netutil.AddrFamilyIPv4),
+ 20,
+ 20,
+ )
testCases := []struct {
req *dns.Msg
- resp *dns.Msg
+ respECS dns.RR
ecs *agd.ECS
- name string
ctrySubnet netip.Prefix
- wantNumReq int
- wantTTL uint32
+ name string
}{{
- req: aReq,
- resp: dnsservertest.NewResp(
- dns.RcodeSuccess,
- aReq,
- dnsservertest.RRSection{
- RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})},
- Sec: dnsservertest.SectionAnswer,
- },
- dnsservertest.RRSection{
- RRs: []dns.RR{ecsExtra},
- Sec: dnsservertest.SectionExtra,
- },
- ),
+ req: aReq,
+ respECS: ecsExtra,
ecs: &agd.ECS{
Location: &agd.Location{
Country: ctry,
@@ -230,24 +218,11 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
Subnet: subnet,
Scope: 0,
},
+ ctrySubnet: defaultCtrySubnet,
name: "with_country",
- ctrySubnet: defaultCtrySubnet,
- wantNumReq: 1,
- wantTTL: defaultTTL,
}, {
- req: aReq,
- resp: dnsservertest.NewResp(
- dns.RcodeSuccess,
- aReq,
- dnsservertest.RRSection{
- RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})},
- Sec: dnsservertest.SectionAnswer,
- },
- dnsservertest.RRSection{
- RRs: []dns.RR{ecsExtra},
- Sec: dnsservertest.SectionExtra,
- },
- ),
+ req: aReq,
+ respECS: ecsExtra,
ecs: &agd.ECS{
Location: &agd.Location{
Country: ctry,
@@ -255,112 +230,330 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
Subnet: subnet,
Scope: 0,
},
- name: "no_country",
ctrySubnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
- wantNumReq: 1,
- wantTTL: defaultTTL,
+ name: "no_country",
}, {
- req: aReqNoECS,
- resp: dnsservertest.NewResp(
- dns.RcodeSuccess,
- aReq,
- dnsservertest.RRSection{
- RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})},
- Sec: dnsservertest.SectionAnswer,
- },
- dnsservertest.RRSection{
- RRs: []dns.RR{ecsExtra},
- Sec: dnsservertest.SectionExtra,
- },
- ),
+ req: aReqNoECS,
+ respECS: ecsExtra,
ecs: nil,
- name: "edns_no_ecs",
ctrySubnet: defaultCtrySubnet,
- wantNumReq: 1,
- wantTTL: defaultTTL,
+ name: "edns_no_ecs",
+ }, {
+ req: aReq,
+ respECS: ecsExtra,
+ ecs: nil,
+ ctrySubnet: defaultCtrySubnet,
+ name: "country_from_ip",
}, {
req: aReq,
- resp: dnsservertest.NewResp(
- dns.RcodeSuccess,
- aReq,
- dnsservertest.RRSection{
- RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})},
- Sec: dnsservertest.SectionAnswer,
- },
- dnsservertest.RRSection{
- RRs: []dns.RR{ecsExtra},
- Sec: dnsservertest.SectionExtra,
- },
+ respECS: dnsservertest.NewECSExtra(
+ netutil.IPv4Zero(),
+ uint16(netutil.AddrFamilyIPv4),
+ 0,
+ 0,
),
- ecs: nil,
- name: "country_from_ip",
+ ecs: &agd.ECS{
+ Location: &agd.Location{
+ Country: agd.CountryNone,
+ },
+ Subnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
+ Scope: 0,
+ },
ctrySubnet: defaultCtrySubnet,
- wantNumReq: 1,
- wantTTL: defaultTTL,
+ name: "zero_ecs",
}}
const N = 5
+
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
+ resp := dnsservertest.NewResp(
+ dns.RcodeSuccess,
+ aReq,
+ dnsservertest.RRSection{
+ RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})},
+ Sec: dnsservertest.SectionAnswer,
+ },
+ dnsservertest.RRSection{
+ RRs: []dns.RR{tc.respECS},
+ Sec: dnsservertest.SectionExtra,
+ },
+ )
+
numReq := 0
handler := dnsserver.HandlerFunc(
func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error {
numReq++
- return rw.WriteMsg(ctx, req, tc.resp)
+ return rw.WriteMsg(ctx, req, resp)
},
)
withCache := newWithCache(t, handler, ctry, tc.ctrySubnet)
- ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{
+ ri := &agd.RequestInfo{
Location: &agd.Location{
Country: ctry,
},
ECS: tc.ecs,
Host: tc.req.Question[0].Name,
RemoteIP: remoteIP,
- })
-
- var nrw *dnsserver.NonWriterResponseWriter
- var msg *dns.Msg
- var respOpt *dns.OPT
- for i := 0; i < N; i++ {
- addr := &net.UDPAddr{IP: remoteIP.AsSlice(), Port: 53}
- nrw = dnsserver.NewNonWriterResponseWriter(addr, addr)
- err := withCache.ServeDNS(ctx, nrw, tc.req)
- require.NoError(t, err)
-
- msg = nrw.Msg()
- respOpt = msg.IsEdns0()
- if tc.ecs == nil && respOpt != nil {
- require.Empty(t, respOpt.Option)
- }
}
+ var msg *dns.Msg
+ for i := 0; i < N; i++ {
+ msg = exchange(t, ri, withCache, tc.req)
+ }
require.NotNil(t, msg)
+ assert.Equal(t, 1, numReq)
+
+ require.NotEmpty(t, msg.Answer)
+ assert.Equal(t, defaultTTL, msg.Answer[0].Header().Ttl)
+
+ respOpt := msg.IsEdns0()
if tc.ecs == nil {
+ if respOpt != nil {
+ require.Empty(t, respOpt.Option)
+ }
+
return
}
- assert.Equal(t, tc.wantNumReq, numReq)
-
- if len(msg.Answer) > 0 {
- assert.Equal(t, tc.wantTTL, msg.Answer[0].Header().Ttl)
- }
-
- require.NotNil(t, respOpt)
require.Len(t, respOpt.Option, 1)
-
subnetOpt := testutil.RequireTypeAssert[*dns.EDNS0_SUBNET](t, respOpt.Option[0])
- assert.Equal(t, ip, subnetOpt.Address)
- assert.Equal(t, uint8(prefixLen), subnetOpt.SourceNetmask)
- assert.Equal(t, uint8(prefixLen), subnetOpt.SourceScope)
+ assert.Equal(t, net.IP(tc.ecs.Subnet.Addr().AsSlice()), subnetOpt.Address)
+ assert.Equal(t, uint8(tc.ecs.Subnet.Bits()), subnetOpt.SourceNetmask)
+ assert.Equal(t, uint8(tc.ecs.Subnet.Bits()), subnetOpt.SourceScope)
})
}
}
+func TestMiddleware_Wrap_ecsOrder(t *testing.T) {
+ // Helper values and functions
+
+ const respSendTimeout = 1 * time.Second
+
+ newResp := func(t *testing.T, req *dns.Msg, answer, extra dns.RR) (resp *dns.Msg) {
+ t.Helper()
+
+ return dnsservertest.NewResp(dns.RcodeSuccess, req, dnsservertest.RRSection{
+ RRs: []dns.RR{answer},
+ Sec: dnsservertest.SectionAnswer,
+ }, dnsservertest.RRSection{
+ RRs: []dns.RR{extra},
+ Sec: dnsservertest.SectionExtra,
+ })
+ }
+
+ reqNoECS := dnsservertest.NewReq(reqHostname, dns.TypeA, dns.ClassINET)
+ reqNoECS.SetEdns0(dnsmsg.DefaultEDNSUDPSize, false)
+
+ const prefixLen = 24
+
+ reqWithECS := reqNoECS.Copy()
+ opt := testutil.RequireTypeAssert[*dns.OPT](t, reqWithECS.Extra[len(reqWithECS.Extra)-1])
+ opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
+ Code: dns.EDNS0SUBNET,
+ Family: uint16(netutil.AddrFamilyIPv4),
+ SourceNetmask: prefixLen,
+ SourceScope: 0,
+ Address: netip.PrefixFrom(remoteIP, prefixLen).Masked().Addr().AsSlice(),
+ })
+
+ reqZeroECS := reqNoECS.Copy()
+ opt = testutil.RequireTypeAssert[*dns.OPT](t, reqZeroECS.Extra[len(reqZeroECS.Extra)-1])
+ opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
+ Code: dns.EDNS0SUBNET,
+ Family: uint16(netutil.AddrFamilyIPv4),
+ SourceNetmask: 0,
+ SourceScope: 0,
+ Address: netutil.IPv4Zero(),
+ })
+
+ const ctry = agd.CountryAD
+
+ ctrySubnet := netip.PrefixFrom(remoteIP, 16).Masked()
+ ctryECS := dnsservertest.NewECSExtra(
+ ctrySubnet.Addr().AsSlice(),
+ uint16(netutil.AddrFamilyIPv4),
+ prefixLen,
+ 16,
+ )
+ zeroECS := dnsservertest.NewECSExtra(netutil.IPv4Zero(), uint16(netutil.AddrFamilyIPv4), 0, 0)
+
+ pt := testutil.PanicT{}
+ respCh := make(chan *dns.Msg, 1)
+ handler := dnsserver.HandlerFunc(
+ func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error {
+ resp, ok := testutil.RequireReceive(pt, respCh, respSendTimeout)
+ require.True(pt, ok)
+
+ return rw.WriteMsg(ctx, req, resp)
+ },
+ )
+
+ answerA := dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})
+ answerB := dnsservertest.NewA(reqHostname, defaultTTL, net.IP{5, 6, 7, 8})
+
+ // Tests
+
+ // request is a single request in a sequence. answer and extra are
+ // prerequisites for configuring handler's response before resolving msg,
+ // those should be nil when the response is expected to come from cache.
+ type request = struct {
+ answer dns.RR
+ extra dns.RR
+ msg *dns.Msg
+ wantAns []dns.RR
+ }
+
+ testCases := []struct {
+ name string
+ sequence []request
+ }{{
+ name: "no_ecs_first",
+ sequence: []request{{
+ answer: answerA,
+ extra: ctryECS,
+ msg: reqNoECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqWithECS,
+ wantAns: []dns.RR{answerA},
+ }},
+ }, {
+ name: "ecs_first",
+ sequence: []request{{
+ answer: answerA,
+ extra: ctryECS,
+ msg: reqWithECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqNoECS,
+ wantAns: []dns.RR{answerA},
+ }},
+ }, {
+ name: "zero_after_no_ecs",
+ sequence: []request{{
+ answer: answerA,
+ extra: ctryECS,
+ msg: reqNoECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: answerB,
+ extra: zeroECS,
+ msg: reqZeroECS,
+ wantAns: []dns.RR{answerB},
+ }},
+ }, {
+ name: "different_caches",
+ sequence: []request{{
+ answer: answerA,
+ extra: ctryECS,
+ msg: reqWithECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: answerB,
+ extra: zeroECS,
+ msg: reqZeroECS,
+ wantAns: []dns.RR{answerB},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqWithECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqZeroECS,
+ wantAns: []dns.RR{answerB},
+ }},
+ }, {
+ name: "no_ecs_upstream",
+ sequence: []request{{
+ answer: answerA,
+ extra: zeroECS,
+ msg: reqZeroECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: answerB,
+ extra: zeroECS,
+ msg: reqNoECS,
+ wantAns: []dns.RR{answerB},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqZeroECS,
+ wantAns: []dns.RR{answerA},
+ }, {
+ answer: nil,
+ extra: nil,
+ msg: reqNoECS,
+ wantAns: []dns.RR{answerB},
+ }},
+ }}
+
+ for _, tc := range testCases {
+ withCache := newWithCache(t, handler, ctry, ctrySubnet)
+
+ t.Run(tc.name, func(t *testing.T) {
+ for i, req := range tc.sequence {
+ if req.answer != nil && req.extra != nil {
+ resp := newResp(t, req.msg, req.answer, req.extra)
+ testutil.RequireSend(t, respCh, resp, respSendTimeout)
+ }
+
+ subnet, _, err := dnsmsg.ECSFromMsg(req.msg)
+ require.NoError(t, err)
+
+ ri := &agd.RequestInfo{
+ Location: &agd.Location{Country: ctry},
+ ECS: nil,
+ Host: req.msg.Question[0].Name,
+ RemoteIP: remoteIP,
+ }
+ if subnet != (netip.Prefix{}) {
+ ri.ECS = &agd.ECS{Subnet: subnet, Scope: 0}
+ }
+
+ // Make sure each step succeeded.
+ require.True(t, t.Run(fmt.Sprintf("step_%d", i), func(t *testing.T) {
+ got := exchange(t, ri, withCache, req.msg)
+ assert.Equal(t, req.wantAns, got.Answer)
+ }))
+ }
+ })
+ }
+}
+
+// exchange resolves req with h using context with ri.
+func exchange(
+ t testing.TB,
+ ri *agd.RequestInfo,
+ h dnsserver.Handler,
+ req *dns.Msg,
+) (resp *dns.Msg) {
+ t.Helper()
+
+ // TODO(a.garipov): Propose netip.Addr.WithPort.
+ addr := &net.UDPAddr{IP: remoteIP.AsSlice(), Port: 53}
+ nrw := dnsserver.NewNonWriterResponseWriter(addr, addr)
+
+ ctx := agd.ContextWithRequestInfo(context.Background(), ri)
+ err := h.ServeDNS(ctx, nrw, req)
+ require.NoError(t, err)
+
+ msg := nrw.Msg()
+ require.NotNil(t, msg)
+
+ return msg
+}
+
// newWithCache is a helper constructor of a handler for tests.
func newWithCache(
t testing.TB,
@@ -370,6 +563,8 @@ func newWithCache(
) (wrapped dnsserver.Handler) {
t.Helper()
+ pt := testutil.PanicT{}
+
// TODO(a.garipov): Actually test ASNs once we have the data.
geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func(
@@ -377,9 +572,7 @@ func newWithCache(
_ agd.ASN,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {
- t.Helper()
-
- assert.Equal(t, wantCtry, ctry)
+ require.Equal(pt, wantCtry, ctry)
return geoIPNet, nil
},
diff --git a/internal/errcoll/sentry_test.go b/internal/errcoll/sentry_test.go
index 675b162..5ad81f7 100644
--- a/internal/errcoll/sentry_test.go
+++ b/internal/errcoll/sentry_test.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/getsentry/sentry-go"
@@ -74,10 +74,8 @@ func TestSentryErrorCollector(t *testing.T) {
Device: &agd.Device{ID: devID},
Profile: &agd.Profile{ID: profID},
FilteringGroup: &agd.FilteringGroup{ID: fltGrpID},
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
- ID: reqID,
+ Messages: agdtest.NewConstructor(),
+ ID: reqID,
})
origErr := errors.Error("test error")
diff --git a/internal/filter/compfilter.go b/internal/filter/compfilter.go
index cbfa521..b579a2a 100644
--- a/internal/filter/compfilter.go
+++ b/internal/filter/compfilter.go
@@ -298,16 +298,17 @@ func (f *compFilter) Close() (err error) {
return nil
}
- var errs []error
+ errs := make([]error, len(f.ruleLists))
for i, rl := range f.ruleLists {
err = rl.Close()
if err != nil {
- errs = append(errs, fmt.Errorf("rule list at index %d: %w", i, err))
+ errs[i] = fmt.Errorf("rule list at index %d: %w", i, err)
}
}
- if len(errs) > 0 {
- return errors.List("closing filters", errs...)
+ err = errors.Join(errs...)
+ if err != nil {
+ return fmt.Errorf("closing filters: %w", err)
}
return nil
diff --git a/internal/filter/compfilter_internal_test.go b/internal/filter/compfilter_internal_test.go
index d9a26b4..d0ab69c 100644
--- a/internal/filter/compfilter_internal_test.go
+++ b/internal/filter/compfilter_internal_test.go
@@ -5,7 +5,6 @@ import (
"net"
"strings"
"testing"
- "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
@@ -56,9 +55,7 @@ func TestCompFilter_FilterRequest_badrequest(t *testing.T) {
}}
ri := &agd.RequestInfo{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
+ Messages: newConstructor(),
Host: testReqHost,
RemoteIP: testRemoteIP,
QType: dns.TypeA,
@@ -135,9 +132,7 @@ func TestCompFilter_FilterRequest_hostsRules(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ri := &agd.RequestInfo{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
+ Messages: newConstructor(),
Host: tc.reqHost,
RemoteIP: testRemoteIP,
QType: tc.reqType,
@@ -274,9 +269,7 @@ func TestCompFilter_FilterRequest_dnsrewrite(t *testing.T) {
ctx := context.Background()
ri := &agd.RequestInfo{
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
+ Messages: newConstructor(),
Host: testReqHost,
RemoteIP: testRemoteIP,
QType: dns.TypeA,
diff --git a/internal/filter/dnsrewrite_internal_test.go b/internal/filter/dnsrewrite_internal_test.go
index 4ab7383..83aa3d3 100644
--- a/internal/filter/dnsrewrite_internal_test.go
+++ b/internal/filter/dnsrewrite_internal_test.go
@@ -3,12 +3,10 @@ package filter
import (
"net"
"testing"
- "time"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
- "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
@@ -76,9 +74,7 @@ func Test_filterDNSRewrite(t *testing.T) {
aRecordRule, _ := rules.NewNetworkRule("|a-record^$dnsrewrite=127.0.0.1", 1)
refusedRule, _ := rules.NewNetworkRule("|refused^$dnsrewrite=REFUSED", 1)
- messages := &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- }
+ messages := newConstructor()
req := dnsservertest.NewReq(testReqFQDN, dns.TypeA, dns.ClassINET)
diff --git a/internal/filter/filter.go b/internal/filter/filter.go
index cdc402a..e176111 100644
--- a/internal/filter/filter.go
+++ b/internal/filter/filter.go
@@ -16,7 +16,9 @@ import (
// Common Constants, Functions, and Types
// maxFilterSize is the maximum size of downloaded filters.
-const maxFilterSize = 196 * int64(datasize.MB)
+//
+// TODO(ameshkov): Consider making configurable.
+const maxFilterSize = 256 * int64(datasize.MB)
// defaultFilterRefreshTimeout is the default timeout to use when fetching
// filter lists data.
diff --git a/internal/filter/filter_internal_test.go b/internal/filter/filter_internal_test.go
index 99ee699..31e659e 100644
--- a/internal/filter/filter_internal_test.go
+++ b/internal/filter/filter_internal_test.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
+ "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
)
// Common test constants.
@@ -23,3 +24,11 @@ const testReqFQDN = testReqHost + "."
// testRemoteIP is the client IP for tests
var testRemoteIP = netip.MustParseAddr("1.2.3.4")
+
+// newConstructor returns a standard dnsmsg.Constructor for tests.
+//
+// TODO(a.garipov): Use [agdtest.NewConstructor] once the package is split and
+// import cycles are resolved.
+func newConstructor() (c *dnsmsg.Constructor) {
+ return dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, 10*time.Second)
+}
diff --git a/internal/filter/filter_test.go b/internal/filter/filter_test.go
index 39383a0..8c360c2 100644
--- a/internal/filter/filter_test.go
+++ b/internal/filter/filter_test.go
@@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
+ "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/golibs/testutil"
@@ -216,12 +217,10 @@ func newReqInfo(
Device: d,
Profile: p,
FilteringGroup: g,
- Messages: &dnsmsg.Constructor{
- FilteredResponseTTL: 10 * time.Second,
- },
- Host: host,
- RemoteIP: ip,
- QType: qt,
+ Messages: agdtest.NewConstructor(),
+ Host: host,
+ RemoteIP: ip,
+ QType: qt,
}
return ri
diff --git a/internal/filter/hashstorage/hashstorage.go b/internal/filter/hashstorage/hashstorage.go
index 8234279..29afda9 100644
--- a/internal/filter/hashstorage/hashstorage.go
+++ b/internal/filter/hashstorage/hashstorage.go
@@ -71,8 +71,8 @@ func New(hostnames string) (s *Storage, err error) {
// resulting slice shares storage for all underlying strings.
//
// TODO(a.garipov): This currently doesn't take duplicates into account.
-func (s *Storage) Hashes(hps []Prefix) (hashes []string) {
- if len(hps) == 0 {
+func (s *Storage) Hashes(prefs []Prefix) (hashes []string) {
+ if len(prefs) == 0 {
return nil
}
@@ -81,8 +81,8 @@ func (s *Storage) Hashes(hps []Prefix) (hashes []string) {
// First, calculate the number of hashes to allocate the buffer.
l := 0
- for _, hp := range hps {
- hashSufs := s.hashSuffixes[hp]
+ for _, pref := range prefs {
+ hashSufs := s.hashSuffixes[pref]
l += len(hashSufs)
}
@@ -100,15 +100,12 @@ func (s *Storage) Hashes(hps []Prefix) (hashes []string) {
// using hex.NewEncoder, because that seems to incur a significant
// performance hit.
var buf [hashEncLen]byte
- for _, hp := range hps {
- hashSufs := s.hashSuffixes[hp]
+ for _, pref := range prefs {
+ hashSufs := s.hashSuffixes[pref]
for _, suf := range hashSufs {
- // Slicing is safe here, since the contents of hp and suf are being
- // encoded.
-
- // nolint:looppointer
- hex.Encode(buf[:], hp[:])
- // nolint:looppointer
+ // nolint:looppointer // Slicing is safe; used for encoding.
+ hex.Encode(buf[:], pref[:])
+ // nolint:looppointer // Slicing is safe; used for encoding.
hex.Encode(buf[PrefixEncLen:], suf[:])
_, _ = b.Write(buf[:])
}
@@ -126,19 +123,17 @@ func (s *Storage) Hashes(hps []Prefix) (hashes []string) {
// Matches returns true if the host matches one of the hashes.
func (s *Storage) Matches(host string) (ok bool) {
sum := sha256.Sum256([]byte(host))
- hp := *(*Prefix)(sum[:PrefixLen])
+ pref := Prefix(sum[:PrefixLen])
var buf [hashLen]byte
- hashSufs, ok := s.loadHashSuffixes(hp)
+ hashSufs, ok := s.loadHashSuffixes(pref)
if !ok {
return false
}
- copy(buf[:], hp[:])
+ copy(buf[:], pref[:])
for _, suf := range hashSufs {
- // Slicing is safe here, because we make a copy.
-
- // nolint:looppointer
+ // nolint:looppointer // Slicing is safe; used for copying.
copy(buf[PrefixLen:], suf[:])
if buf == sum {
return true
@@ -161,8 +156,8 @@ func (s *Storage) Reset(hostnames string) (n int, err error) {
// This is optimized, see https://github.com/golang/go/issues/20138.
//
// TODO(a.garipov): Use clear once golang/go#56351 is implemented.
- for hp := range s.hashSuffixes {
- delete(s.hashSuffixes, hp)
+ for pref := range s.hashSuffixes {
+ delete(s.hashSuffixes, pref)
}
sc := bufio.NewScanner(strings.NewReader(hostnames))
@@ -173,12 +168,9 @@ func (s *Storage) Reset(hostnames string) (n int, err error) {
}
sum := sha256.Sum256([]byte(host))
- hp := *(*Prefix)(sum[:PrefixLen])
-
- // TODO(a.garipov): Here and everywhere, convert to array directly when
- // proposal golang/go#46505 is implemented in Go 1.20.
- suf := *(*suffix)(sum[PrefixLen:])
- s.hashSuffixes[hp] = append(s.hashSuffixes[hp], suf)
+ pref := Prefix(sum[:PrefixLen])
+ suf := suffix(sum[PrefixLen:])
+ s.hashSuffixes[pref] = append(s.hashSuffixes[pref], suf)
n++
}
@@ -193,11 +185,11 @@ func (s *Storage) Reset(hostnames string) (n int, err error) {
// loadHashSuffixes returns hash suffixes for the given prefix. It is safe for
// concurrent use. sufs must not be modified.
-func (s *Storage) loadHashSuffixes(hp Prefix) (sufs []suffix, ok bool) {
+func (s *Storage) loadHashSuffixes(pref Prefix) (sufs []suffix, ok bool) {
s.mu.RLock()
defer s.mu.RUnlock()
- sufs, ok = s.hashSuffixes[hp]
+ sufs, ok = s.hashSuffixes[pref]
return sufs, ok
}
diff --git a/internal/filter/serviceblocker.go b/internal/filter/serviceblocker.go
index 04cff42..ccb1f60 100644
--- a/internal/filter/serviceblocker.go
+++ b/internal/filter/serviceblocker.go
@@ -171,14 +171,13 @@ func (r *svcIndexResp) toInternal(
return nil, nil
}
- var errs []error
-
services = make(serviceRuleLists, l)
- for _, svc := range r.BlockedServices {
+ errs := make([]error, len(r.BlockedServices))
+ for i, svc := range r.BlockedServices {
var id agd.BlockedServiceID
id, err = agd.NewBlockedServiceID(svc.ID)
if err != nil {
- errs = append(errs, fmt.Errorf("validating id: %w", err))
+ errs[i] = fmt.Errorf("service at index %d: validating id: %w", i, err)
continue
}
@@ -200,7 +199,7 @@ func (r *svcIndexResp) toInternal(
useCache,
)
if err != nil {
- errs = append(errs, fmt.Errorf("compiling %s: %w", svc.ID, err))
+ errs[i] = fmt.Errorf("compiling %s: %w", svc.ID, err)
continue
}
@@ -208,8 +207,9 @@ func (r *svcIndexResp) toInternal(
services[id] = rl
}
- if len(errs) > 0 {
- return nil, errors.List("converting blocked services", errs...)
+ err = errors.Join(errs...)
+ if err != nil {
+ return nil, fmt.Errorf("converting blocked services: %w", err)
}
return services, nil
diff --git a/internal/geoip/asntops.go b/internal/geoip/asntops.go
index 99fc1e2..c5d9fcf 100644
--- a/internal/geoip/asntops.go
+++ b/internal/geoip/asntops.go
@@ -29,7 +29,6 @@ var allTopASNs = map[agd.ASN]struct{}{
2519: {},
2527: {},
2586: {},
- 2607: {},
2740: {},
2856: {},
2860: {},
@@ -37,7 +36,6 @@ var allTopASNs = map[agd.ASN]struct{}{
3212: {},
3215: {},
3216: {},
- 3221: {},
3238: {},
3243: {},
3249: {},
@@ -59,6 +57,7 @@ var allTopASNs = map[agd.ASN]struct{}{
4230: {},
4609: {},
4638: {},
+ 4648: {},
4657: {},
4713: {},
4760: {},
@@ -70,7 +69,6 @@ var allTopASNs = map[agd.ASN]struct{}{
4775: {},
4788: {},
4804: {},
- 4808: {},
4812: {},
4817: {},
4818: {},
@@ -129,7 +127,6 @@ var allTopASNs = map[agd.ASN]struct{}{
7992: {},
8014: {},
8048: {},
- 8075: {},
8151: {},
8167: {},
8193: {},
@@ -146,21 +143,26 @@ var allTopASNs = map[agd.ASN]struct{}{
8473: {},
8544: {},
8551: {},
+ 8560: {},
8585: {},
+ 8661: {},
8680: {},
8681: {},
8697: {},
8708: {},
+ 8728: {},
8764: {},
8781: {},
8818: {},
8866: {},
+ 8881: {},
8926: {},
8953: {},
8978: {},
9009: {},
9038: {},
9051: {},
+ 9105: {},
9121: {},
9146: {},
9158: {},
@@ -199,13 +201,14 @@ var allTopASNs = map[agd.ASN]struct{}{
10226: {},
10269: {},
10396: {},
- 10474: {},
10620: {},
+ 10796: {},
11081: {},
11139: {},
11315: {},
11427: {},
11556: {},
+ 11562: {},
11594: {},
11664: {},
11816: {},
@@ -266,6 +269,7 @@ var allTopASNs = map[agd.ASN]struct{}{
15397: {},
15399: {},
15433: {},
+ 15480: {},
15502: {},
15557: {},
15704: {},
@@ -279,7 +283,6 @@ var allTopASNs = map[agd.ASN]struct{}{
15958: {},
15962: {},
15964: {},
- 15994: {},
16010: {},
16019: {},
16028: {},
@@ -287,7 +290,6 @@ var allTopASNs = map[agd.ASN]struct{}{
16116: {},
16135: {},
16232: {},
- 16247: {},
16276: {},
16345: {},
16437: {},
@@ -321,7 +323,6 @@ var allTopASNs = map[agd.ASN]struct{}{
18881: {},
19114: {},
19429: {},
- 19626: {},
19711: {},
19863: {},
20001: {},
@@ -349,7 +350,6 @@ var allTopASNs = map[agd.ASN]struct{}{
21450: {},
21497: {},
21575: {},
- 21744: {},
21826: {},
21928: {},
21996: {},
@@ -357,6 +357,7 @@ var allTopASNs = map[agd.ASN]struct{}{
22069: {},
22085: {},
22351: {},
+ 22581: {},
22724: {},
22773: {},
22927: {},
@@ -399,7 +400,6 @@ var allTopASNs = map[agd.ASN]struct{}{
25472: {},
25513: {},
25543: {},
- 26130: {},
26599: {},
26611: {},
26615: {},
@@ -419,7 +419,6 @@ var allTopASNs = map[agd.ASN]struct{}{
27781: {},
27800: {},
27831: {},
- 27839: {},
27882: {},
27884: {},
27895: {},
@@ -474,10 +473,12 @@ var allTopASNs = map[agd.ASN]struct{}{
30999: {},
31012: {},
31027: {},
+ 31037: {},
31042: {},
- 31126: {},
+ 31122: {},
31133: {},
31163: {},
+ 31204: {},
31205: {},
31213: {},
31224: {},
@@ -487,6 +488,8 @@ var allTopASNs = map[agd.ASN]struct{}{
31615: {},
31721: {},
32020: {},
+ 32189: {},
+ 33363: {},
33392: {},
33567: {},
33576: {},
@@ -495,9 +498,9 @@ var allTopASNs = map[agd.ASN]struct{}{
33765: {},
33771: {},
33779: {},
- 33788: {},
33874: {},
33915: {},
+ 33983: {},
34058: {},
34170: {},
34557: {},
@@ -513,6 +516,7 @@ var allTopASNs = map[agd.ASN]struct{}{
35725: {},
35805: {},
35819: {},
+ 35892: {},
35900: {},
36290: {},
36549: {},
@@ -530,7 +534,6 @@ var allTopASNs = map[agd.ASN]struct{}{
36926: {},
36935: {},
36939: {},
- 36945: {},
36947: {},
36958: {},
36962: {},
@@ -538,7 +541,6 @@ var allTopASNs = map[agd.ASN]struct{}{
36974: {},
36988: {},
36992: {},
- 36994: {},
36996: {},
36998: {},
36999: {},
@@ -577,9 +579,11 @@ var allTopASNs = map[agd.ASN]struct{}{
37337: {},
37342: {},
37343: {},
+ 37349: {},
37371: {},
37376: {},
37385: {},
+ 37406: {},
37410: {},
37424: {},
37440: {},
@@ -606,13 +610,13 @@ var allTopASNs = map[agd.ASN]struct{}{
37594: {},
37611: {},
37612: {},
+ 37614: {},
37616: {},
37645: {},
37649: {},
37671: {},
37693: {},
37705: {},
- 38008: {},
38009: {},
38077: {},
38195: {},
@@ -625,36 +629,33 @@ var allTopASNs = map[agd.ASN]struct{}{
38742: {},
38800: {},
38819: {},
- 38875: {},
38901: {},
38999: {},
39010: {},
39232: {},
39603: {},
- 39611: {},
39642: {},
39891: {},
- 40065: {},
40945: {},
41164: {},
41202: {},
41329: {},
41330: {},
41557: {},
+ 41564: {},
41653: {},
41697: {},
41738: {},
41750: {},
41897: {},
41937: {},
- 41965: {},
42003: {},
42082: {},
42298: {},
42313: {},
42437: {},
+ 42532: {},
42560: {},
- 42610: {},
42772: {},
42779: {},
42863: {},
@@ -676,9 +677,8 @@ var allTopASNs = map[agd.ASN]struct{}{
44244: {},
44558: {},
44869: {},
- 44925: {},
- 45102: {},
45143: {},
+ 45168: {},
45177: {},
45178: {},
45245: {},
@@ -686,7 +686,6 @@ var allTopASNs = map[agd.ASN]struct{}{
45355: {},
45356: {},
45498: {},
- 45558: {},
45609: {},
45650: {},
45669: {},
@@ -703,6 +702,7 @@ var allTopASNs = map[agd.ASN]struct{}{
47331: {},
47377: {},
47394: {},
+ 47588: {},
47589: {},
47883: {},
47956: {},
@@ -717,7 +717,6 @@ var allTopASNs = map[agd.ASN]struct{}{
48887: {},
48953: {},
49273: {},
- 49734: {},
49800: {},
49902: {},
49981: {},
@@ -726,8 +725,8 @@ var allTopASNs = map[agd.ASN]struct{}{
50251: {},
50266: {},
50616: {},
- 50710: {},
50973: {},
+ 50979: {},
51207: {},
51375: {},
51407: {},
@@ -745,11 +744,10 @@ var allTopASNs = map[agd.ASN]struct{}{
52341: {},
52362: {},
52398: {},
+ 52433: {},
52468: {},
- 53667: {},
55330: {},
55430: {},
- 55714: {},
55805: {},
55836: {},
55850: {},
@@ -759,27 +757,27 @@ var allTopASNs = map[agd.ASN]struct{}{
56089: {},
56167: {},
56300: {},
+ 56653: {},
+ 56665: {},
56696: {},
56902: {},
- 57218: {},
57269: {},
57293: {},
57388: {},
57513: {},
57704: {},
+ 58065: {},
58224: {},
58460: {},
58731: {},
- 58952: {},
59257: {},
59588: {},
- 59625: {},
59989: {},
60068: {},
60258: {},
- 60781: {},
61143: {},
61461: {},
+ 62563: {},
63949: {},
64466: {},
131178: {},
@@ -795,46 +793,41 @@ var allTopASNs = map[agd.ASN]struct{}{
132471: {},
132618: {},
132831: {},
- 133384: {},
133385: {},
133481: {},
133579: {},
133612: {},
+ 133897: {},
134783: {},
- 135298: {},
- 135300: {},
- 135376: {},
+ 135409: {},
136255: {},
- 136969: {},
137412: {},
138179: {},
139759: {},
139831: {},
139898: {},
+ 139922: {},
196838: {},
197207: {},
- 197706: {},
197830: {},
198279: {},
198605: {},
199140: {},
199155: {},
- 199276: {},
199731: {},
200134: {},
- 200313: {},
- 200651: {},
- 200740: {},
- 201019: {},
201167: {},
201767: {},
201986: {},
202087: {},
202254: {},
+ 202422: {},
203214: {},
203953: {},
203995: {},
204170: {},
+ 204317: {},
+ 204342: {},
204649: {},
205368: {},
205714: {},
@@ -847,8 +840,10 @@ var allTopASNs = map[agd.ASN]struct{}{
207810: {},
210315: {},
210542: {},
+ 211144: {},
213155: {},
213373: {},
+ 262145: {},
262186: {},
262197: {},
262202: {},
@@ -860,7 +855,6 @@ var allTopASNs = map[agd.ASN]struct{}{
263824: {},
264628: {},
264645: {},
- 264663: {},
264668: {},
264731: {},
269729: {},
@@ -874,7 +868,6 @@ var allTopASNs = map[agd.ASN]struct{}{
327765: {},
327769: {},
327776: {},
- 327786: {},
327799: {},
327802: {},
327885: {},
@@ -888,20 +881,21 @@ var allTopASNs = map[agd.ASN]struct{}{
328169: {},
328191: {},
328200: {},
- 328207: {},
328228: {},
328250: {},
328286: {},
328297: {},
328309: {},
+ 328411: {},
328453: {},
328469: {},
- 328471: {},
+ 328488: {},
328586: {},
328605: {},
- 328727: {},
+ 328708: {},
328755: {},
328943: {},
+ 329020: {},
394311: {},
395561: {},
396304: {},
@@ -914,10 +908,10 @@ var allTopASNs = map[agd.ASN]struct{}{
var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryAD: 6752,
agd.CountryAE: 5384,
- agd.CountryAF: 45178,
+ agd.CountryAF: 55330,
agd.CountryAG: 11594,
agd.CountryAI: 11139,
- agd.CountryAL: 197706,
+ agd.CountryAL: 21183,
agd.CountryAM: 12297,
agd.CountryAO: 37119,
agd.CountryAR: 7303,
@@ -929,7 +923,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryAZ: 28787,
agd.CountryBA: 9146,
agd.CountryBB: 14813,
- agd.CountryBD: 24432,
+ agd.CountryBD: 24389,
agd.CountryBE: 5432,
agd.CountryBF: 37577,
agd.CountryBG: 8866,
@@ -948,32 +942,33 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryBY: 25106,
agd.CountryBZ: 10269,
agd.CountryCA: 812,
+ agd.CountryCC: 198605,
agd.CountryCD: 37020,
- agd.CountryCF: 22351,
- agd.CountryCG: 36924,
+ agd.CountryCF: 37460,
+ agd.CountryCG: 37451,
agd.CountryCH: 3303,
agd.CountryCI: 29571,
agd.CountryCK: 10131,
agd.CountryCL: 27651,
- agd.CountryCM: 36912,
+ agd.CountryCM: 30992,
agd.CountryCN: 4134,
agd.CountryCO: 26611,
agd.CountryCR: 11830,
agd.CountryCU: 27725,
agd.CountryCV: 37517,
agd.CountryCW: 52233,
+ agd.CountryCX: 198605,
agd.CountryCY: 6866,
agd.CountryCZ: 5610,
- agd.CountryDE: 60068,
+ agd.CountryDE: 3320,
agd.CountryDJ: 30990,
agd.CountryDK: 3292,
agd.CountryDM: 40945,
agd.CountryDO: 6400,
agd.CountryDZ: 36947,
agd.CountryEC: 27947,
- agd.CountryEE: 2586,
+ agd.CountryEE: 3249,
agd.CountryEG: 8452,
- agd.CountryEH: 36947,
agd.CountryER: 24757,
agd.CountryES: 12479,
agd.CountryET: 24757,
@@ -983,8 +978,8 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryFM: 139759,
agd.CountryFO: 15389,
agd.CountryFR: 3215,
- agd.CountryGA: 36924,
- agd.CountryGB: 9009,
+ agd.CountryGA: 16058,
+ agd.CountryGB: 2856,
agd.CountryGD: 46650,
agd.CountryGE: 16010,
agd.CountryGF: 3215,
@@ -992,17 +987,17 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryGH: 30986,
agd.CountryGI: 8301,
agd.CountryGL: 8818,
- agd.CountryGM: 25250,
+ agd.CountryGM: 37309,
agd.CountryGN: 37461,
agd.CountryGP: 3215,
agd.CountryGQ: 37173,
agd.CountryGR: 6799,
agd.CountryGT: 14754,
agd.CountryGU: 3605,
- agd.CountryGW: 327769,
+ agd.CountryGW: 37559,
agd.CountryGY: 19863,
agd.CountryHK: 4760,
- agd.CountryHN: 14754,
+ agd.CountryHN: 52262,
agd.CountryHR: 5391,
agd.CountryHT: 27653,
agd.CountryHU: 5483,
@@ -1018,7 +1013,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryIT: 1267,
agd.CountryJE: 8680,
agd.CountryJM: 30689,
- agd.CountryJO: 8376,
+ agd.CountryJO: 48832,
agd.CountryJP: 2516,
agd.CountryKE: 33771,
agd.CountryKG: 50223,
@@ -1035,7 +1030,7 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryLC: 15344,
agd.CountryLI: 20634,
agd.CountryLK: 18001,
- agd.CountryLR: 37410,
+ agd.CountryLR: 37094,
agd.CountryLS: 33567,
agd.CountryLT: 8764,
agd.CountryLU: 9009,
@@ -1054,12 +1049,12 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryMN: 17882,
agd.CountryMO: 4609,
agd.CountryMP: 7131,
- agd.CountryMQ: 16028,
+ agd.CountryMQ: 3215,
agd.CountryMR: 29544,
agd.CountryMS: 396304,
agd.CountryMT: 12709,
agd.CountryMU: 23889,
- agd.CountryMV: 7642,
+ agd.CountryMV: 55944,
agd.CountryMW: 37440,
agd.CountryMX: 8151,
agd.CountryMY: 4788,
@@ -1067,21 +1062,22 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryNA: 36996,
agd.CountryNC: 18200,
agd.CountryNE: 37531,
- agd.CountryNF: 198605,
+ agd.CountryNF: 45168,
agd.CountryNG: 29465,
agd.CountryNI: 14754,
- agd.CountryNL: 60068,
+ agd.CountryNL: 1136,
agd.CountryNO: 2119,
agd.CountryNP: 17501,
agd.CountryNR: 45355,
+ agd.CountryNU: 198605,
agd.CountryNZ: 4771,
agd.CountryOM: 28885,
agd.CountryPA: 11556,
agd.CountryPE: 12252,
agd.CountryPF: 9471,
- agd.CountryPG: 58460,
+ agd.CountryPG: 139898,
agd.CountryPH: 9299,
- agd.CountryPK: 17557,
+ agd.CountryPK: 45669,
agd.CountryPL: 43447,
agd.CountryPM: 3695,
agd.CountryPR: 21928,
@@ -1097,11 +1093,12 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountryRW: 36924,
agd.CountrySA: 39891,
agd.CountrySB: 45891,
- agd.CountrySC: 36958,
+ agd.CountrySC: 131267,
agd.CountrySD: 15706,
agd.CountrySE: 1257,
agd.CountrySG: 4773,
agd.CountrySI: 3212,
+ agd.CountrySJ: 198605,
agd.CountrySK: 6855,
agd.CountrySL: 37164,
agd.CountrySM: 15433,
@@ -1115,21 +1112,22 @@ var countryTopASNs = map[agd.Country]agd.ASN{
agd.CountrySY: 29256,
agd.CountrySZ: 328169,
agd.CountryTC: 394311,
- agd.CountryTD: 327756,
- agd.CountryTG: 36924,
+ agd.CountryTD: 327802,
+ agd.CountryTG: 24691,
agd.CountryTH: 131445,
agd.CountryTJ: 43197,
+ agd.CountryTK: 4648,
agd.CountryTL: 58731,
agd.CountryTM: 51495,
agd.CountryTN: 37705,
- agd.CountryTO: 45355,
+ agd.CountryTO: 38201,
agd.CountryTR: 16135,
agd.CountryTT: 27800,
agd.CountryTW: 3462,
agd.CountryTZ: 36908,
agd.CountryUA: 15895,
agd.CountryUG: 37075,
- agd.CountryUS: 51765,
+ agd.CountryUS: 7922,
agd.CountryUY: 6057,
agd.CountryUZ: 8193,
agd.CountryVA: 8978,
diff --git a/internal/geoip/file.go b/internal/geoip/file.go
index 7207558..432e3ca 100644
--- a/internal/geoip/file.go
+++ b/internal/geoip/file.go
@@ -103,12 +103,12 @@ func ipToCacheKey(ip netip.Addr) (k any) {
if ip.Is4() {
a := ip.As4()
- return *(*[3]byte)(a[:])
+ return [3]byte(a[:])
}
a := ip.As16()
- return *(*[7]byte)(a[:])
+ return [7]byte(a[:])
}
// type check
diff --git a/internal/geoip/geoip_test.go b/internal/geoip/geoip_test.go
index cbaa87a..b6d2d50 100644
--- a/internal/geoip/geoip_test.go
+++ b/internal/geoip/geoip_test.go
@@ -43,7 +43,7 @@ var testIPWithCountry = netip.MustParseAddr("2001:218::")
// Subnets for CountrySubnet tests.
var (
- testIPv4CountrySubnet = netip.MustParsePrefix("50.114.0.0/24")
+ testIPv4CountrySubnet = netip.MustParsePrefix("76.128.0.0/24")
// TODO(a.garipov): Either find a better subnet and country that don't
// trigger the ASN optimizations or just remove this one completely.
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index c405d88..201dc77 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -1,19 +1,19 @@
module github.com/AdguardTeam/AdGuardDNS/internal/tools
-go 1.19
+go 1.20
require (
github.com/fzipp/gocyclo v0.6.0
github.com/golangci/misspell v0.4.0
- github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b
- github.com/kisielk/errcheck v1.6.2
+ github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28
+ github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1
- github.com/securego/gosec/v2 v2.14.0
- golang.org/x/tools v0.3.0
- golang.org/x/vuln v0.0.0-20221122171214-05fb7250142c
- honnef.co/go/tools v0.3.3
+ github.com/securego/gosec/v2 v2.15.0
+ golang.org/x/tools v0.7.0
+ golang.org/x/vuln v0.0.0-20230308034057-d4ed0a4fab9e
+ honnef.co/go/tools v0.4.2
mvdan.cc/gofumpt v0.4.0
- mvdan.cc/unparam v0.0.0-20221109083536-cac433cd1bcb
+ mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95
)
require (
@@ -24,10 +24,10 @@ require (
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-20221126150942-6ab00d035af9 // indirect
- golang.org/x/exp/typeparams v0.0.0-20221126150942-6ab00d035af9 // indirect
- golang.org/x/mod v0.7.0 // indirect
+ golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect
+ golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect
+ golang.org/x/mod v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect
- golang.org/x/sys v0.2.0 // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
+ golang.org/x/sys v0.6.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 4cafc03..5653710 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -7,6 +7,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0=
github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
@@ -17,10 +18,10 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
-github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b h1:TYNAU9lu7ggdAereRq0dzCIDzHu9mNyGLj/hd5PXq8I=
-github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
-github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c=
-github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
+github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U=
+github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
+github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
+github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fxdJg=
@@ -29,14 +30,14 @@ 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.3.1 h1:8SbseP7qM32WcvE6VaN6vfXxv698izmsJ1UQX9ve7T8=
-github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI=
+github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
+github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/securego/gosec/v2 v2.14.0 h1:U1hfs0oBackChXA72plCYVA4cOlQ4gO+209dHiSNZbI=
-github.com/securego/gosec/v2 v2.14.0/go.mod h1:Ff03zEi5NwSOfXj9nFpBfhbWTtROCkg9N+9goggrYn4=
+github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw=
+github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8=
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/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -53,22 +54,22 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
-golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
-golang.org/x/exp/typeparams v0.0.0-20221126150942-6ab00d035af9 h1:HLMZVag1HHlHs3TLOQ9+wAeKt5tp1ri1khLdhYjBcFw=
-golang.org/x/exp/typeparams v0.0.0-20221126150942-6ab00d035af9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+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-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ=
+golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
-golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
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=
@@ -83,36 +84,34 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
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.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
-golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
-golang.org/x/vuln v0.0.0-20221122171214-05fb7250142c h1:Q/cUnXhEEKm8vd19JItKXGfjQl2Tts0p7mR0uXW7LJE=
-golang.org/x/vuln v0.0.0-20221122171214-05fb7250142c/go.mod h1:8nFLBv8KFyZ2VuczUYssYKh+fcBR3BuXDG/HIWcxlwM=
+golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/vuln v0.0.0-20230308034057-d4ed0a4fab9e h1:zLJSre6/6VuA0wMLQe9ShzEncb0k8E/z9wVhmlnRdNs=
+golang.org/x/vuln v0.0.0-20230308034057-d4ed0a4fab9e/go.mod h1:ydpjOTRSBwOBFJRP/w5NF2HSPnFg1JxobEZQGOirxgo=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.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=
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.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
-honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
+honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc=
+honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
-mvdan.cc/unparam v0.0.0-20221109083536-cac433cd1bcb h1:9LbDISp+oqkv/ByYp16oylssClEsX8yG7J/azWt6IOU=
-mvdan.cc/unparam v0.0.0-20221109083536-cac433cd1bcb/go.mod h1:7fKhD/gH+APJ9Y27S2PYO7+oVWtb3XPrw9W5ayxVq2A=
+mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 h1:n/xhncJPSt0YzfOhnyn41XxUdrWQNgmLBG72FE27Fqw=
+mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95/go.mod h1:2vU506e8nGWodqcci641NLi4im2twWSq4Lod756epHQ=
diff --git a/internal/websvc/linkip.go b/internal/websvc/linkip.go
index 90f1668..66e394b 100644
--- a/internal/websvc/linkip.go
+++ b/internal/websvc/linkip.go
@@ -37,6 +37,8 @@ func linkedIPHandler(
// Use a custom Director to make sure we send the correct Host header and
// don't send anything besides the path.
+ //
+ // TODO(a.garipov): Use the Go 1.20 [httputil.ReverseProxy.Rewrite] API?
director := func(r *http.Request) {
r.URL.Scheme = apiURL.Scheme
r.Host, r.URL.Host = apiURL.Host, apiURL.Host
diff --git a/main.go b/main.go
index 6d53945..e8a42c4 100644
--- a/main.go
+++ b/main.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2022 AdGuard Software Ltd.
+// Copyright (C) 2022-2023 AdGuard Software Ltd.
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh
index efc84a8..33baede 100644
--- a/scripts/make/go-lint.sh
+++ b/scripts/make/go-lint.sh
@@ -35,7 +35,7 @@ set -f -u
go_version="$( "${GO:-go}" version )"
readonly go_version
-go_min_version='go1.19.4'
+go_min_version='go1.20.2'
go_version_msg="
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
if you have the version installed, please set the GO environment variable.
diff --git a/scripts/make/go-test.sh b/scripts/make/go-test.sh
index 08fe6bb..5b87aa4 100644
--- a/scripts/make/go-test.sh
+++ b/scripts/make/go-test.sh
@@ -43,7 +43,7 @@ go="${GO:-go}"
count_flags='--count=1'
shuffle_flags='--shuffle=on'
# TODO(ameshkov): Find out, why QUIC tests are so slow, and return to 30s.
-timeout_flags="${TIMEOUT_FLAGS:---timeout=60s}"
+timeout_flags="${TIMEOUT_FLAGS:---timeout=90s}"
readonly go count_flags shuffle_flags timeout_flags
# TODO(a.garipov): Remove the dnsserver stuff once it is separated.
diff --git a/scripts/test/bindtodevice.docker b/scripts/test/bindtodevice.docker
new file mode 100644
index 0000000..c2ec557
--- /dev/null
+++ b/scripts/test/bindtodevice.docker
@@ -0,0 +1,19 @@
+# Use the golang:alpine as the base image as it already has most of the
+# necessary ip(8) tooling installed.
+FROM golang:alpine
+
+RUN apk add bind-tools bmake gcc git libc-dev &&\
+ ln /usr/bin/bmake /usr/bin/make &&\
+ mkdir /test/ &&\
+ git config --global --add safe.directory /test
+
+WORKDIR /test/
+
+ENV ADGUARD_DNS_TEST_NET_INTERFACE='eth0'
+
+# The ip route operations must be here and not in the RUN instruction above,
+# because they require --cap-add='NET_ADMIN', which is unavailable during build
+# time. See ./bindtodevice.sh.
+ENTRYPOINT ip route del '172.17.0.0/16' dev 'eth0' &&\
+ ip route add local '172.17.0.0/16' dev 'eth0' &&\
+ exec sh
diff --git a/scripts/test/bindtodevice.sh b/scripts/test/bindtodevice.sh
new file mode 100644
index 0000000..65d6551
--- /dev/null
+++ b/scripts/test/bindtodevice.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -e -f -u -x
+
+use_sudo="${USE_SUDO:-0}"
+readonly use_sudo
+
+maybe_sudo() {
+ if [ "$use_sudo" -eq 0 ]
+ then
+ "$@"
+ else
+ sudo "$@"
+ fi
+}
+
+maybe_sudo docker build\
+ -t agdns_bindtodevice_test\
+ -\
+ < ./scripts/test/bindtodevice.docker
+
+maybe_sudo docker run\
+ --cap-add='NET_ADMIN'\
+ --name='agdns_bindtodevice_test'\
+ --rm\
+ -i\
+ -t\
+ -v "$PWD":'/test'\
+ -v "$( go env GOMODCACHE )":'/go/pkg/mod'\
+ agdns_bindtodevice_test