Sync v2.1.6

This commit is contained in:
Andrey Meshkov 2023-03-18 17:11:10 +03:00
parent f20c533cc3
commit 879ee07966
118 changed files with 4999 additions and 2059 deletions

13
.github/config.yaml vendored Normal file
View File

@ -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'

30
.github/feature_request_template.yaml vendored Normal file
View File

@ -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

111
.github/issue_template.yaml vendored Normal file
View File

@ -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 `<details> </details>` tag to hide screenshots under the spoiler.
placeholder: If applicable add screenshots explaining your problem.
value: |
<details><summary>Screenshot 1:</summary>
<!-- paste screenshot here -->
</details>
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Information
description:
placeholder: Add any other context about the problem here.
validations:
required: false

View File

@ -92,7 +92,7 @@ following features:
## Software License ## 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 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 the terms of the GNU Affero General Public License as published by the Free

View File

@ -8,7 +8,6 @@ ratelimit:
refuseany: true refuseany: true
# If response is larger than this, it is counted as several responses. # If response is larger than this, it is counted as several responses.
response_size_estimate: 1KB response_size_estimate: 1KB
rps: 30
# Rate limit options for IPv4 addresses. # Rate limit options for IPv4 addresses.
ipv4: ipv4:
# Rate of requests per second for one subnet for IPv4 addresses. # Rate of requests per second for one subnet for IPv4 addresses.

View File

@ -1,8 +1,19 @@
# AdGuard DNS Development Setup # AdGuard DNS Development Setup
## Contents
* [Initial setup](#init)
* [Common Makefile macros and targets](#makefile)
* [How to run AdGuard DNS](#run)
* [Testing](#testing)
## <a href="#init" id="init" name="init">Initial setup</a>
Development is supported on Linux and macOS (aka Darwin) systems. 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. 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.
## <a href="#makefile" id="makefile" name="makefile">Common Makefile Macros And Targets</a> ## <a href="#makefile" id="makefile" name="makefile">Common Makefile macros and targets</a>
Most development tasks are done through the use of our Makefile. Please keep Most development tasks are done through the use of our Makefile. Please keep
the Makefile POSIX-compliant and portable. the Makefile POSIX-compliant and portable.
@ -92,13 +103,13 @@ This is not an extensive list. See `../Makefile`.
## <a href="#run" id="run" name="run">How To Run AdGuard DNS</a> ## <a href="#run" id="run" name="run">How to run AdGuard DNS</a>
This is an example on how to run AdGuard DNS locally. This is an example on how to run AdGuard DNS locally.
### <a href="#run-1" id="run-1" name="run-1">Step 1: Prepare The TLS Certificate And The Key</a> ### <a href="#run-1" id="run-1" name="run-1">Step 1: prepare the TLS certificate and the key</a>
Keeping the test files in the `test` directory since it's added to `.gitignore`: Keeping the test files in the `test` directory since it's added to `.gitignore`:
@ -122,7 +133,7 @@ openssl rand 32 > ./tls_key_2
### <a href="#run-2" id="run-2" name="run-2">Step 2: Prepare The DNSCrypt Configuration</a> ### <a href="#run-2" id="run-2" name="run-2">Step 2: prepare the DNSCrypt configuration</a>
Install the [`dnscrypt`][dnsc] tool: Install the [`dnscrypt`][dnsc] tool:
@ -143,7 +154,7 @@ dnscrypt generate -p testdns -o ./dnscrypt.yml
### <a href="#run-3" id="run-3" name="run-3">Step 3: Prepare The Configuration File</a> ### <a href="#run-3" id="run-3" name="run-3">Step 3: prepare the configuration file</a>
```sh ```sh
cd ../ cd ../
@ -152,7 +163,7 @@ cp -f config.dist.yml config.yml
### <a href="#run-4" id="run-4" name="run-4">Step 4: Prepare The Test Data</a> ### <a href="#run-4" id="run-4" name="run-4">Step 4: prepare the test data</a>
```sh ```sh
echo '<html><body>Dangerous content ahead</body></html>' > ./test/block_page_sb.html echo '<html><body>Dangerous content ahead</body></html>' > ./test/block_page_sb.html
@ -163,7 +174,7 @@ echo '<html><body>Error 500</body></html>' > ./test/error_500.html
### <a href="#run-5" id="run-5" name="run-5">Step 5: Compile AdGuard DNS</a> ### <a href="#run-5" id="run-5" name="run-5">Step 5: compile AdGuard DNS</a>
```sh ```sh
make build make build
@ -171,7 +182,7 @@ make build
### <a href="#run-6" id="run-6" name="run-6">Step 6: Prepare Cache Data And GeoIP</a> ### <a href="#run-6" id="run-6" name="run-6">Step 6: prepare cache data and GeoIP</a>
We'll use the test versions of the GeoIP databases here. 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
### <a href="#run-7" id="run-7" name="run-7">Step 7: Run AdGuard DNS</a> ### <a href="#run-7" id="run-7" name="run-7">Step 7: run AdGuard DNS</a>
You'll need to supply the following: You'll need to supply the following:
@ -235,7 +246,7 @@ env \
### <a href="#run-8" id="run-8" name="run-8">Step 8: Test Your Instance</a> ### <a href="#run-8" id="run-8" name="run-8">Step 8: test your instance</a>
Plain DNS: Plain DNS:
@ -275,3 +286,55 @@ dnslookup example.org sdns://AQcAAAAAAAAADjEyNy4wLjAuMTo1NDQzIAbKgP3dmXybr1DaKIF
[dnsc]: https://github.com/ameshkov/dnscrypt [dnsc]: https://github.com/ameshkov/dnscrypt
[dnscdl]: https://github.com/ameshkov/dnscrypt/releases [dnscdl]: https://github.com/ameshkov/dnscrypt/releases
## <a href="#testing" id="testing" name="testing">Testing</a>
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`.
### <a href="#testing-bindtodevice" id="testing-bindtodevice" name="testing-bindtodevice">Testing `SO_BINDTODEVICE` features</a>
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
```

52
go.mod
View File

@ -1,30 +1,31 @@
module github.com/AdguardTeam/AdGuardDNS module github.com/AdguardTeam/AdGuardDNS
go 1.19 go 1.20
require ( require (
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.100.0 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/AdguardTeam/urlfilter v0.16.1
github.com/ameshkov/dnscrypt/v2 v2.2.5 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/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/caarlos0/env/v6 v6.10.1 github.com/caarlos0/env/v7 v7.1.0
github.com/getsentry/sentry-go v0.15.0 github.com/getsentry/sentry-go v0.19.0
github.com/google/renameio v1.0.1 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/oschwald/maxminddb-golang v1.10.0
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0
github.com/prometheus/client_model v0.3.0 github.com/prometheus/client_model v0.3.0
github.com/prometheus/common v0.37.0 github.com/prometheus/common v0.41.0
github.com/stretchr/testify v1.8.1 github.com/quic-go/quic-go v0.33.0
go.etcd.io/bbolt v1.3.6 github.com/stretchr/testify v1.8.2
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 go.etcd.io/bbolt v1.3.7
golang.org/x/net v0.4.0 golang.org/x/exp v0.0.0-20230307190834-24139beb5833
golang.org/x/sys v0.3.0 golang.org/x/net v0.8.0
golang.org/x/time v0.2.0 golang.org/x/sys v0.6.0
golang.org/x/time v0.3.0
gopkg.in/yaml.v2 v2.4.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/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // 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/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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // 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/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/matttproud/golang_protobuf_extensions v1.0.4 // 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/panjf2000/ants/v2 v2.7.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/crypto v0.3.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
golang.org/x/mod v0.7.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
golang.org/x/text v0.5.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
golang.org/x/tools v0.3.0 // 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 google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

531
go.sum
View File

@ -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.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.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.11.4 h1:IltyvxwCTN+xxJF5sh6VadF8Zfbf8elgCm9dgijSVzM= github.com/AdguardTeam/golibs v0.12.1 h1:bJfFzCnUCl+QsP6prUltM2Sjt0fTiDBPlxuAwfKP3g8=
github.com/AdguardTeam/golibs v0.11.4/go.mod h1:87bN2x4VsTritptE3XZg9l8T6gznWsIxHBcQ1DeRIXA= 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/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= github.com/AdguardTeam/urlfilter v0.16.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 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= 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 h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= 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 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY=
github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM= github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= 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-20230201085229-3ddf4bad03dc h1:Keo7wQ7UODUaHcEi7ltENhbAK2VgZjfat6mLy03tQzo=
github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= github.com/axiomhq/hyperloglog v0.0.0-20230201085229-3ddf4bad03dc/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= 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 h1:6+ZFm0flnudZzdSE0JxlhR2hKnGPcNB35BjQf4RYQDY=
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= 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/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/getsentry/sentry-go v0.19.0 h1:BcCH3CN5tXt5aML+gwmbFwVptLLQA+eT866fCO9wVOM=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKYwpEqryTOC/nE=
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/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 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-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 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.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 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
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/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/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/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/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 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.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
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/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.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= 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 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M= github.com/panjf2000/ants/v2 v2.7.1 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 h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 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.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
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/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA= github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ= github.com/shirou/gopsutil/v3 v3.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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.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.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.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= 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 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 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= 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.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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-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-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-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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-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.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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-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-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-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-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-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-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-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-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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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.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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-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-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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.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 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/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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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=

View File

@ -1,4 +1,4 @@
go 1.19 go 1.20
use ( use (
. .

View File

@ -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/service/change v0.0.0-20181023043359-a85b471d5412 h1:GvWw74lx5noHocd+f6HBMXK6DuggBB1dhVkuGZbv7qM=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c h1:ivON6cwHK1OH26MZyWDCnbTRZZf0IhNsENoNAKFS1g4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c h1:ivON6cwHK1OH26MZyWDCnbTRZZf0IhNsENoNAKFS1g4=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8=
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.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/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGjKN9pinUU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= 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/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 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-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 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/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-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.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/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/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-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-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-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/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -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-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 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
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 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 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.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 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= 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/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= 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= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=

View File

@ -58,7 +58,7 @@ func NewDeviceID(s string) (id DeviceID, err error) {
return "", err return "", err
} }
err = netutil.ValidateDomainNameLabel(s) err = netutil.ValidateHostnameLabel(s)
if err != nil { if err != nil {
// Unwrap the error to replace the domain name label wrapper message // Unwrap the error to replace the domain name label wrapper message
// with our own. // with our own.

View File

@ -5,6 +5,7 @@ import (
"math" "math"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
) )
@ -14,7 +15,8 @@ import (
// the infrastructure, a profile is also called a “DNS server”. We call it // the infrastructure, a profile is also called a “DNS server”. We call it
// profile, because it's less confusing. // 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 // 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 // response by grouping parental, rule list, and safe browsing settings into
@ -24,6 +26,9 @@ type Profile struct {
// FilteringEnabled is set to false. // FilteringEnabled is set to false.
Parental *ParentalProtectionSettings Parental *ParentalProtectionSettings
// BlockingMode defines the way blocked responses are constructed.
BlockingMode dnsmsg.BlockingModeCodec
// ID is the unique ID of this profile. // ID is the unique ID of this profile.
ID ProfileID ID ProfileID

View File

@ -179,6 +179,8 @@ type profileCache struct {
// saveStorageCache saves profiles data to cache file. // saveStorageCache saves profiles data to cache file.
func (db *DefaultProfileDB) saveProfileCache(ctx context.Context) (err error) { func (db *DefaultProfileDB) saveProfileCache(ctx context.Context) (err error) {
log.Info("profiledb: saving profile cache")
var resp *PSProfilesResponse var resp *PSProfilesResponse
resp, err = db.storage.Profiles(ctx, &PSProfilesRequest{ resp, err = db.storage.Profiles(ctx, &PSProfilesRequest{
SyncTime: time.Time{}, SyncTime: time.Time{},
@ -205,33 +207,43 @@ func (db *DefaultProfileDB) saveProfileCache(ctx context.Context) (err error) {
return err 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 return nil
} }
// defaultProfileDBCacheVersion is the version of cached data structure. It's // defaultProfileDBCacheVersion is the version of cached data structure. It's
// manually incremented on every change in [profileCache] structure. // manually incremented on every change in [profileCache] structure.
const defaultProfileDBCacheVersion = 1 const defaultProfileDBCacheVersion = 2
// loadProfileCache loads profiles data from cache file. // loadProfileCache loads profiles data from cache file.
func (db *DefaultProfileDB) loadProfileCache() (err error) { func (db *DefaultProfileDB) loadProfileCache() (err error) {
log.Info("profiledb: loading cache")
data, err := db.loadStorageCache() data, err := db.loadStorageCache()
if err != nil { if err != nil {
return fmt.Errorf("loading cache: %w", err) return fmt.Errorf("loading cache: %w", err)
} }
if data == nil { if data == nil {
log.Info("profiledb: cache is empty")
return nil return nil
} }
if data.Version == defaultProfileDBCacheVersion { if data.Version == defaultProfileDBCacheVersion {
profiles := data.Profiles profiles := data.Profiles
devNum := db.setProfiles(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.syncTime = data.SyncTime
db.syncTimeFull = data.SyncTime db.syncTimeFull = data.SyncTime
} else {
log.Info(
"profiledb: cache version %d is different from %d",
data.Version,
defaultProfileDBCacheVersion,
)
} }
return nil return nil

View File

@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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) { ) (resp *agd.PSProfilesResponse, err error) {
return &agd.PSProfilesResponse{ return &agd.PSProfilesResponse{
Profiles: []*agd.Profile{{ Profiles: []*agd.Profile{{
BlockingMode: dnsmsg.BlockingModeCodec{
Mode: &dnsmsg.BlockingModeNullIP{},
},
ID: testProfID, ID: testProfID,
Devices: []*agd.Device{dev}, Devices: []*agd.Device{dev},
}}, }},

View File

@ -4,6 +4,7 @@ import (
"crypto/tls" "crypto/tls"
"net/netip" "net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"github.com/ameshkov/dnscrypt/v2" "github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -87,8 +88,8 @@ type Server struct {
// Server Name. // Server Name.
Name ServerName Name ServerName
// BindAddresses are addresses this server binds to. // BindData are the socket binding data for this server.
BindAddresses []netip.AddrPort BindData []*ServerBindData
// Protocol is the protocol of the server. // Protocol is the protocol of the server.
Protocol Protocol Protocol Protocol
@ -98,6 +99,22 @@ type Server struct {
LinkedIPEnabled bool 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. // ServerName is the name of a server.
type ServerName string type ServerName string

View File

@ -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
}

View File

@ -1,3 +1,18 @@
// Package agdtest contains simple mocks for common interfaces and other test // Package agdtest contains simple mocks for common interfaces and other test
// utilities. // utilities.
package agdtest 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)
}

View File

@ -10,10 +10,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdmaps"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
// Billing Statistics Uploader // Billing Statistics Uploader
@ -114,13 +113,8 @@ type v1DevicesActivityReqDevice struct {
// billStatRecsToReq converts billing statistics records into devices for the // billStatRecsToReq converts billing statistics records into devices for the
// devices activity HTTP API. // devices activity HTTP API.
func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityReqDevice) { func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityReqDevice) {
// Sort the keys to make the queries reproducible and testable. devices = make([]*v1DevicesActivityReqDevice, 0, len(records))
deviceIDs := maps.Keys(records) agdmaps.OrderedRange(records, func(id agd.DeviceID, rec *billstat.Record) (cont bool) {
slices.Sort(deviceIDs)
devices = make([]*v1DevicesActivityReqDevice, 0, len(deviceIDs))
for _, id := range deviceIDs {
rec := records[id]
devices = append(devices, &v1DevicesActivityReqDevice{ devices = append(devices, &v1DevicesActivityReqDevice{
ClientCountry: rec.Country, ClientCountry: rec.Country,
DeviceID: id, DeviceID: id,
@ -129,7 +123,9 @@ func billStatRecsToReq(records billstat.Records) (devices []*v1DevicesActivityRe
Queries: rec.Queries, Queries: rec.Queries,
Proto: uint8(rec.Proto), Proto: uint8(rec.Proto),
}) })
}
return true
})
return devices return devices
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp" "github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
@ -158,6 +159,7 @@ type v1SettingsRespSettings struct {
Parental *v1SettingsRespParental `json:"parental"` Parental *v1SettingsRespParental `json:"parental"`
RuleLists *v1SettingsRespRuleLists `json:"rule_lists"` RuleLists *v1SettingsRespRuleLists `json:"rule_lists"`
SafeBrowsing *v1SettingsRespSafeBrowsing `json:"safe_browsing"` SafeBrowsing *v1SettingsRespSafeBrowsing `json:"safe_browsing"`
BlockingMode dnsmsg.BlockingModeCodec `json:"blocking_mode"`
Devices []*v1SettingsRespDevice `json:"devices"` Devices []*v1SettingsRespDevice `json:"devices"`
CustomRules []string `json:"custom_rules"` CustomRules []string `json:"custom_rules"`
FilteredResponseTTL uint32 `json:"filtered_response_ttl"` FilteredResponseTTL uint32 `json:"filtered_response_ttl"`
@ -180,6 +182,9 @@ func (rs *v1SettingsRespSettings) UnmarshalJSON(b []byte) (err error) {
type defaultDec v1SettingsRespSettings type defaultDec v1SettingsRespSettings
s := defaultDec{ s := defaultDec{
BlockingMode: dnsmsg.BlockingModeCodec{
Mode: &dnsmsg.BlockingModeNullIP{},
},
BlockFirefoxCanary: true, BlockFirefoxCanary: true,
} }
@ -496,6 +501,7 @@ func (r *v1SettingsResp) toInternal(
pr.Profiles = append(pr.Profiles, &agd.Profile{ pr.Profiles = append(pr.Profiles, &agd.Profile{
Parental: parental, Parental: parental,
BlockingMode: s.BlockingMode,
ID: id, ID: id,
UpdateTime: updTime, UpdateTime: updTime,
Devices: devices, Devices: devices,

View File

@ -14,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/backend" "github.com/AdguardTeam/AdGuardDNS/internal/backend"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -30,6 +31,10 @@ func TestProfileStorage_Profiles(t *testing.T) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pt := testutil.PanicT{} 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() reqURLStr = r.URL.String()
b, err := os.ReadFile(filepath.Join("testdata", "profiles.json")) b, err := os.ReadFile(filepath.Join("testdata", "profiles.json"))
require.NoError(pt, err) require.NoError(pt, err)
@ -86,6 +91,7 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
Start: 0, Start: 0,
End: 59, End: 59,
} }
wantParental := &agd.ParentalProtectionSettings{ wantParental := &agd.ParentalProtectionSettings{
Schedule: &agd.ParentalProtectionSchedule{ Schedule: &agd.ParentalProtectionSchedule{
Week: &agd.WeeklySchedule{ Week: &agd.WeeklySchedule{
@ -105,12 +111,23 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
GeneralSafeSearch: false, GeneralSafeSearch: false,
YoutubeSafeSearch: false, YoutubeSafeSearch: false,
} }
wantLinkedIP := netip.AddrFrom4([4]byte{1, 2, 3, 4}) 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{ want := &agd.PSProfilesResponse{
SyncTime: syncTime, SyncTime: syncTime,
Profiles: []*agd.Profile{{ Profiles: []*agd.Profile{{
Parental: nil, Parental: nil,
BlockingMode: dnsmsg.BlockingModeCodec{
Mode: &dnsmsg.BlockingModeNullIP{},
},
ID: "37f97ee9", ID: "37f97ee9",
UpdateTime: updTime, UpdateTime: updTime,
Devices: []*agd.Device{{ Devices: []*agd.Device{{
@ -133,9 +150,10 @@ func testProfileResp(t *testing.T) *agd.PSProfilesResponse {
BlockPrivateRelay: true, BlockPrivateRelay: true,
BlockFirefoxCanary: true, BlockFirefoxCanary: true,
}, { }, {
Parental: wantParental, Parental: wantParental,
ID: "83f3ea8f", BlockingMode: wantBlockingMode,
UpdateTime: updTime, ID: "83f3ea8f",
UpdateTime: updTime,
Devices: []*agd.Device{{ Devices: []*agd.Device{{
ID: "0d7724fa", ID: "0d7724fa",
Name: "Device 1", Name: "Device 1",

View File

@ -5,13 +5,11 @@
"dns_id": "37f97ee9", "dns_id": "37f97ee9",
"filtering_enabled": true, "filtering_enabled": true,
"query_log_enabled": true, "query_log_enabled": true,
"safe_browsing": "safe_browsing": {
{
"enabled": true "enabled": true
}, },
"deleted": false, "deleted": false,
"block_private_relay": true, "block_private_relay": true,
"block_firefox_canary": true,
"devices": [ "devices": [
{ {
"id": "118ffe93", "id": "118ffe93",
@ -24,8 +22,7 @@
"filtering_enabled": true "filtering_enabled": true
} }
], ],
"rule_lists": "rule_lists": {
{
"enabled": true, "enabled": true,
"ids": [ "ids": [
"1" "1"
@ -38,8 +35,7 @@
"dns_id": "83f3ea8f", "dns_id": "83f3ea8f",
"filtering_enabled": true, "filtering_enabled": true,
"query_log_enabled": true, "query_log_enabled": true,
"safe_browsing": "safe_browsing": {
{
"enabled": true "enabled": true
}, },
"deleted": true, "deleted": true,
@ -107,6 +103,11 @@
] ]
}, },
"filtered_response_ttl": 3600, "filtered_response_ttl": 3600,
"blocking_mode": {
"type": "custom_ip",
"ipv4": "1.2.3.4",
"ipv6": "1234::cdef"
},
"custom_rules": [ "custom_rules": [
"||example.org^" "||example.org^"
] ]

View File

@ -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)
}

View File

@ -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

View File

@ -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"

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
})
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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 }

View File

@ -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 }

View File

@ -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 }

View File

@ -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())
}

View File

@ -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
}

View File

@ -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
}

View File

@ -3,28 +3,22 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/AdguardTeam/AdGuardDNS/internal/agdmaps"
"github.com/prometheus/common/model" "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. // additionalInfo is a extra info configuration.
type additionalInfo map[string]string type additionalInfo map[string]string
// validateAdditionalInfo return an error is the section is invalid. // validateAdditionalInfo return an error is the section is invalid.
func (c additionalInfo) validate() (err error) { func (c additionalInfo) validate() (err error) {
if c == nil { return agdmaps.OrderedRangeError(c, func(k, _ string) (keyErr error) {
return nil if model.LabelName(k).IsValid() {
} 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 nil return fmt.Errorf("prometheus labels must match %s, got %q", model.LabelNameRE, k)
})
} }

View File

@ -1,10 +1,13 @@
package cmd package cmd
import ( import (
"context"
"fmt"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/backend" "github.com/AdguardTeam/AdGuardDNS/internal/backend"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
@ -64,3 +67,68 @@ func (c *backendConfig) validate() (err error) {
return nil 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
}

View File

@ -15,7 +15,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
// DNS Server Check Configuration // DNS server check configuration
// checkConfig is the DNS server checking configuration. // checkConfig is the DNS server checking configuration.
type checkConfig struct { type checkConfig struct {
@ -86,8 +86,6 @@ func (c *checkConfig) toInternal(
// validate returns an error if the DNS server checking configuration is // validate returns an error if the DNS server checking configuration is
// invalid. // invalid.
//
// TODO(a.garipov): Factor out IP validation; add IPv6 validation.
func (c *checkConfig) validate() (err error) { func (c *checkConfig) validate() (err error) {
if c == nil { if c == nil {
return errNilConfig 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 // validateNonNilIPs returns an error if ips is empty or had IP addresses of
// incorrect protocol version. // incorrect protocol version.
//
// TODO(a.garipov): Merge with [validateAddrs].
func validateNonNilIPs(ips []netip.Addr, fam netutil.AddrFamily) (err error) { func validateNonNilIPs(ips []netip.Addr, fam netutil.AddrFamily) (err error) {
if len(ips) == 0 { if len(ips) == 0 {
return fmt.Errorf("no %s", fam) return fmt.Errorf("no %s", fam)

View File

@ -8,20 +8,15 @@ import (
"math/rand" "math/rand"
"os" "os"
"runtime" "runtime"
"sync"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet" "github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/backend"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc" "github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck" "github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip" "github.com/AdguardTeam/AdGuardDNS/internal/geoip"
@ -37,6 +32,8 @@ import (
func Main() { func Main() {
// Initial Configuration // 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()) rand.Seed(time.Now().UnixNano())
// Log only to stdout and let users decide how to process it. // 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. // Signal service startup now that we have the logs set up.
log.Info("main: starting adguard dns") log.Info("main: starting adguard dns")
// Error Collector // Error collector
// //
// TODO(a.garipov): Consider parsing SENTRY_DSN separately to set sentry up // TODO(a.garipov): Consider parsing SENTRY_DSN separately to set sentry up
// first and collect panics from the readEnvs call above as well. // first and collect panics from the readEnvs call above as well.
@ -60,7 +57,7 @@ func Main() {
defer collectPanics(errColl) defer collectPanics(errColl)
// Configuration File // Configuration file
c, err := readConfig(envs.ConfPath) c, err := readConfig(envs.ConfPath)
check(err) check(err)
@ -68,88 +65,54 @@ func Main() {
err = c.validate() err = c.validate()
check(err) check(err)
// Additional Metrics // Additional metrics
metrics.SetAdditionalInfo(c.AdditionalMetricsInfo) metrics.SetAdditionalInfo(c.AdditionalMetricsInfo)
// GeoIP Database // Signal handler
// We start GeoIP initialization early in a dedicated routine cause it sigHdlr := newSignalHandler()
// takes time, later we wait for completion and continue with GeoIP.
// 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. // See AGDNS-884.
geoIPMu := &sync.Mutex{} geoIP, geoIPRefr := &geoip.File{}, &agd.RefreshWorker{}
geoIPErrCh := make(chan error, 1)
var ( go setupGeoIP(geoIP, geoIPRefr, geoIPErrCh, c.GeoIP, envs, errColl)
geoIP *geoip.File
geoIPErr error
)
geoIPMu.Lock()
go func() {
defer geoIPMu.Unlock()
geoIP, geoIPErr = envs.geoIP(c.GeoIP)
}()
// Safe-browsing and adult-blocking filters // Safe-browsing and adult-blocking filters
// TODO(ameshkov): Consider making configurable. // TODO(ameshkov): Consider making configurable.
filteringResolver := agdnet.NewCachingResolver( filteringResolver := agdnet.NewCachingResolver(agdnet.DefaultResolver{}, 1*timeutil.Day)
agdnet.DefaultResolver{},
1*timeutil.Day,
)
err = os.MkdirAll(envs.FilterCachePath, agd.DefaultDirPerm) err = os.MkdirAll(envs.FilterCachePath, agd.DefaultDirPerm)
check(err) check(err)
safeBrowsingConf, err := c.SafeBrowsing.toInternal( safeBrowsingHashes, safeBrowsingFilter, err := setupHashPrefixFilter(
errColl, c.SafeBrowsing,
filteringResolver, filteringResolver,
agd.FilterListIDSafeBrowsing, agd.FilterListIDSafeBrowsing,
envs.FilterCachePath, envs.FilterCachePath,
sigHdlr,
errColl,
) )
check(err) check(err)
safeBrowsingFilter, err := filter.NewHashPrefix(safeBrowsingConf) adultBlockingHashes, adultBlockingFilter, err := setupHashPrefixFilter(
check(err) c.AdultBlocking,
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,
filteringResolver, filteringResolver,
agd.FilterListIDAdultBlocking, agd.FilterListIDAdultBlocking,
envs.FilterCachePath, envs.FilterCachePath,
sigHdlr,
errColl,
) )
check(err) 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 // Filter storage and filtering groups
fltStrgConf := c.Filters.toInternal( fltStrgConf := c.Filters.toInternal(
@ -160,31 +123,16 @@ func Main() {
adultBlockingFilter, adultBlockingFilter,
) )
fltStrg, err := filter.NewDefaultStorage(fltStrgConf) fltRefrTimeout := c.Filters.RefreshTimeout.Duration
fltStrg, err := setupFilterStorage(fltStrgConf, sigHdlr, errColl, fltRefrTimeout)
check(err) 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) fltGroups, err := c.FilteringGroups.toInternal(fltStrg)
check(err) check(err)
messages := &dnsmsg.Constructor{ // Server groups
FilteredResponseTTL: c.Filters.ResponseTTL.Duration,
} messages := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, c.Filters.ResponseTTL.Duration)
srvGrps, err := c.ServerGroups.toInternal(messages, fltGroups) srvGrps, err := c.ServerGroups.toInternal(messages, fltGroups)
check(err) check(err)
@ -197,133 +145,45 @@ func Main() {
check(err) check(err)
} }
// TLS Session Tickets Rotation // TLS session-tickets rotation
tickRot, err := newTicketRotator(srvGrps) err = setupTicketRotator(srvGrps, sigHdlr, errColl)
check(err) check(err)
tickRotUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ // Profiles database and billing statistics
Context: ctxWithDefaultTimeout,
Refresher: tickRot, profDB, billStatRec, err := setupBackend(c.Backend, envs, sigHdlr, errColl)
ErrColl: errColl,
Name: "tickrot",
// TODO(ameshkov): Consider making configurable.
Interval: 1 * time.Minute,
RefreshOnShutdown: false,
RoutineLogsAreDebug: true,
})
err = tickRotUpd.Start()
check(err) check(err)
// Profiles Database // DNS checker
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
dnsCk, err := dnscheck.NewConsul(c.Check.toInternal(envs, messages, errColl)) dnsCk, err := dnscheck.NewConsul(c.Check.toInternal(envs, messages, errColl))
check(err) check(err)
// DNSDB // DNSDB
dnsDB, dnsDBUpd := envs.buildDNSDB(errColl) dnsDB, err := envs.buildDNSDB(sigHdlr, errColl)
err = dnsDBUpd.Start()
check(err) check(err)
// Filtering Rule Statistics // Filtering-rule statistics
ruleStat, ruleStatUpd := envs.ruleStat(errColl) ruleStat, err := envs.buildRuleStat(sigHdlr, errColl)
err = ruleStatUpd.Start()
check(err) 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) check(err)
allowlist := ratelimit.NewDynamicAllowlist(allowSubnets, nil) // GeoIP database
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
// Wait for long-running GeoIP initialization. // Wait for long-running GeoIP initialization.
geoIPMu.Lock() check(<-geoIPErrCh)
defer geoIPMu.Unlock()
check(geoIPErr) sigHdlr.add(geoIPRefr)
geoIPUpd := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ // Web service
Context: ctxWithDefaultTimeout,
Refresher: geoIP,
ErrColl: errColl,
Name: "geoip",
Interval: c.GeoIP.RefreshIvl.Duration,
RefreshOnShutdown: false,
RoutineLogsAreDebug: false,
})
err = geoIPUpd.Start()
check(err)
// Web Service
webConf, err := c.Web.toInternal(envs, dnsCk, errColl) webConf, err := c.Web.toInternal(envs, dnsCk, errColl)
check(err) check(err)
@ -333,7 +193,9 @@ func Main() {
// instead of returning an error. // instead of returning an error.
_ = webSvc.Start() _ = webSvc.Start()
// DNS Service sigHdlr.add(webSvc)
// DNS service
metricsListener := prometheus.NewForwardMetricsListener(len(c.Upstream.FallbackServers) + 1) metricsListener := prometheus.NewForwardMetricsListener(len(c.Upstream.FallbackServers) + 1)
@ -351,11 +213,8 @@ func Main() {
}, c.Upstream.Healthcheck.Enabled) }, c.Upstream.Healthcheck.Enabled)
dnsConf := &dnssvc.Config{ dnsConf := &dnssvc.Config{
Messages: messages, Messages: messages,
SafeBrowsing: filter.NewSafeBrowsingServer( SafeBrowsing: filter.NewSafeBrowsingServer(safeBrowsingHashes, adultBlockingHashes),
safeBrowsingConf.Hashes,
adultBlockingConf.Hashes,
),
BillStat: billStatRec, BillStat: billStatRec,
ProfileDB: profDB, ProfileDB: profDB,
DNSCheck: dnsCk, DNSCheck: dnsCk,
@ -365,7 +224,7 @@ func Main() {
FilterStorage: fltStrg, FilterStorage: fltStrg,
GeoIP: geoIP, GeoIP: geoIP,
Handler: handler, Handler: handler,
QueryLog: queryLog, QueryLog: c.buildQueryLog(envs),
RuleStat: ruleStat, RuleStat: ruleStat,
Upstream: upstream, Upstream: upstream,
RateLimit: rateLimiter, RateLimit: rateLimiter,
@ -380,6 +239,10 @@ func Main() {
dnsSvc, err := dnssvc.New(dnsConf) dnsSvc, err := dnssvc.New(dnsConf)
check(err) check(err)
sigHdlr.add(dnsSvc)
// Connectivity check
err = connectivityCheck(dnsConf, c.ConnectivityCheck) err = connectivityCheck(dnsConf, c.ConnectivityCheck)
check(err) check(err)
@ -387,11 +250,15 @@ func Main() {
err = upstreamHealthcheckUpd.Start() err = upstreamHealthcheckUpd.Start()
check(err) check(err)
sigHdlr.add(upstreamHealthcheckUpd)
// The DNS service is considered critical, so its Start method panics // The DNS service is considered critical, so its Start method panics
// instead of returning an error. // instead of returning an error.
_ = dnsSvc.Start() _ = dnsSvc.Start()
// Debug HTTP Service sigHdlr.add(dnsSvc)
// Debug HTTP-service
debugSvc := debugsvc.New(envs.debugConf(dnsDB)) debugSvc := debugsvc.New(envs.debugConf(dnsDB))
@ -399,6 +266,8 @@ func Main() {
// instead of returning an error. // instead of returning an error.
_ = debugSvc.Start() _ = debugSvc.Start()
sigHdlr.add(debugSvc)
// Signal that the server is started. // Signal that the server is started.
metrics.SetUpGauge( metrics.SetUpGauge(
agd.Version(), agd.Version(),
@ -408,23 +277,7 @@ func Main() {
runtime.Version(), runtime.Version(),
) )
h := newSignalHandler( os.Exit(sigHdlr.handle())
debugSvc,
webSvc,
dnsSvc,
safeBrowsingUpd,
adultBlockingUpd,
profDBUpd,
dnsDBUpd,
geoIPUpd,
ruleStatUpd,
allowlistUpd,
fltStrgUpd,
tickRotUpd,
billStatRecUpd,
)
os.Exit(h.handle())
} }
// collectPanics reports all panics in Main. It should be called in a defer. // collectPanics reports all panics in Main. It should be called in a defer.
@ -448,13 +301,3 @@ func collectPanics(errColl agd.ErrorCollector) {
panic(v) 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)
}

View File

@ -1,12 +1,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog" "github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -154,60 +155,6 @@ func (c *configuration) validate() (err error) {
return nil 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. // readConfig reads the configuration.
func readConfig(confPath string) (c *configuration, err error) { func readConfig(confPath string) (c *configuration, err error) {
// #nosec G304 -- Trust the path to the configuration file that is given // #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 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)
}

View File

@ -11,6 +11,8 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
// Connectivity check configuration
// connCheckConfig is the connectivity check configuration. // connCheckConfig is the connectivity check configuration.
type connCheckConfig struct { type connCheckConfig struct {
// ProbeIPv4 is a probe v4 address to perform a check to. // 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) { func containsIPv6BindAddress(serverGroups []*agd.ServerGroup) (ok bool) {
for _, srvGrp := range serverGroups { for _, srvGrp := range serverGroups {
for _, s := range srvGrp.Servers { for _, s := range srvGrp.Servers {
for _, addr := range s.BindAddresses { for _, bindData := range s.BindData {
if addr.Addr().Is6() { if addr := bindData.AddrPort; addr.IsValid() && addr.Addr().Is6() {
return true return true
} }
} }

View File

@ -14,7 +14,7 @@ import (
"golang.org/x/exp/slices" "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. // ddrConfig is the configuration for a server group's DDR handler.
type ddrConfig struct { type ddrConfig struct {
@ -118,7 +118,7 @@ func (c *ddrConfig) validate() (err error) {
} }
domainSuf := wildcard[2:] domainSuf := wildcard[2:]
err = netutil.ValidateDomainName(domainSuf) err = netutil.ValidateHostname(domainSuf)
if err != nil { if err != nil {
return fmt.Errorf("device_records: %w", err) return fmt.Errorf("device_records: %w", err)
} }
@ -130,7 +130,7 @@ func (c *ddrConfig) validate() (err error) {
} }
for domain, r := range c.PublicRecords { for domain, r := range c.PublicRecords {
err = netutil.ValidateDomainName(domain) err = netutil.ValidateHostname(domain)
if err != nil { if err != nil {
return fmt.Errorf("public_records: %w", err) 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") 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 { for i, addr := range r.IPv4Hints {
if !addr.Is4() { if !addr.Is4() {
return fmt.Errorf("ipv4_hints: at index %d: not an ipv4 addr", i) return fmt.Errorf("ipv4_hints: at index %d: not an ipv4 addr", i)

View File

@ -16,11 +16,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat" "github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
env "github.com/caarlos0/env/v6" "github.com/caarlos0/env/v7"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
) )
// Environment Configuration // Environment configuration
// environments represents the configuration that is kept in the environment. // environments represents the configuration that is kept in the environment.
type environments struct { type environments struct {
@ -97,13 +97,14 @@ func (envs *environments) buildErrColl() (errColl agd.ErrorCollector, err error)
return errcoll.NewSentryErrorCollector(cli), nil return errcoll.NewSentryErrorCollector(cli), nil
} }
// buildDNSDB builds and returns an anonymous statistics collector and its // buildDNSDB builds and returns an anonymous statistics collector and register
// refresh worker. // its refresher in sigHdlr, if needed.
func (envs *environments) buildDNSDB( func (envs *environments) buildDNSDB(
sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl agd.ErrorCollector,
) (d dnsdb.Interface, refr agd.Service) { ) (d dnsdb.Interface, err error) {
if envs.DNSDBPath == "" { if envs.DNSDBPath == "" {
return dnsdb.Empty{}, agd.EmptyService{} return dnsdb.Empty{}, nil
} }
b := dnsdb.NewBolt(&dnsdb.BoltConfig{ b := dnsdb.NewBolt(&dnsdb.BoltConfig{
@ -111,7 +112,7 @@ func (envs *environments) buildDNSDB(
ErrColl: errColl, ErrColl: errColl,
}) })
refr = agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: b, Refresher: b,
ErrColl: errColl, ErrColl: errColl,
@ -121,8 +122,14 @@ func (envs *environments) buildDNSDB(
RefreshOnShutdown: true, RefreshOnShutdown: true,
RoutineLogsAreDebug: false, 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. // geoIP returns an GeoIP database implementation from environment.
@ -174,22 +181,23 @@ func (envs *environments) debugConf(dnsDB dnsdb.Interface) (conf *debugsvc.Confi
return conf return conf
} }
// ruleStat returns a filtering rule statistics collector from environment. It // buildRuleStat returns a filtering rule statistics collector from environment and
// also returns the refresh worker service that updates the collector. // registers its refresher in sigHdlr, if necessary.
func (envs *environments) ruleStat( func (envs *environments) buildRuleStat(
sigHdlr signalHandler,
errColl agd.ErrorCollector, errColl agd.ErrorCollector,
) (r rulestat.Interface, refr agd.Service) { ) (r rulestat.Interface, err error) {
if envs.RuleStatURL == nil { if envs.RuleStatURL == nil {
log.Info("main: warning: not collecting rule stats") log.Info("main: warning: not collecting rule stats")
return rulestat.Empty{}, agd.EmptyService{} return rulestat.Empty{}, nil
} }
httpRuleStat := rulestat.NewHTTP(&rulestat.HTTPConfig{ httpRuleStat := rulestat.NewHTTP(&rulestat.HTTPConfig{
URL: &envs.RuleStatURL.URL, URL: &envs.RuleStatURL.URL,
}) })
refr = agd.NewRefreshWorker(&agd.RefreshWorkerConfig{ refr := agd.NewRefreshWorker(&agd.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout, Context: ctxWithDefaultTimeout,
Refresher: httpRuleStat, Refresher: httpRuleStat,
ErrColl: errColl, ErrColl: errColl,
@ -199,8 +207,14 @@ func (envs *environments) ruleStat(
RefreshOnShutdown: true, RefreshOnShutdown: true,
RoutineLogsAreDebug: false, 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 // strictBool is a type for booleans that are parsed from the environment more

View File

@ -7,7 +7,7 @@ import (
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
) )
// Error Helpers // Error-handling utilities
// check is a simple error-checking helper. It must only be used within Main. // check is a simple error-checking helper. It must only be used within Main.
func check(err error) { func check(err error) {
@ -40,16 +40,6 @@ type numberOrDuration interface {
constraints.Integer | timeutil.Duration 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 // 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. // 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) { func newMustBePositiveError[T numberOrDuration](prop string, v T) (err error) {

View File

@ -1,6 +1,8 @@
package cmd package cmd
import ( import (
"context"
"fmt"
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
@ -10,7 +12,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
// Filters Configuration // Filters configuration
// filtersConfig contains the configuration for the filter lists and filtering // filtersConfig contains the configuration for the filter lists and filtering
// storage to be used. // storage to be used.
@ -92,3 +94,37 @@ func (c *filtersConfig) validate() (err error) {
return nil 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
}

82
internal/cmd/geoip.go Normal file
View File

@ -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
}

28
internal/cmd/querylog.go Normal file
View File

@ -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"`
}

View File

@ -2,7 +2,11 @@ package cmd
import ( import (
"fmt" "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/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
"github.com/c2h5oh/datasize" "github.com/c2h5oh/datasize"
@ -117,3 +121,41 @@ func (c *rateLimitConfig) validate() (err error) {
validatePositive("allowlist.refresh_interval", c.Allowlist.RefreshIvl), 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
}

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"fmt"
"path/filepath" "path/filepath"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
@ -84,3 +85,42 @@ func (c *safeBrowsingConfig) validate() (err error) {
return nil 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
}

View File

@ -8,22 +8,22 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/slices"
) )
// Server Configuration // Server configuration
// toInternal returns the configuration of DNS servers for a single server // toInternal returns the configuration of DNS servers for a single server
// group. srvs is assumed to be valid. // group. srvs is assumed to be valid.
func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err error) { func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err error) {
dnsSrvs = make([]*agd.Server, 0, len(srvs)) dnsSrvs = make([]*agd.Server, 0, len(srvs))
for _, srv := range srvs { for _, srv := range srvs {
bindData := srv.bindData()
name := agd.ServerName(srv.Name) name := agd.ServerName(srv.Name)
switch p := srv.Protocol; p { switch p := srv.Protocol; p {
case srvProtoDNS: case srvProtoDNS:
dnsSrvs = append(dnsSrvs, &agd.Server{ dnsSrvs = append(dnsSrvs, &agd.Server{
Name: name, Name: name,
BindAddresses: slices.Clone(srv.BindAddresses), BindData: bindData,
Protocol: agd.ProtoDNS, Protocol: agd.ProtoDNS,
LinkedIPEnabled: srv.LinkedIPEnabled, LinkedIPEnabled: srv.LinkedIPEnabled,
}) })
@ -37,7 +37,7 @@ func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err e
dnsSrvs = append(dnsSrvs, &agd.Server{ dnsSrvs = append(dnsSrvs, &agd.Server{
DNSCrypt: dcConf, DNSCrypt: dcConf,
Name: name, Name: name,
BindAddresses: slices.Clone(srv.BindAddresses), BindData: bindData,
Protocol: agd.ProtoDNSCrypt, Protocol: agd.ProtoDNSCrypt,
LinkedIPEnabled: srv.LinkedIPEnabled, LinkedIPEnabled: srv.LinkedIPEnabled,
}) })
@ -56,7 +56,7 @@ func (srvs servers) toInternal(tlsConfig *agd.TLS) (dnsSrvs []*agd.Server, err e
dnsSrvs = append(dnsSrvs, &agd.Server{ dnsSrvs = append(dnsSrvs, &agd.Server{
TLS: tlsConf, TLS: tlsConf,
Name: name, Name: name,
BindAddresses: slices.Clone(srv.BindAddresses), BindData: bindData,
Protocol: p.toInternal(), Protocol: p.toInternal(),
LinkedIPEnabled: srv.LinkedIPEnabled, LinkedIPEnabled: srv.LinkedIPEnabled,
}) })
@ -166,6 +166,21 @@ type server struct {
LinkedIPEnabled bool `yaml:"linked_ip_enabled"` 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. // validate returns an error if the configuration is invalid.
func (s *server) validate() (err error) { func (s *server) validate() (err error) {
switch { switch {
@ -177,6 +192,11 @@ func (s *server) validate() (err error) {
return errors.Error("no bind_addresses") return errors.Error("no bind_addresses")
} }
err = validateAddrs(s.BindAddresses)
if err != nil {
return fmt.Errorf("bind_addresses: %w", err)
}
err = s.Protocol.validate() err = s.Protocol.validate()
if err != nil { if err != nil {
return fmt.Errorf("protocol: %w", err) return fmt.Errorf("protocol: %w", err)

View File

@ -20,6 +20,22 @@ type signalHandler struct {
services []agd.Service 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. // Exit status constants.
const ( const (
statusSuccess = 0 statusSuccess = 0
@ -54,8 +70,9 @@ func (h *signalHandler) shutdown() (status int) {
defer cancel() defer cancel()
log.Info("sighdlr: shutting down services") log.Info("sighdlr: shutting down services")
for i, service := range h.services { for i := len(h.services) - 1; i >= 0; i-- {
err := service.Shutdown(ctx) s := h.services[i]
err := s.Shutdown(ctx)
if err != nil { if err != nil {
log.Error("sighdlr: shutting down service at index %d: %s", i, err) log.Error("sighdlr: shutting down service at index %d: %s", i, err)
status = statusError status = statusError
@ -66,15 +83,3 @@ func (h *signalHandler) shutdown() (status int) {
return status 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
}

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics" "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 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). // 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 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
}

View File

@ -13,7 +13,7 @@ import (
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
) )
// DNS Upstream Configuration // DNS upstream configuration
// upstreamConfig module configuration // upstreamConfig module configuration
type upstreamConfig struct { type upstreamConfig struct {
@ -60,10 +60,9 @@ func (c *upstreamConfig) validate() (err error) {
return newMustBePositiveError("timeout", c.Timeout) return newMustBePositiveError("timeout", c.Timeout)
} }
for i, ipp := range c.FallbackServers { err = validateAddrs(c.FallbackServers)
if ipp == (netip.AddrPort{}) { if err != nil {
return fmt.Errorf("fallback at index %d: no address", i) return fmt.Errorf("fallback: %w", err)
}
} }
return errors.Annotate(c.Healthcheck.validate(), "healthcheck: %w") return errors.Annotate(c.Healthcheck.validate(), "healthcheck: %w")

View File

@ -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
}

View File

@ -220,12 +220,10 @@ func TestConsul_Check(t *testing.T) {
}} }}
conf := &dnscheck.ConsulConfig{ conf := &dnscheck.ConsulConfig{
Messages: &dnsmsg.Constructor{ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
FilteredResponseTTL: ttl * time.Second, Domains: []string{checkDomain},
}, IPv4: []net.IP{{1, 2, 3, 4}},
Domains: []string{checkDomain}, IPv6: []net.IP{net.ParseIP("1234::5678")},
IPv4: []net.IP{{1, 2, 3, 4}},
IPv6: []net.IP{net.ParseIP("1234::5678")},
} }
dnsCk, err := dnscheck.NewConsul(conf) dnsCk, err := dnscheck.NewConsul(conf)
@ -249,9 +247,7 @@ func TestConsul_Check(t *testing.T) {
Host: tc.host, Host: tc.host,
RemoteIP: testRemoteIP, RemoteIP: testRemoteIP,
QType: tc.qtype, QType: tc.qtype,
Messages: &dnsmsg.Constructor{ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
FilteredResponseTTL: ttl * time.Second,
},
} }
resp, cErr := dnsCk.Check(ctx, req, ri) resp, cErr := dnsCk.Check(ctx, req, ri)

View File

@ -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() {}

View File

@ -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
}

View File

@ -14,28 +14,65 @@ import (
// Constructor creates DNS messages for blocked or modified responses. // Constructor creates DNS messages for blocked or modified responses.
type Constructor struct { type Constructor struct {
// FilteredResponseTTL is the time-to-live value used for responses created blockingMode BlockingMode
// by this message constructor. fltRespTTL time.Duration
FilteredResponseTTL time.Duration
} }
// NewBlockedRespMsg returns a blocked DNS response message. For A and AAAA // NewConstructor returns a properly initialized constructor with the given
// requests, it returns a response with an unspecified (aka null) IP (0.0.0.0 // options. respTTL is the time-to-live value used for responses created by
// for IPv4, [::] for IPv6). For all other types of requests, it returns // this message constructor. bm is the blocking mode to use in
// a response with no answers. // [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) { func (c *Constructor) NewBlockedRespMsg(req *dns.Msg) (msg *dns.Msg, err error) {
if qt := req.Question[0].Qtype; qt == dns.TypeA || qt == dns.TypeAAAA { switch m := c.blockingMode.(type) {
msg, err = c.NewIPRespMsg(req, nil) case *BlockingModeCustomIP:
if err != nil { return c.newBlockedCustomIPRespMsg(req, m)
// Technically should never happen. case *BlockingModeNullIP:
return nil, err switch qt := req.Question[0].Qtype; qt {
case dns.TypeA, dns.TypeAAAA:
return c.NewIPRespMsg(req, nil)
default:
return c.NewMsgNODATA(req), nil
} }
} else { case *BlockingModeNXDOMAIN:
msg = c.NewRespMsg(req) return c.NewMsgNXDOMAIN(req), nil
msg.Ns = c.newSOARecords(req) 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 // 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{ return dns.RR_Header{
Name: req.Question[0].Name, Name: req.Question[0].Name,
Rrtype: rrType, Rrtype: rrType,
Ttl: uint32(c.FilteredResponseTTL.Seconds()), Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET, Class: dns.ClassINET,
} }
} }
@ -162,7 +199,7 @@ func (c *Constructor) newHdrWithClass(req *dns.Msg, rrType RRType, class dns.Cla
return dns.RR_Header{ return dns.RR_Header{
Name: req.Question[0].Name, Name: req.Question[0].Name,
Rrtype: rrType, Rrtype: rrType,
Ttl: uint32(c.FilteredResponseTTL.Seconds()), Ttl: uint32(c.fltRespTTL.Seconds()),
Class: uint16(class), Class: uint16(class),
} }
} }
@ -291,7 +328,7 @@ func (c *Constructor) newSOARecords(req *dns.Msg) (soaRecs []dns.RR) {
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: zone, Name: zone,
Rrtype: dns.TypeSOA, Rrtype: dns.TypeSOA,
Ttl: uint32(c.FilteredResponseTTL.Seconds()), Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET, Class: dns.ClassINET,
}, },
Mbox: "hostmaster.", // zone will be appended later if it's not empty or "." Mbox: "hostmaster.", // zone will be appended later if it's not empty or "."

View File

@ -1,6 +1,8 @@
package dnsmsg_test package dnsmsg_test
import ( import (
"net"
"net/netip"
"strings" "strings"
"testing" "testing"
@ -24,15 +26,13 @@ func newTXTExtra(ttl uint32, strs ...string) (extra []dns.RR) {
}} }}
} }
func TestConstructor_NewBlockedRespMsg(t *testing.T) { func TestConstructor_NewBlockedRespMsg_nullIP(t *testing.T) {
mc := dnsmsg.Constructor{ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
FilteredResponseTTL: testFltRespTTL,
}
testCases := []struct { testCases := []struct {
name string name string
wantAnsNum int wantAnsNum int
qt uint16 qt dnsmsg.RRType
}{{ }{{
name: "a", name: "a",
wantAnsNum: 1, wantAnsNum: 1,
@ -49,38 +49,137 @@ func TestConstructor_NewBlockedRespMsg(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := dnsservertest.NewReq("example.com", tc.qt, dns.ClassINET) req := dnsservertest.NewReq(testFQDN, tc.qt, dns.ClassINET)
resp, err := mc.NewBlockedRespMsg(req) resp, err := mc.NewBlockedRespMsg(req)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)
assert.Equal(t, dns.RcodeSuccess, resp.Rcode) assert.Equal(t, dns.RcodeSuccess, resp.Rcode)
ttl := uint32(testFltRespTTL.Seconds()) const wantTTL = testFltRespTTLSec
if tc.wantAnsNum == 0 { if tc.wantAnsNum == 0 {
assert.Empty(t, resp.Answer) assert.Empty(t, resp.Answer)
require.Len(t, resp.Ns, 1) require.Len(t, resp.Ns, 1)
ns := resp.Ns[0] nsTTL := resp.Ns[0].Header().Ttl
assert.Equal(t, ttl, ns.Header().Ttl) assert.Equal(t, wantTTL, nsTTL)
} else { } else {
require.Len(t, resp.Answer, 1) require.Len(t, resp.Answer, 1)
ans := resp.Answer[0] ansTTL := resp.Answer[0].Header().Ttl
assert.Equal(t, ttl, ans.Header().Ttl) assert.Equal(t, wantTTL, ansTTL)
} }
}) })
} }
} }
func TestConstructor_noAnswerMethods(t *testing.T) { func TestConstructor_NewBlockedRespMsg_customIP(t *testing.T) {
mc := dnsmsg.Constructor{ wantIPv4 := netip.MustParseAddr("1.2.3.4")
FilteredResponseTTL: testFltRespTTL, wantIPv6 := netip.MustParseAddr("1234::cdef")
}
req := dnsservertest.NewReq("example.com", dns.TypeA, dns.ClassINET) testCases := []struct {
ttl := uint32(testFltRespTTL.Seconds()) 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 { testCases := []struct {
method func(req *dns.Msg) (resp *dns.Msg) method func(req *dns.Msg) (resp *dns.Msg)
@ -108,6 +207,7 @@ func TestConstructor_noAnswerMethods(t *testing.T) {
want: dns.RcodeSuccess, want: dns.RcodeSuccess,
}} }}
const wantTTL = testFltRespTTLSec
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
resp := tc.method(req) resp := tc.method(req)
@ -117,18 +217,15 @@ func TestConstructor_noAnswerMethods(t *testing.T) {
assert.Empty(t, resp.Answer) assert.Empty(t, resp.Answer)
assert.Equal(t, tc.want, dnsmsg.RCode(resp.Rcode)) assert.Equal(t, tc.want, dnsmsg.RCode(resp.Rcode))
ns := resp.Ns[0] nsTTL := resp.Ns[0].Header().Ttl
assert.Equal(t, ttl, ns.Header().Ttl) assert.Equal(t, wantTTL, nsTTL)
}) })
} }
} }
func TestConstructor_NewTXTRespMsg(t *testing.T) { func TestConstructor_NewTXTRespMsg(t *testing.T) {
mc := dnsmsg.Constructor{ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
FilteredResponseTTL: testFltRespTTL, req := dnsservertest.NewReq(testFQDN, dns.TypeTXT, dns.ClassINET)
}
req := dnsservertest.NewReq("example.com.", dns.TypeTXT, dns.ClassINET)
tooLong := strings.Repeat("1", dnsmsg.MaxTXTStringLen+1) tooLong := strings.Repeat("1", dnsmsg.MaxTXTStringLen+1)
testCases := []struct { testCases := []struct {
@ -157,6 +254,7 @@ func TestConstructor_NewTXTRespMsg(t *testing.T) {
strs: []string{tooLong}, strs: []string{tooLong},
}} }}
const wantTTL = testFltRespTTLSec
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
resp, err := mc.NewTXTRespMsg(req, tc.strs...) resp, err := mc.NewTXTRespMsg(req, tc.strs...)
@ -173,20 +271,17 @@ func TestConstructor_NewTXTRespMsg(t *testing.T) {
require.Len(t, resp.Answer, 1) require.Len(t, resp.Answer, 1)
ans := resp.Answer[0] ans := resp.Answer[0]
ttl := uint32(testFltRespTTL.Seconds()) ansTTL := ans.Header().Ttl
assert.Equal(t, ttl, 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) assert.Equal(t, tc.strs, txt.Txt)
}) })
} }
} }
func TestConstructor_AppendDebugExtra(t *testing.T) { func TestConstructor_AppendDebugExtra(t *testing.T) {
mc := dnsmsg.Constructor{ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
FilteredResponseTTL: testFltRespTTL,
}
shortText := "This is a short test text" shortText := "This is a short test text"
longText := strings.Repeat("a", 2*dnsmsg.MaxTXTStringLen) longText := strings.Repeat("a", 2*dnsmsg.MaxTXTStringLen)
@ -200,14 +295,14 @@ func TestConstructor_AppendDebugExtra(t *testing.T) {
name: "short_text", name: "short_text",
text: shortText, text: shortText,
qt: dns.TypeTXT, qt: dns.TypeTXT,
wantExtra: newTXTExtra(uint32(mc.FilteredResponseTTL.Seconds()), shortText), wantExtra: newTXTExtra(testFltRespTTLSec, shortText),
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "long_text", name: "long_text",
text: longText, text: longText,
qt: dns.TypeTXT, qt: dns.TypeTXT,
wantExtra: newTXTExtra( wantExtra: newTXTExtra(
uint32(mc.FilteredResponseTTL.Seconds()), testFltRespTTLSec,
longText[:dnsmsg.MaxTXTStringLen], longText[:dnsmsg.MaxTXTStringLen],
longText[dnsmsg.MaxTXTStringLen:], longText[dnsmsg.MaxTXTStringLen:],
), ),
@ -222,11 +317,11 @@ func TestConstructor_AppendDebugExtra(t *testing.T) {
name: "empty_text", name: "empty_text",
text: "", text: "",
qt: dns.TypeTXT, qt: dns.TypeTXT,
wantExtra: newTXTExtra(uint32(mc.FilteredResponseTTL.Seconds()), ""), wantExtra: newTXTExtra(testFltRespTTLSec, ""),
wantErrMsg: "", wantErrMsg: "",
}} }}
const fqdn = "example.com." const fqdn = testFQDN
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

View File

@ -19,8 +19,17 @@ func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m) testutil.DiscardLogOutput(m)
} }
// testFltRespTTL is the common filtered response TTL. // Common filtered response TTL constants.
const testFltRespTTL = 10 * time.Second const (
testFltRespTTL = 10 * time.Second
testFltRespTTLSec = uint32(testFltRespTTL / time.Second)
)
// Common domain names for tests.
const (
testDomain = "test.example"
testFQDN = testDomain + "."
)
// Common test constants. // Common test constants.
const ( 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)) 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.SetEdns0(dnsmsg.DefaultEDNSUDPSize, true)
msg.Extra = append(msg.Extra, dnsservertest.NewECSExtra( msg.Extra = append(msg.Extra, dnsservertest.NewECSExtra(
ip.AsSlice(), ip.AsSlice(),
@ -70,7 +79,7 @@ func TestClone(t *testing.T) {
}, },
name: "empty_slice_ans", name: "empty_slice_ans",
}, { }, {
msg: dnsservertest.NewReq("example.com.", dns.TypeA, dns.ClassINET), msg: dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET),
name: "a", name: "a",
}} }}
@ -86,7 +95,7 @@ func TestECSFromMsg(t *testing.T) {
ipv4Net := netip.MustParsePrefix("1.2.3.0/24") ipv4Net := netip.MustParsePrefix("1.2.3.0/24")
ipv6Net := netip.MustParsePrefix("2001:0:0102:0304::/64") 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 { testCases := []struct {
msg *dns.Msg msg *dns.Msg

View File

@ -237,7 +237,7 @@ func (c *Constructor) NewDDRTemplate(
// Keep the name empty for the client of the API to fill it. // Keep the name empty for the client of the API to fill it.
Name: "", Name: "",
Rrtype: dns.TypeSVCB, Rrtype: dns.TypeSVCB,
Ttl: uint32(c.FilteredResponseTTL.Seconds()), Ttl: uint32(c.fltRespTTL.Seconds()),
Class: dns.ClassINET, Class: dns.ClassINET,
}, },
Priority: prio, Priority: prio,

View File

@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/urlfilter/rules" "github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -24,10 +25,7 @@ var (
func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) { func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
// Preconditions. // Preconditions.
mc := &dnsmsg.Constructor{ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
FilteredResponseTTL: testFltRespTTL,
}
req := &dns.Msg{ req := &dns.Msg{
Question: []dns.Question{{ Question: []dns.Question{{
Name: "abcd", Name: "abcd",
@ -36,14 +34,13 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
// Constants and helper values. // Constants and helper values.
const host = "example.com"
const prio = 32 const prio = 32
// Helper functions. // Helper functions.
dnssvcb := func(key, value string) (svcb *rules.DNSSVCB) { dnssvcb := func(key, value string) (svcb *rules.DNSSVCB) {
svcb = &rules.DNSSVCB{ svcb = &rules.DNSSVCB{
Target: host, Target: testFQDN,
Priority: prio, Priority: prio,
} }
@ -63,11 +60,11 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: req.Question[0].Name, Name: req.Question[0].Name,
Rrtype: dns.TypeSVCB, Rrtype: dns.TypeSVCB,
Ttl: uint32(mc.FilteredResponseTTL.Seconds()), Ttl: testFltRespTTLSec,
Class: dns.ClassINET, Class: dns.ClassINET,
}, },
Priority: prio, Priority: prio,
Target: dns.Fqdn(host), Target: testFQDN,
} }
if kv != nil { if kv != nil {
@ -92,8 +89,8 @@ func TestConstructor_NewAnswerHTTPS_andSVCB(t *testing.T) {
want: wantsvcb(nil), want: wantsvcb(nil),
name: "invalid", name: "invalid",
}, { }, {
svcb: dnssvcb("alpn", "h3"), svcb: dnssvcb("alpn", http3.NextProtoH3),
want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{"h3"}}), want: wantsvcb(&dns.SVCBAlpn{Alpn: []string{http3.NextProtoH3}}),
name: "alpn", name: "alpn",
}, { }, {
svcb: dnssvcb("dohpath", "/some/url/path"), svcb: dnssvcb("dohpath", "/some/url/path"),
@ -170,9 +167,7 @@ func TestConstructor_NewDDR(t *testing.T) {
dohPath = "/dns-query" dohPath = "/dns-query"
) )
mc := &dnsmsg.Constructor{ mc := dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, testFltRespTTL)
FilteredResponseTTL: testFltRespTTL,
}
testCases := []struct { testCases := []struct {
name string name string
@ -248,7 +243,7 @@ func TestConstructor_NewDDR(t *testing.T) {
assert.Equal(t, targetFQDN, svcb.Target) assert.Equal(t, targetFQDN, svcb.Target)
assert.Equal(t, prio, svcb.Priority) 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) assert.ElementsMatch(t, tc.wantVals, svcb.Value)
}) })
} }

View File

@ -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 // 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 // the terms of the GNU Affero General Public License as published by the Free

View File

@ -103,6 +103,9 @@ const (
// RRSection is the slice of resource records to be appended to a new message // RRSection is the slice of resource records to be appended to a new message
// created by NewReq and NewResp. // created by NewReq and NewResp.
//
// TODO(e.burkov): Use separate types for different sections of DNS message
// instead of constants.
type RRSection struct { type RRSection struct {
RRs []dns.RR RRs []dns.RR
Sec MsgSection 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) { func NewA(name string, ttl uint32, a net.IP) (rr dns.RR) {
return &dns.A{ return &dns.A{
Hdr: dns.RR_Header{ 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. // NewSOA constructs the new resource record of type SOA.
func NewSOA(name string, ttl uint32, ns, mbox string) (rr dns.RR) { func NewSOA(name string, ttl uint32, ns, mbox string) (rr dns.RR) {
return &dns.SOA{ return &dns.SOA{

View File

@ -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)
}

View File

@ -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 // 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 // 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 in the server configuration. For instance, you can use
[prometheus.ServerMetricsListener] to make it record prometheus metrics. [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 [dnscrypt module]: https://github.com/ameshkov/dnscrypt
[module documentation]: https://github.com/ameshkov/dnscrypt#server [module documentation]: https://github.com/ameshkov/dnscrypt#server
*/ */

View File

@ -3,6 +3,7 @@ package dnsserver
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"github.com/AdguardTeam/golibs/errors" "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 // case. It seems like all places where this function is used should detect
// precise error conditions for exiting a loop instead of this. // precise error conditions for exiting a loop instead of this.
func isNonCriticalNetError(err error) (ok bool) { func isNonCriticalNetError(err error) (ok bool) {
if errors.Is(os.ErrDeadlineExceeded, err) {
return true
}
var netErr net.Error var netErr net.Error
//lint:ignore SA1019 See TODO in the function documentation. return errors.As(err, &netErr) && netErr.Timeout()
return errors.As(err, &netErr) && netErr.Temporary()
} }

View File

@ -151,22 +151,15 @@ var _ io.Closer = &Handler{}
// Close implements the [io.Closer] interface for *Handler. // Close implements the [io.Closer] interface for *Handler.
func (h *Handler) Close() (err error) { func (h *Handler) Close() (err error) {
var errs []error errs := make([]error, len(h.fallbacks)+1)
errs[0] = h.upstream.Close()
cErr := h.upstream.Close() for i, f := range h.fallbacks {
if cErr != nil { errs[i+1] = f.Close()
errs = append(errs, cErr)
} }
for _, f := range h.fallbacks { err = errors.Join(errs...)
cErr = f.Close() if err != nil {
if cErr != nil { return fmt.Errorf("closing forward handler: %w", err)
errs = append(errs, cErr)
}
}
if len(errs) > 0 {
return errors.List("closing forward handler", errs...)
} }
return nil return nil

View File

@ -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. // Close implements the io.Closer interface for *UpstreamPlain.
func (u *UpstreamPlain) Close() (err error) { func (u *UpstreamPlain) Close() (err error) {
var errs []error udpErr := u.connsPoolUDP.Close()
tcpErr := u.connsPoolTCP.Close()
err = u.connsPoolUDP.Close() return errors.Annotate(errors.Join(udpErr, tcpErr), "closing upstream: %w")
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
} }
// String implements the fmt.Stringer interface for *UpstreamPlain. // String implements the fmt.Stringer interface for *UpstreamPlain.

View File

@ -1,49 +1,48 @@
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
go 1.19 go 1.20
require ( 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/dnscrypt/v2 v2.2.5
github.com/ameshkov/dnsstamps v1.0.3 github.com/ameshkov/dnsstamps v1.0.3
github.com/bluele/gcache v0.0.2 github.com/bluele/gcache v0.0.2
github.com/lucas-clemente/quic-go v0.31.0 github.com/miekg/dns v1.1.52
github.com/miekg/dns v1.1.50
github.com/panjf2000/ants/v2 v2.7.1 github.com/panjf2000/ants/v2 v2.7.1
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0
github.com/stretchr/testify v1.8.1 github.com/quic-go/quic-go v0.33.0
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 github.com/stretchr/testify v1.8.2
golang.org/x/net v0.4.0 golang.org/x/exp v0.0.0-20230307190834-24139beb5833
golang.org/x/sys v0.3.0 golang.org/x/net v0.8.0
golang.org/x/sys v0.6.0
) )
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/beorn7/perks v1.0.1 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // 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/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/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/onsi/ginkgo/v2 v2.9.0 // indirect
github.com/onsi/ginkgo/v2 v2.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.41.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/crypto v0.3.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
golang.org/x/mod v0.7.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
golang.org/x/text v0.5.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
golang.org/x/tools v0.3.0 // 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 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -1,556 +1,128 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AdguardTeam/golibs v0.12.1 h1:bJfFzCnUCl+QsP6prUltM2Sjt0fTiDBPlxuAwfKP3g8=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AdguardTeam/golibs v0.12.1/go.mod h1:rIglKDHdLvFT1UbhumBLHO9S4cvWS9MEyT1njommI/Y=
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/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= 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 h1:Ju1gQeez+6XLtk/b/k3RoJ2t+Ls+BSItLTZjZeedneY=
github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM= github.com/ameshkov/dnscrypt/v2 v2.2.5/go.mod h1:Cu5GgMvCR10BeXgACiGDwXyOpfMktsSIidml1XBp6uM=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-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 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
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/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M= github.com/panjf2000/ants/v2 v2.7.1 h1:qBy5lfSdbxvrR0yUnZfaEDjf0FlCw4ufsbcsxmE7r+M=
github.com/panjf2000/ants/v2 v2.7.1/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8= github.com/panjf2000/ants/v2 v2.7.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 h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 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.41.0 h1:npo01n6vUlRViIj5fgwiK8vlNIh8bnoxqh3gypKsyAw=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.41.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.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.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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-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.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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-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-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-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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-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-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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.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 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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=

View File

@ -3,6 +3,7 @@
package netext package netext
import ( import (
"fmt"
"net" "net"
"syscall" "syscall"
@ -37,7 +38,7 @@ func setIPOpts(c net.PacketConn) (err error) {
err6 := ipv6.NewPacketConn(c).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true) err6 := ipv6.NewPacketConn(c).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true)
err4 := ipv4.NewPacketConn(c).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true) err4 := ipv4.NewPacketConn(c).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true)
if err4 != nil && err6 != nil { 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 return nil

View File

@ -3,6 +3,7 @@ package pool
import ( import (
"context" "context"
"fmt"
"net" "net"
"sync" "sync"
"time" "time"
@ -118,14 +119,11 @@ func (p *Pool) Close() (err error) {
errs = append(errs, err) errs = append(errs, err)
} }
} }
// This marks the pool as closed. // This marks the pool as closed.
p.connsChan = nil p.connsChan = nil
if len(errs) > 0 { return errors.Annotate(errors.Join(errs...), "closing pool: %w")
return errors.List("errors when closing the pool", errs...)
}
return nil
} }
// closeConn is used when the pool is closed. In this case we attempt to close // 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) { func (p *Pool) closeConn(conn *Conn) (err error) {
err = conn.Close() err = conn.Close()
if err != nil { if err != nil {
return errors.List( return errors.WithDeferred(fmt.Errorf("closing pool connection: %w", err), ErrClosed)
"error while closing a pool connection",
err,
ErrClosed,
)
} }
return ErrClosed return ErrClosed

View File

@ -16,8 +16,8 @@ import (
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/ameshkov/dnscrypt/v2" "github.com/ameshkov/dnscrypt/v2"
"github.com/ameshkov/dnsstamps" "github.com/ameshkov/dnsstamps"
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@ -18,9 +18,9 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2" "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 // 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 // *tls.Config if no NextProto is specified there and DoH3 is supposed to be
// used. // 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 // ConfigHTTPS is a struct that needs to be passed to NewServerHTTPS to
// initialize a new ServerHTTPS instance. You can choose whether HTTP/3 is // 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) defer s.handlePanicAndExit(ctx)
u := &url.URL{ u := &url.URL{
Scheme: "h3", Scheme: http3.NextProtoH3,
Host: s.addr, Host: s.addr,
} }
log.Info("[%s]: Start listening to %s", s.name, u) 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) rAddr := h.remoteAddr(r)
lAddr := h.localAddr lAddr := h.localAddr
rw := NewNonWriterResponseWriter(lAddr, rAddr) rw := NewNonWriterResponseWriter(lAddr, rAddr)
ctx = httpContextWithClientInfo(ctx, r)
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
}
// Serve the query // Serve the query
written := h.srv.serveDNS(ctx, m, rw) written := h.srv.serveDNS(ctx, m, rw)
@ -524,36 +516,19 @@ func (s *ServerHTTPS) listenQUIC(ctx context.Context) (err error) {
return nil return nil
} }
// httpContextWithClientInfo adds client info to the context. ctx is never nil, // httpContextWithClientInfo adds client info to the context.
// even when there is an error. func httpContextWithClientInfo(parent context.Context, r *http.Request) (ctx context.Context) {
func httpContextWithClientInfo(
parent context.Context,
r *http.Request,
) (ctx context.Context, err error) {
ctx = parent ctx = parent
ci := ClientInfo{ ci := ClientInfo{
URL: netutil.CloneURL(r.URL), URL: netutil.CloneURL(r.URL),
} }
// Due to the quic-go bug we should use Host instead of r.TLS. See if r.TLS != nil {
// 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 {
ci.TLSServerName = strings.ToLower(r.TLS.ServerName) ci.TLSServerName = strings.ToLower(r.TLS.ServerName)
} }
return ContextWithClientInfo(ctx, ci), nil return ContextWithClientInfo(ctx, ci)
} }
// httpRequestToMsg reads the DNS message from http.Request. // httpRequestToMsg reads the DNS message from http.Request.

View File

@ -19,9 +19,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -114,14 +114,9 @@ func TestServerHTTPS_integration_serveRequests(t *testing.T) {
return srv.Shutdown(context.Background()) return srv.Shutdown(context.Background())
}) })
// Create a test message // Create a test message.
req := new(dns.Msg) req := dnsservertest.NewReq("example.org.", dns.TypeA, dns.ClassINET)
req.Id = dns.Id()
req.RecursionDesired = true req.RecursionDesired = true
name := "example.org."
req.Question = []dns.Question{
{Name: name, Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
var resp *dns.Msg var resp *dns.Msg
addr := srv.LocalTCPAddr() addr := srv.LocalTCPAddr()
@ -232,7 +227,7 @@ func TestDNSMsgToJSONMsg(t *testing.T) {
Target: "example.com", Target: "example.com",
Value: []dns.SVCBKeyValue{ Value: []dns.SVCBKeyValue{
&dns.SVCBAlpn{ &dns.SVCBAlpn{
Alpn: []string{"h2", "h3"}, Alpn: []string{http2.NextProtoTLS, http3.NextProtoH3},
}, },
&dns.SVCBECHConfig{ &dns.SVCBECHConfig{
ECH: []byte{1, 2}, ECH: []byte{1, 2},
@ -343,6 +338,80 @@ func TestServerHTTPS_integration_ENDS0Padding(t *testing.T) {
require.NotEmpty(t, paddingOpt.Padding) 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( func mustDoHReq(
t testing.TB, t testing.TB,
httpsAddr net.Addr, httpsAddr net.Addr,
@ -394,7 +463,7 @@ func mustDoHReq(
func createDoHClient(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) { func createDoHClient(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.Client, err error) {
if dnsserver.NetworkFromAddr(httpsAddr) == dnsserver.NetworkUDP { if dnsserver.NetworkFromAddr(httpsAddr) == dnsserver.NetworkUDP {
return createDoH3Client(httpsAddr, tlsConfig) return createDoH3Client(httpsAddr, tlsConfig, nil)
} }
return createDoH2Client(httpsAddr, tlsConfig) return createDoH2Client(httpsAddr, tlsConfig)
@ -431,9 +500,13 @@ func createDoH2Client(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.C
}, nil }, 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 = tlsConfig.Clone()
tlsConfig.NextProtos = []string{"h3"} tlsConfig.NextProtos = []string{http3.NextProtoH3}
transport := &http3.RoundTripper{ transport := &http3.RoundTripper{
DisableCompression: true, DisableCompression: true,
@ -445,6 +518,7 @@ func createDoH3Client(httpsAddr net.Addr, tlsConfig *tls.Config) (client *http.C
) (c quic.EarlyConnection, e error) { ) (c quic.EarlyConnection, e error) {
return quic.DialAddrEarlyContext(ctx, httpsAddr.String(), tlsCfg, cfg) return quic.DialAddrEarlyContext(ctx, httpsAddr.String(), tlsCfg, cfg)
}, },
QuicConfig: quicConfig,
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
} }

View File

@ -16,9 +16,9 @@ import (
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/bluele/gcache" "github.com/bluele/gcache"
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"github.com/quic-go/quic-go"
) )
const ( const (
@ -438,10 +438,11 @@ func (s *ServerQUIC) readQUICMsg(
// The client MUST send the DNS query over the selected stream, and MUST // The client MUST send the DNS query over the selected stream, and MUST
// indicate through the STREAM FIN mechanism that no further data will // indicate through the STREAM FIN mechanism that no further data will
// be sent on that stream. // be sent on that stream.
_ = stream.SetReadDeadline(time.Now().Add(DefaultReadTimeout)) _ = stream.SetReadDeadline(time.Now().Add(DefaultReadTimeout))
// Read the stream data until io.EOF, i.e. until FIN is received.
var n int 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 // 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 // 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 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 // listenQUIC creates the UDP listener for the ServerQUIC.addr and also starts
// the QUIC listener. // the QUIC listener.
func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) { func (s *ServerQUIC) listenQUIC(ctx context.Context) (err error) {
@ -630,9 +657,14 @@ func newServerQUICConfig(metrics MetricsListener) (conf *quic.Config) {
return &quic.Config{ return &quic.Config{
MaxIdleTimeout: maxQUICIdleTimeout, MaxIdleTimeout: maxQUICIdleTimeout,
RequireAddressValidation: v.requiresValidation,
MaxIncomingStreams: math.MaxUint16, MaxIncomingStreams: math.MaxUint16,
MaxIncomingUniStreams: 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
},
} }
} }

View File

@ -2,18 +2,21 @@ package dnsserver_test
import ( import (
"context" "context"
"crypto/tls"
"encoding/binary" "encoding/binary"
"io" "io"
"net"
"sync" "sync"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -34,9 +37,9 @@ func TestServerQUIC_integration_query(t *testing.T) {
conn, err := quic.DialAddr(addr.String(), tlsConfig, nil) conn, err := quic.DialAddr(addr.String(), tlsConfig, nil)
require.NoError(t, err) require.NoError(t, err)
defer func(conn quic.Connection, code quic.ApplicationErrorCode, s string) { defer testutil.CleanupAndRequireSuccess(t, func() (err error) {
_ = conn.CloseWithError(code, s) return conn.CloseWithError(0, "")
}(conn, 0, "") })
// Send multiple queries to the DNS server in parallel // Send multiple queries to the DNS server in parallel
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
@ -102,6 +105,111 @@ func TestServerQUIC_integration_ENDS0Padding(t *testing.T) {
require.NotEmpty(t, paddingOpt.Padding) 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. // sendQUICMessage sends a test QUIC message.
func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Msg, error) { func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Msg, error) {
// Open stream. // Open stream.
@ -126,14 +234,14 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
copy(buf[2:], data) copy(buf[2:], data)
} }
// Send the DNS query to the stream. err = writeQUICStream(buf, stream)
_, err = stream.Write(buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Close closes the write-direction of the stream // Closes the write-direction of the stream and sends a STREAM FIN packet.
// and sends a STREAM FIN packet. // A DoQ client MUST send a FIN packet to indicate that the query is
// finished.
_ = stream.Close() _ = stream.Close()
// Now read the response. // Now read the response.
@ -161,3 +269,30 @@ func sendQUICMessage(conn quic.Connection, req *dns.Msg, doqDraft bool) (*dns.Ms
return reply, nil 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
}

View File

@ -3,10 +3,9 @@ package dnssvc
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "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"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/filter" "github.com/AdguardTeam/AdGuardDNS/internal/filter"
@ -21,11 +20,13 @@ import (
func newTXTExtra(strs [][2]string) (extra []dns.RR) { func newTXTExtra(strs [][2]string) (extra []dns.RR) {
for _, v := range strs { for _, v := range strs {
extra = append(extra, &dns.TXT{ extra = append(extra, &dns.TXT{
// TODO(a.garipov): Consider exporting dnsmsg.Constructor.newHdr and
// using it here.
Hdr: dns.RR_Header{ Hdr: dns.RR_Header{
Name: v[0], Name: v[0],
Rrtype: dns.TypeTXT, Rrtype: dns.TypeTXT,
Class: dns.ClassCHAOS, Class: dns.ClassCHAOS,
Ttl: 1, Ttl: uint32(agdtest.FilteredResponseTTL.Seconds()),
}, },
Txt: []string{v[1]}, Txt: []string{v[1]},
}) })
@ -35,7 +36,9 @@ func newTXTExtra(strs [][2]string) (extra []dns.RR) {
} }
func TestService_writeDebugResponse(t *testing.T) { func TestService_writeDebugResponse(t *testing.T) {
svc := &Service{messages: &dnsmsg.Constructor{FilteredResponseTTL: time.Second}} svc := &Service{
messages: agdtest.NewConstructor(),
}
const ( const (
fltListID1 agd.FilterListID = "fl1" fltListID1 agd.FilterListID = "fl1"

View File

@ -56,7 +56,7 @@ func TestService_Wrap_deviceID(t *testing.T) {
cliSrvName: "!!!.dns.example.com", cliSrvName: "!!!.dns.example.com",
wantDeviceID: "", wantDeviceID: "",
wantErrMsg: `tls server name device id check: bad device id "!!!": ` + wantErrMsg: `tls server name device id check: bad device id "!!!": ` +
`bad domain name label rune '!'`, `bad hostname label rune '!'`,
wildcards: []string{"*.dns.example.com"}, wildcards: []string{"*.dns.example.com"},
proto: agd.ProtoDoT, proto: agd.ProtoDoT,
}, { }, {
@ -159,7 +159,7 @@ func TestService_Wrap_deviceIDHTTPS(t *testing.T) {
path: "/dns-query/!!!", path: "/dns-query/!!!",
wantDeviceID: "", wantDeviceID: "",
wantErrMsg: `http url device id check: bad device id "!!!": ` + wantErrMsg: `http url device id check: bad device id "!!!": ` +
`bad domain name label rune '!'`, `bad hostname label rune '!'`,
}} }}
const proto = agd.ProtoDoH const proto = agd.ProtoDoH

View File

@ -8,7 +8,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat" "github.com/AdguardTeam/AdGuardDNS/internal/billstat"
@ -16,6 +15,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb" "github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg" "github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "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/prometheus"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/filter" "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 set, is used instead of the package-level function
// NewListener when creating a DNS listener. // NewListener when creating a DNS listener.
// //
// TODO(a.garipov, ameshkov): Consider creating a single dnsserver.Dialer // TODO(a.garipov): The handler and service logic should really not be
// interface/builder function to use instead of the many // internwined in this way. See AGDNS-1327.
// dnsserver.NewServerFoo constructors.
NewListener NewListenerFunc NewListener NewListenerFunc
// Handler is used as the main DNS handler instead of a simple forwarder. // 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 { for _, s := range g.servers {
err = shutdownListeners(ctx, s.listeners) err = shutdownListeners(ctx, s.listeners)
if err != nil { if err != nil {
err = fmt.Errorf("group %q: server %q: %w", g.name, s.name, err) errs = append(errs, fmt.Errorf("group %q: server %q: %w", g.name, s.name, err))
errs = append(errs, err)
} }
} }
} }
if len(errs) > 0 { err = errors.Join(errs...)
return errors.List("shutting down dns service", errs...) if err != nil {
return fmt.Errorf("shutting down dns service: %w", err)
} }
return nil return nil
@ -328,10 +327,11 @@ type Listener = dnsserver.Server
type NewListenerFunc func( type NewListenerFunc func(
s *agd.Server, s *agd.Server,
name string, name string,
addr netip.AddrPort, addr string,
h dnsserver.Handler, h dnsserver.Handler,
nonDNS http.Handler, nonDNS http.Handler,
errColl agd.ErrorCollector, errColl agd.ErrorCollector,
lc netext.ListenConfig,
) (l Listener, err error) ) (l Listener, err error)
// listener is a Listener along with some of its associated data. // 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. // listenerName returns a standard name for a listener.
func listenerName(srvName agd.ServerName, addr netip.AddrPort, p agd.Protocol) (name string) { func listenerName(srvName agd.ServerName, addr string, proto agd.Protocol) (name string) {
return fmt.Sprintf("%s/%s/%s", srvName, p, addr) return fmt.Sprintf("%s/%s/%s", srvName, proto, addr)
} }
// NewListener returns a new Listener. It is the default DNS listener // 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( func NewListener(
s *agd.Server, s *agd.Server,
name string, name string,
addr netip.AddrPort, addr string,
h dnsserver.Handler, h dnsserver.Handler,
nonDNS http.Handler, nonDNS http.Handler,
errColl agd.ErrorCollector, errColl agd.ErrorCollector,
lc netext.ListenConfig,
) (l Listener, err error) { ) (l Listener, err error) {
defer func() { err = errors.Annotate(err, "listener %q: %w", name) }() defer func() { err = errors.Annotate(err, "listener %q: %w", name) }()
dcConf := s.DNSCrypt dcConf := s.DNSCrypt
addrStr := addr.String()
metricsListener := &errCollMetricsListener{ metricsListener := &errCollMetricsListener{
errColl: errColl, errColl: errColl,
@ -367,11 +367,13 @@ func NewListener(
} }
confBase := dnsserver.ConfigBase{ confBase := dnsserver.ConfigBase{
Name: name, Name: name,
Addr: addrStr, Addr: addr,
Handler: h, Network: dnsserver.NetworkAny,
BaseContext: ctxWithReqID, Handler: h,
Metrics: metricsListener, Metrics: metricsListener,
BaseContext: ctxWithReqID,
ListenConfig: lc,
} }
switch p := s.Protocol; p { switch p := s.Protocol; p {
@ -464,12 +466,17 @@ func newServers(
imw, imw,
) )
listeners := make([]*listener, 0, len(s.BindAddresses)) listeners := make([]*listener, 0, len(s.BindData))
for _, addr := range s.BindAddresses { for _, bindData := range s.BindData {
addr := bindData.Address
if addr == "" {
addr = bindData.AddrPort.String()
}
name := listenerName(s.Name, addr, s.Protocol) name := listenerName(s.Name, addr, s.Protocol)
var l Listener 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 { if err != nil {
return nil, fmt.Errorf("server %q: %w", s.Name, err) return nil, fmt.Errorf("server %q: %w", s.Name, err)
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward" "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc" "github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -105,10 +106,11 @@ func newTestListenerFunc(tl *testListener) (f dnssvc.NewListenerFunc) {
return func( return func(
_ *agd.Server, _ *agd.Server,
_ string, _ string,
_ netip.AddrPort, _ string,
_ dnsserver.Handler, _ dnsserver.Handler,
_ http.Handler, _ http.Handler,
_ agd.ErrorCollector, _ agd.ErrorCollector,
_ netext.ListenConfig,
) (l dnssvc.Listener, err error) { ) (l dnssvc.Listener, err error) {
return tl, nil return tl, nil
} }
@ -157,9 +159,11 @@ func TestService_Start(t *testing.T) {
} }
srv := &agd.Server{ srv := &agd.Server{
Name: "test_server", Name: "test_server",
Protocol: agd.ProtoDNS, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
}},
Protocol: agd.ProtoDNS,
} }
c := &dnssvc.Config{ c := &dnssvc.Config{
@ -195,35 +199,45 @@ func TestService_Start(t *testing.T) {
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
srvs := []*agd.Server{{ srvs := []*agd.Server{{
DNSCrypt: nil, DNSCrypt: nil,
TLS: nil, TLS: nil,
Name: "test_server_dns", Name: "test_server_dns",
Protocol: agd.ProtoDNS, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:53"),
}},
Protocol: agd.ProtoDNS,
}, { }, {
DNSCrypt: &agd.DNSCryptConfig{}, DNSCrypt: &agd.DNSCryptConfig{},
TLS: nil, TLS: nil,
Name: "test_server_dnscrypt_tcp", Name: "test_server_dnscrypt_tcp",
Protocol: agd.ProtoDNSCrypt, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:8853")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:8853"),
}},
Protocol: agd.ProtoDNSCrypt,
}, { }, {
DNSCrypt: nil, DNSCrypt: nil,
TLS: &tls.Config{}, TLS: &tls.Config{},
Name: "test_server_doh", Name: "test_server_doh",
Protocol: agd.ProtoDoH, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:443"),
}},
Protocol: agd.ProtoDoH,
}, { }, {
DNSCrypt: nil, DNSCrypt: nil,
TLS: &tls.Config{}, TLS: &tls.Config{},
Name: "test_server_doq", Name: "test_server_doq",
Protocol: agd.ProtoDoQ, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:853")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
}},
Protocol: agd.ProtoDoQ,
}, { }, {
DNSCrypt: nil, DNSCrypt: nil,
TLS: &tls.Config{}, TLS: &tls.Config{},
Name: "test_server_dot", Name: "test_server_dot",
Protocol: agd.ProtoDoT, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:853")}, AddrPort: netip.MustParseAddrPort("127.0.0.1:853"),
}},
Protocol: agd.ProtoDoT,
}} }}
c := &dnssvc.Config{ c := &dnssvc.Config{

View File

@ -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) optlog.Debug3("init mw: found profile %s and device %s by %s", prof.ID, dev.ID, byWhat)
ri.Device, ri.Profile = dev, prof ri.Device, ri.Profile = dev, prof
ri.Messages = &dnsmsg.Constructor{ ri.Messages = dnsmsg.NewConstructor(prof.BlockingMode.Mode, prof.FilteredResponseTTL)
FilteredResponseTTL: prof.FilteredResponseTTL,
}
} }
return nil return nil

View File

@ -6,7 +6,6 @@ import (
"net" "net"
"net/netip" "net/netip"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
@ -45,23 +44,31 @@ func TestInitMw_ServeDNS_ddr(t *testing.T) {
srvs := map[agd.ServerName]*agd.Server{ srvs := map[agd.ServerName]*agd.Server{
"dot": { "dot": {
TLS: &tls.Config{}, TLS: &tls.Config{},
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("1.2.3.4:12345")}, BindData: []*agd.ServerBindData{{
Protocol: agd.ProtoDoT, AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
}},
Protocol: agd.ProtoDoT,
}, },
"doh": { "doh": {
TLS: &tls.Config{}, TLS: &tls.Config{},
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("5.6.7.8:54321")}, BindData: []*agd.ServerBindData{{
Protocol: agd.ProtoDoH, AddrPort: netip.MustParseAddrPort("5.6.7.8:54321"),
}},
Protocol: agd.ProtoDoH,
}, },
"dns": { "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, Protocol: agd.ProtoDNS,
LinkedIPEnabled: true, LinkedIPEnabled: true,
}, },
"dns_nolink": { "dns_nolink": {
BindAddresses: []netip.AddrPort{netip.MustParseAddrPort("2.4.6.8:53")}, BindData: []*agd.ServerBindData{{
Protocol: agd.ProtoDNS, 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 var dev *agd.Device
mw := &initMw{ mw := &initMw{
messages: &dnsmsg.Constructor{ messages: agdtest.NewConstructor(),
FilteredResponseTTL: 10 * time.Second, fltGrp: &agd.FilteringGroup{},
}, srvGrp: srvGrp,
fltGrp: &agd.FilteringGroup{},
srvGrp: srvGrp,
db: &agdtest.ProfileDB{ db: &agdtest.ProfileDB{
OnProfileByDeviceID: func( OnProfileByDeviceID: func(
_ context.Context, _ context.Context,
@ -437,9 +442,7 @@ func TestInitMw_ServeDNS_specialDomain(t *testing.T) {
} }
mw := &initMw{ mw := &initMw{
messages: &dnsmsg.Constructor{ messages: agdtest.NewConstructor(),
FilteredResponseTTL: 10 * time.Second,
},
fltGrp: &agd.FilteringGroup{ fltGrp: &agd.FilteringGroup{
BlockPrivateRelay: tc.fltGrpBlocked, BlockPrivateRelay: tc.fltGrpBlocked,
BlockFirefoxCanary: tc.fltGrpBlocked, BlockFirefoxCanary: tc.fltGrpBlocked,
@ -492,19 +495,18 @@ func BenchmarkInitMw_Wrap(b *testing.B) {
}, },
Name: agd.ServerGroupName("test_server_group"), Name: agd.ServerGroupName("test_server_group"),
Servers: []*agd.Server{{ Servers: []*agd.Server{{
BindAddresses: []netip.AddrPort{ BindData: []*agd.ServerBindData{{
netip.MustParseAddrPort("1.2.3.4:12345"), AddrPort: netip.MustParseAddrPort("1.2.3.4:12345"),
netip.MustParseAddrPort("4.3.2.1:12345"), }, {
}, AddrPort: netip.MustParseAddrPort("4.3.2.1:12345"),
}},
Protocol: agd.ProtoDoT, Protocol: agd.ProtoDoT,
}}, }},
} }
messages := &dnsmsg.Constructor{ messages := agdtest.NewConstructor()
FilteredResponseTTL: 10 * time.Second,
}
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")} ipv6Hints := []netip.Addr{netip.MustParseAddr("2001::1234")}
srvGrp.DDR.DeviceTargets.Add(devIDTarget) srvGrp.DDR.DeviceTargets.Add(devIDTarget)

View File

@ -148,11 +148,13 @@ func TestService_Wrap_withClient(t *testing.T) {
srvAddr := netip.MustParseAddrPort("94.149.14.14:853") srvAddr := netip.MustParseAddrPort("94.149.14.14:853")
srvName := agd.ServerName("test_server_dns_tls") srvName := agd.ServerName("test_server_dns_tls")
srvs := []*agd.Server{{ srvs := []*agd.Server{{
DNSCrypt: nil, DNSCrypt: nil,
TLS: nil, TLS: nil,
Name: srvName, Name: srvName,
Protocol: agd.ProtoDoT, BindData: []*agd.ServerBindData{{
BindAddresses: []netip.AddrPort{srvAddr}, AddrPort: srvAddr,
}},
Protocol: agd.ProtoDoT,
}} }}
tl := newTestListener() tl := newTestListener()
@ -211,9 +213,7 @@ func TestService_Wrap_withClient(t *testing.T) {
fltGrpID := agd.FilteringGroupID("1234") fltGrpID := agd.FilteringGroupID("1234")
srvGrpName := agd.ServerGroupName("test_group") srvGrpName := agd.ServerGroupName("test_group")
c := &dnssvc.Config{ c := &dnssvc.Config{
Messages: &dnsmsg.Constructor{ Messages: agdtest.NewConstructor(),
FilteredResponseTTL: 10 * time.Second,
},
BillStat: &agdtest.BillStatRecorder{ BillStat: &agdtest.BillStatRecorder{
OnRecord: func( OnRecord: func(
_ context.Context, _ context.Context,

View File

@ -114,11 +114,9 @@ func TestPreServiceMwHandler_ServeDNS(t *testing.T) {
} }
mw := &preServiceMw{ mw := &preServiceMw{
messages: &dnsmsg.Constructor{ messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, ttl*time.Second),
FilteredResponseTTL: ttl * time.Second, filter: srv,
}, checker: dnsCk,
filter: srv,
checker: dnsCk,
} }
handler := dnsservertest.DefaultHandler() handler := dnsservertest.DefaultHandler()
h := mw.Wrap(handler) h := mw.Wrap(handler)

View File

@ -174,15 +174,7 @@ func (svc *Service) responseData(
} }
if netIP != nil { if netIP != nil {
// Make sure to validate the IP address, because it's not guaranteed to var err error
// 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)
}
ip, err = netutil.IPToAddr(netIP, fam) ip, err = netutil.IPToAddr(netIP, fam)
if err != nil { if err != nil {
svc.reportf(ctx, "converting %s resp data: %w", rrType, err) svc.reportf(ctx, "converting %s resp data: %w", rrType, err)

View File

@ -85,9 +85,7 @@ func TestWriteFilteredResp(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ri := &agd.RequestInfo{ ri := &agd.RequestInfo{
Messages: &dnsmsg.Constructor{ Messages: dnsmsg.NewConstructor(&dnsmsg.BlockingModeNullIP{}, fltRespTTL*time.Second),
FilteredResponseTTL: fltRespTTL * time.Second,
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -20,23 +20,46 @@ import (
// cacheRequest contains data necessary to get a value from the cache. It is // cacheRequest contains data necessary to get a value from the cache. It is
// used to optimize goroutine stack usage. // used to optimize goroutine stack usage.
type cacheRequest struct { 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 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 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 // 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 // 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. // support ECS, isECSDependent is true. cr, cr.req, and cr.subnet must not be
func (mw *Middleware) get(req *dns.Msg, cr *cacheRequest) (resp *dns.Msg, found, hostHasECS bool) { // nil.
func (mw *Middleware) get(
req *dns.Msg,
cr *cacheRequest,
) (resp *dns.Msg, found, isECSDependent bool) {
key := mw.toCacheKey(cr, false) key := mw.toCacheKey(cr, false)
item, ok := itemFromCache(mw.cache, key, cr) item, ok := itemFromCache(mw.cache, key, cr)
if ok { if ok {
return fromCacheItem(item, req, cr.reqDO), true, false 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) key = mw.toCacheKey(cr, true)
item, ok = itemFromCache(mw.ecsCache, key, cr) item, ok = itemFromCache(mw.ecsCache, key, cr)
if ok { if ok {
@ -82,9 +105,11 @@ var hashSeed = maphash.MakeSeed()
// toCacheKey returns the appropriate cache key for msg. msg must have one // toCacheKey returns the appropriate cache key for msg. msg must have one
// question record. subnet must not be nil. // 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 // Use maphash explicitly instead of using a key structure to reduce
// allocations and optimize interface conversion up the stack. // allocations and optimize interface conversion up the stack.
//
// TODO(a.garipov, e.burkov): Consider just using struct as a key.
h := &maphash.Hash{} h := &maphash.Hash{}
h.SetSeed(hashSeed) h.SetSeed(hashSeed)
@ -102,9 +127,11 @@ func (mw *Middleware) toCacheKey(cr *cacheRequest, hostHasECS bool) (key uint64)
_, _ = h.Write(buf[:]) _, _ = h.Write(buf[:])
if hostHasECS { if respIsECSDependent {
_, _ = h.Write(addr.AsSlice()) _, _ = h.Write(addr.AsSlice())
_ = h.WriteByte(byte(cr.subnet.Bits())) _ = h.WriteByte(byte(cr.subnet.Bits()))
} else {
_ = h.WriteByte(mathutil.BoolToNumber[byte](cr.isECSDeclined))
} }
return h.Sum64() 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 // set saves resp to the cache if it's cacheable. If msg cannot be cached, it
// is ignored. // 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) ttl := findLowestTTL(resp)
if ttl == 0 || !isCacheable(resp) { if ttl == 0 || !isCacheable(resp) {
return return
} }
cache := mw.cache cache := mw.cache
if hostHasECS { if respIsECSDependent {
cache = mw.ecsCache cache = mw.ecsCache
} }
key := mw.toCacheKey(cr, hostHasECS) key := mw.toCacheKey(cr, respIsECSDependent)
item := toCacheItem(resp, cr.host) item := toCacheItem(resp, cr.host)
err := cache.SetWithExpire(key, item, time.Duration(ttl)*time.Second) err := cache.SetWithExpire(key, item, time.Duration(ttl)*time.Second)

View File

@ -5,7 +5,6 @@ package ecscache
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip"
"sync" "sync"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
@ -15,6 +14,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/metrics" "github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optlog" "github.com/AdguardTeam/AdGuardDNS/internal/optlog"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/bluele/gcache" "github.com/bluele/gcache"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -87,27 +87,30 @@ func writeCachedResponse(
resp *dns.Msg, resp *dns.Msg,
ecs *agd.ECS, ecs *agd.ECS,
ecsFam netutil.AddrFamily, ecsFam netutil.AddrFamily,
hostHasECS bool, respIsECSDependent bool,
) (err error) { ) (err error) {
// Increment the hits metrics here, since we already know if the domain name // 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 // supports ECS or not from the cache data. Increment the misses metrics in
// writeResponse, where this information is retrieved from the upstream // writeResponse, where this information is retrieved from the upstream
metrics.ECSCacheLookupTotalHits.Inc() metrics.ECSCacheLookupTotalHits.Inc()
if hostHasECS { if respIsECSDependent {
metrics.ECSCacheLookupHasSupportHits.Inc() 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 { } else {
metrics.ECSCacheLookupNoSupportHits.Inc() 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 // 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 // been filtered when setting the cache, and the AD bit was set when resp
// was being retrieved from the cache. // was being retrieved from the cache.
@ -123,21 +126,21 @@ func writeCachedResponse(
// the request information using either the contents of the EDNS Client Subnet // the request information using either the contents of the EDNS Client Subnet
// option or the real remote IP address. // option or the real remote IP address.
func ecsFamFromReq(ri *agd.RequestInfo) (ecsFam netutil.AddrFamily) { func ecsFamFromReq(ri *agd.RequestInfo) (ecsFam netutil.AddrFamily) {
if ecs := ri.ECS; ecs != nil { // Assume that families other than IPv4 and IPv6 have been filtered out
if ecs.Subnet.Addr().Is4() { // by dnsmsg.ECSFromMsg.
return netutil.AddrFamilyIPv4 var is4 func() (ok bool)
}
// Assume that families other than IPv4 and IPv6 have been filtered out if ecs := ri.ECS; ecs != nil {
// by dnsmsg.ECSFromMsg. is4 = ecs.Subnet.Addr().Is4
return netutil.AddrFamilyIPv6 } 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 if is4() {
// per RFC 7871.
//
// See https://datatracker.ietf.org/doc/html/rfc7871#section-7.1.1.
if ri.RemoteIP.Is4() {
return netutil.AddrFamilyIPv4 return netutil.AddrFamilyIPv4
} }
@ -184,8 +187,12 @@ func (mw *Middleware) writeUpstreamResponse(
metrics.ECSCacheLookupTotalMisses.Inc() metrics.ECSCacheLookupTotalMisses.Inc()
hostHasECS := scope != 0 && subnet != (netip.Prefix{}) // TODO(e.burkov, a.garipov): Think about ways to mitigate the situation
if hostHasECS { // 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.ECSCacheLookupHasSupportMisses.Inc()
metrics.ECSHasSupportCacheSize.Set(float64(mw.ecsCache.Len(false))) metrics.ECSHasSupportCacheSize.Set(float64(mw.ecsCache.Len(false)))
} else { } else {
@ -193,15 +200,19 @@ func (mw *Middleware) writeUpstreamResponse(
metrics.ECSNoSupportCacheSize.Set(float64(mw.cache.Len(false))) metrics.ECSNoSupportCacheSize.Set(float64(mw.cache.Len(false)))
cr.subnet = netutil.ZeroPrefix(ecsFam) 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 // 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. // a clone of the otherwise filtered response has already been set to cache.
setRespAD(resp, req.AuthenticatedData, reqDO) 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) err = setECS(resp, ri.ECS, ecsFam, true)
if err != nil { if err != nil {
return fmt.Errorf("responding with ecs: %w", err) return fmt.Errorf("responding with ecs: %w", err)
@ -240,37 +251,52 @@ func (mh *mwHandler) ServeDNS(
}() }()
ri := agd.MustRequestInfoFromContext(ctx) ri := agd.MustRequestInfoFromContext(ctx)
cr.host, cr.qType, cr.qClass = ri.Host, ri.QType, ri.QClass 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) cr.reqDO = dnsmsg.IsDO(req)
resp, found, hostHasECS := mw.get(req, cr)
if found { ecsFam := ecsFamFromReq(ri)
// Don't wrap the error, because it's informative enough as is.
return writeCachedResponse(ctx, rw, req, resp, ri.ECS, ecsFam, hostHasECS) 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 // Try getting a cached result using the subnet of the location or zero one
// successful, write, increment the metrics, and return. See also // when explicitly requested by user. If there is one, write, increment the
// writeUpstreamResponse. // metrics, and return. See also [writeCachedResponse].
reqECS := &agd.ECS{ 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, Subnet: cr.subnet,
Scope: 0, Scope: 0,
} }, ecsFam, false)
ecsReq := dnsmsg.Clone(req)
err = setECS(ecsReq, reqECS, ecsFam, false)
if err != nil { if err != nil {
return fmt.Errorf("setting ecs for upstream req: %w", err) return fmt.Errorf("setting ecs for upstream req: %w", err)
} }

View File

@ -2,9 +2,11 @@ package ecscache_test
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/netip" "net/netip"
"testing" "testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest" "github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
@ -32,7 +34,7 @@ const (
) )
// defaultTTL is the default TTL to use in tests. // defaultTTL is the default TTL to use in tests.
const defaultTTL = 3600 const defaultTTL uint32 = 3600
// remoteIP is the IP to use for tests. // remoteIP is the IP to use for tests.
var remoteIP = netip.MustParseAddr("1.2.3.4") var remoteIP = netip.MustParseAddr("1.2.3.4")
@ -151,23 +153,16 @@ func TestMiddleware_Wrap_noECS(t *testing.T) {
agd.CountryNone, agd.CountryNone,
netutil.ZeroPrefix(netutil.AddrFamilyIPv4), netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
) )
ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{ ri := &agd.RequestInfo{
Host: tc.req.Question[0].Name, Host: tc.req.Question[0].Name,
RemoteIP: remoteIP, 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.resp, msg)
assert.Equal(t, tc.wantNumReq, numReq) assert.Equal(t, tc.wantNumReq, numReq)
@ -188,7 +183,7 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
const prefixLen = 24 const prefixLen = 24
ip := net.IP{1, 2, 3, 0} 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{ opt.Option = append(opt.Option, &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET, Code: dns.EDNS0SUBNET,
Family: uint16(netutil.AddrFamilyIPv4), Family: uint16(netutil.AddrFamilyIPv4),
@ -198,31 +193,24 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
}) })
const ctry = agd.CountryAD const ctry = agd.CountryAD
defaultCtrySubnet := netip.MustParsePrefix("1.2.0.0/16") 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 { testCases := []struct {
req *dns.Msg req *dns.Msg
resp *dns.Msg respECS dns.RR
ecs *agd.ECS ecs *agd.ECS
name string
ctrySubnet netip.Prefix ctrySubnet netip.Prefix
wantNumReq int name string
wantTTL uint32
}{{ }{{
req: aReq, req: aReq,
resp: dnsservertest.NewResp( respECS: ecsExtra,
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,
},
),
ecs: &agd.ECS{ ecs: &agd.ECS{
Location: &agd.Location{ Location: &agd.Location{
Country: ctry, Country: ctry,
@ -230,24 +218,11 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
Subnet: subnet, Subnet: subnet,
Scope: 0, Scope: 0,
}, },
ctrySubnet: defaultCtrySubnet,
name: "with_country", name: "with_country",
ctrySubnet: defaultCtrySubnet,
wantNumReq: 1,
wantTTL: defaultTTL,
}, { }, {
req: aReq, req: aReq,
resp: dnsservertest.NewResp( respECS: ecsExtra,
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,
},
),
ecs: &agd.ECS{ ecs: &agd.ECS{
Location: &agd.Location{ Location: &agd.Location{
Country: ctry, Country: ctry,
@ -255,112 +230,330 @@ func TestMiddleware_Wrap_ecs(t *testing.T) {
Subnet: subnet, Subnet: subnet,
Scope: 0, Scope: 0,
}, },
name: "no_country",
ctrySubnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4), ctrySubnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
wantNumReq: 1, name: "no_country",
wantTTL: defaultTTL,
}, { }, {
req: aReqNoECS, req: aReqNoECS,
resp: dnsservertest.NewResp( respECS: ecsExtra,
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,
},
),
ecs: nil, ecs: nil,
name: "edns_no_ecs",
ctrySubnet: defaultCtrySubnet, ctrySubnet: defaultCtrySubnet,
wantNumReq: 1, name: "edns_no_ecs",
wantTTL: defaultTTL, }, {
req: aReq,
respECS: ecsExtra,
ecs: nil,
ctrySubnet: defaultCtrySubnet,
name: "country_from_ip",
}, { }, {
req: aReq, req: aReq,
resp: dnsservertest.NewResp( respECS: dnsservertest.NewECSExtra(
dns.RcodeSuccess, netutil.IPv4Zero(),
aReq, uint16(netutil.AddrFamilyIPv4),
dnsservertest.RRSection{ 0,
RRs: []dns.RR{dnsservertest.NewA(reqHostname, defaultTTL, net.IP{1, 2, 3, 4})}, 0,
Sec: dnsservertest.SectionAnswer,
},
dnsservertest.RRSection{
RRs: []dns.RR{ecsExtra},
Sec: dnsservertest.SectionExtra,
},
), ),
ecs: nil, ecs: &agd.ECS{
name: "country_from_ip", Location: &agd.Location{
Country: agd.CountryNone,
},
Subnet: netutil.ZeroPrefix(netutil.AddrFamilyIPv4),
Scope: 0,
},
ctrySubnet: defaultCtrySubnet, ctrySubnet: defaultCtrySubnet,
wantNumReq: 1, name: "zero_ecs",
wantTTL: defaultTTL,
}} }}
const N = 5 const N = 5
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { 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 numReq := 0
handler := dnsserver.HandlerFunc( handler := dnsserver.HandlerFunc(
func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error { func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) error {
numReq++ numReq++
return rw.WriteMsg(ctx, req, tc.resp) return rw.WriteMsg(ctx, req, resp)
}, },
) )
withCache := newWithCache(t, handler, ctry, tc.ctrySubnet) withCache := newWithCache(t, handler, ctry, tc.ctrySubnet)
ctx := agd.ContextWithRequestInfo(context.Background(), &agd.RequestInfo{ ri := &agd.RequestInfo{
Location: &agd.Location{ Location: &agd.Location{
Country: ctry, Country: ctry,
}, },
ECS: tc.ecs, ECS: tc.ecs,
Host: tc.req.Question[0].Name, Host: tc.req.Question[0].Name,
RemoteIP: remoteIP, 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) 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 tc.ecs == nil {
if respOpt != nil {
require.Empty(t, respOpt.Option)
}
return 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) require.Len(t, respOpt.Option, 1)
subnetOpt := testutil.RequireTypeAssert[*dns.EDNS0_SUBNET](t, respOpt.Option[0]) subnetOpt := testutil.RequireTypeAssert[*dns.EDNS0_SUBNET](t, respOpt.Option[0])
assert.Equal(t, ip, subnetOpt.Address) assert.Equal(t, net.IP(tc.ecs.Subnet.Addr().AsSlice()), subnetOpt.Address)
assert.Equal(t, uint8(prefixLen), subnetOpt.SourceNetmask) assert.Equal(t, uint8(tc.ecs.Subnet.Bits()), subnetOpt.SourceNetmask)
assert.Equal(t, uint8(prefixLen), subnetOpt.SourceScope) 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. // newWithCache is a helper constructor of a handler for tests.
func newWithCache( func newWithCache(
t testing.TB, t testing.TB,
@ -370,6 +563,8 @@ func newWithCache(
) (wrapped dnsserver.Handler) { ) (wrapped dnsserver.Handler) {
t.Helper() t.Helper()
pt := testutil.PanicT{}
// TODO(a.garipov): Actually test ASNs once we have the data. // TODO(a.garipov): Actually test ASNs once we have the data.
geoIP := &agdtest.GeoIP{ geoIP := &agdtest.GeoIP{
OnSubnetByLocation: func( OnSubnetByLocation: func(
@ -377,9 +572,7 @@ func newWithCache(
_ agd.ASN, _ agd.ASN,
_ netutil.AddrFamily, _ netutil.AddrFamily,
) (n netip.Prefix, err error) { ) (n netip.Prefix, err error) {
t.Helper() require.Equal(pt, wantCtry, ctry)
assert.Equal(t, wantCtry, ctry)
return geoIPNet, nil return geoIPNet, nil
}, },

View File

@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd" "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/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@ -74,10 +74,8 @@ func TestSentryErrorCollector(t *testing.T) {
Device: &agd.Device{ID: devID}, Device: &agd.Device{ID: devID},
Profile: &agd.Profile{ID: profID}, Profile: &agd.Profile{ID: profID},
FilteringGroup: &agd.FilteringGroup{ID: fltGrpID}, FilteringGroup: &agd.FilteringGroup{ID: fltGrpID},
Messages: &dnsmsg.Constructor{ Messages: agdtest.NewConstructor(),
FilteredResponseTTL: 10 * time.Second, ID: reqID,
},
ID: reqID,
}) })
origErr := errors.Error("test error") origErr := errors.Error("test error")

View File

@ -298,16 +298,17 @@ func (f *compFilter) Close() (err error) {
return nil return nil
} }
var errs []error errs := make([]error, len(f.ruleLists))
for i, rl := range f.ruleLists { for i, rl := range f.ruleLists {
err = rl.Close() err = rl.Close()
if err != nil { 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 { err = errors.Join(errs...)
return errors.List("closing filters", errs...) if err != nil {
return fmt.Errorf("closing filters: %w", err)
} }
return nil return nil

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