mirror of
https://github.com/AdguardTeam/AdGuardDNS.git
synced 2025-12-23 23:38:37 -05:00
Sync v2.17.0
This commit is contained in:
75
CHANGELOG.md
75
CHANGELOG.md
@@ -7,6 +7,81 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
|
||||
[kec]: https://keepachangelog.com/en/1.0.0/
|
||||
[sem]: https://semver.org/spec/v2.0.0.html
|
||||
|
||||
## AGDNS-3287 / Build 1081
|
||||
|
||||
- Profiles’ file cache version has been incremented to support custom block-page data.
|
||||
|
||||
## AGDNS-3133 / Build 1078
|
||||
|
||||
- The new `tls` have been added:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
enabled: true
|
||||
certificate_groups:
|
||||
example-cert:
|
||||
certificate: '/path/to/cert.crt'
|
||||
key: '/path/to/cert.key'
|
||||
```
|
||||
|
||||
- The property `certificates` of the object `server_groups.tls` has been replaced with the new object, `certificate_groups`. So replace this:
|
||||
|
||||
```yaml
|
||||
server_groups:
|
||||
tls:
|
||||
certificates:
|
||||
- certificate: '/path/to/cert.crt'
|
||||
key: '/path/to/cert.key'
|
||||
# …
|
||||
```
|
||||
|
||||
with this:
|
||||
|
||||
```yaml
|
||||
server_groups:
|
||||
tls:
|
||||
certificate_groups:
|
||||
- name: 'example-cert'
|
||||
# …
|
||||
```
|
||||
|
||||
- The property `certificates` of the objects `web.linked_ip.bind`, `web.adult_blocking.bind`, `web.general_blocking.bind`, `web.safe_browsing.bind`, as well as of the `web.non_doh_bind` have been replaced with the new object, `certificate_groups`. So replace this:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
linked_ip:
|
||||
bind:
|
||||
- # …
|
||||
certificates:
|
||||
- certificate: '/path/to/cert.crt'
|
||||
key: '/path/to/cert.key'
|
||||
# …
|
||||
non_doh_bind:
|
||||
- # …
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
|
||||
# …
|
||||
```
|
||||
|
||||
with this:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
linked_ip:
|
||||
bind:
|
||||
- # …
|
||||
certificate_groups:
|
||||
- name: 'example-cert'
|
||||
# …
|
||||
non_doh_bind:
|
||||
- # …
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
# …
|
||||
```
|
||||
|
||||
## AGDNS-3241 / Build 1067
|
||||
|
||||
- The environment variables `QUERYLOG_SEMAPHORE_ENABLED` and `QUERYLOG_SEMAPHORE_LIMIT` have been added.
|
||||
|
||||
27
Makefile
27
Makefile
@@ -1,20 +1,18 @@
|
||||
# Keep the Makefile POSIX-compliant. We currently allow hyphens in
|
||||
# target names, but that may change in the future.
|
||||
# Keep the Makefile POSIX-compliant. We currently allow hyphens in target
|
||||
# names, but that may change in the future.
|
||||
#
|
||||
# See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html.
|
||||
.POSIX:
|
||||
|
||||
# This comment is used to simplify checking local copies of the
|
||||
# Makefile. Bump this number every time a significant change is made to
|
||||
# this Makefile.
|
||||
# This comment is used to simplify checking local copies of the Makefile. Bump
|
||||
# this number every time a significant change is made to this Makefile.
|
||||
#
|
||||
# AdGuard-Project-Version: 9
|
||||
# AdGuard-Project-Version: 11
|
||||
|
||||
# Don't name these macros "GO" etc., because GNU Make apparently makes
|
||||
# them exported environment variables with the literal value of
|
||||
# "${GO:-go}" and so on, which is not what we need. Use a dot in the
|
||||
# name to make sure that users don't have an environment variable with
|
||||
# the same name.
|
||||
# Don't name these macros "GO" etc., because GNU Make apparently makes them
|
||||
# exported environment variables with the literal value of "${GO:-go}" and so
|
||||
# on, which is not what we need. Use a dot in the name to make sure that users
|
||||
# don't have an environment variable with the same name.
|
||||
#
|
||||
# See https://unix.stackexchange.com/q/646255/105635.
|
||||
GO.MACRO = $${GO:-go}
|
||||
@@ -24,7 +22,7 @@ BRANCH = $${BRANCH:-$$(git rev-parse --abbrev-ref HEAD)}
|
||||
GOAMD64 = v1
|
||||
GOPROXY = https://proxy.golang.org|direct
|
||||
GOTELEMETRY = off
|
||||
GOTOOLCHAIN = go1.25.1
|
||||
GOTOOLCHAIN = go1.25.3
|
||||
RACE = 0
|
||||
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
|
||||
VERSION = 0
|
||||
@@ -51,8 +49,7 @@ ENV_MISC = env \
|
||||
|
||||
# Keep the line above blank.
|
||||
|
||||
# Keep this target first, so that a naked make invocation triggers a
|
||||
# full build.
|
||||
# Keep this target first, so that a naked make invocation triggers a full build.
|
||||
.PHONY: build
|
||||
build: go-deps go-build
|
||||
|
||||
@@ -91,7 +88,7 @@ go-os-check:
|
||||
.PHONY: txt-lint
|
||||
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
|
||||
|
||||
.PHONY: md-lint
|
||||
.PHONY: md-lint sh-lint
|
||||
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
|
||||
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
|
||||
|
||||
|
||||
@@ -184,6 +184,15 @@ check:
|
||||
- 1234::cdee
|
||||
- 1234::cdef
|
||||
|
||||
tls:
|
||||
# If true, enable TLS.
|
||||
enabled: true
|
||||
# The list of certificate groups.
|
||||
certificate_groups:
|
||||
default:
|
||||
certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
|
||||
# Web/HTTP(S) service configuration. All non-root requests to the main service
|
||||
# not matching the static_content map are shown a 404 page. In special
|
||||
# case of `/robots.txt` request the special response is served.
|
||||
@@ -194,9 +203,8 @@ web:
|
||||
bind:
|
||||
- address: '127.0.0.1:9080'
|
||||
- address: '127.0.0.1:9443'
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
# Optional adult blocking web server configuration. static_content is not
|
||||
# served on these addresses. The addresses should be the same as in the
|
||||
# general_blocking and safe_browsing objects.
|
||||
@@ -204,9 +212,8 @@ web:
|
||||
bind:
|
||||
- address: '127.0.0.1:9081'
|
||||
- address: '127.0.0.1:9444'
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
block_page: './test/block_page_adult.html'
|
||||
# Optional general blocking web server configuration. static_content is not
|
||||
# served on these addresses. The addresses should be the same as in the
|
||||
@@ -215,9 +222,8 @@ web:
|
||||
bind:
|
||||
- address: '127.0.0.1:9082'
|
||||
- address: '127.0.0.1:9445'
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
block_page: './test/block_page_general.html'
|
||||
# Optional safe browsing web server configuration. static_content is not
|
||||
# served on these addresses. The addresses should be the same as in the
|
||||
@@ -226,18 +232,16 @@ web:
|
||||
bind:
|
||||
- address: '127.0.0.1:9083'
|
||||
- address: '127.0.0.1:9446'
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
block_page: './test/block_page_sb.html'
|
||||
# Listen addresses for the web service in addition to the ones in the
|
||||
# DNS-over-HTTPS handlers.
|
||||
non_doh_bind:
|
||||
- address: '127.0.0.1:9084'
|
||||
- address: '127.0.0.1:9447'
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
# Static content map. Not served on the linked_ip, safe_browsing and adult_blocking
|
||||
# servers. Paths must not cross the ones used by the DNS-over-HTTPS server.
|
||||
static_content:
|
||||
@@ -410,9 +414,8 @@ server_groups:
|
||||
ipv6_hints:
|
||||
- '::1'
|
||||
tls:
|
||||
certificates:
|
||||
- certificate: './test/cert.crt'
|
||||
key: './test/cert.key'
|
||||
certificate_groups:
|
||||
- name: 'default'
|
||||
session_keys:
|
||||
- './test/tls_key_1'
|
||||
- './test/tls_key_2'
|
||||
|
||||
@@ -19,6 +19,7 @@ Besides the [environment][env], AdGuard DNS uses a [YAML][yaml] file to store co
|
||||
- [Query log](#query_log)
|
||||
- [GeoIP database](#geoip)
|
||||
- [DNS-server check](#check)
|
||||
- [TLS](#tls)
|
||||
- [Web API](#web)
|
||||
- [Safe browsing](#safe_browsing)
|
||||
- [Adult-content blocking](#adult_blocking)
|
||||
@@ -403,6 +404,25 @@ The `check` object has the following properties:
|
||||
|
||||
[http-dnscheck]: http.md#dnscheck-test
|
||||
|
||||
## <a href="#tls" id="tls" name="tls">TLS</a>
|
||||
|
||||
The `tls` object has the following properties:
|
||||
|
||||
- <a href="#tls-enabled" id="tls-enabled" name="tls-enabled">`enabled`</a>: If true, the TLS certificates are used. Otherwise, any object supporting TLS should be configured to not use it.
|
||||
|
||||
**Example:** `true`.
|
||||
|
||||
- <a href="#tls-certificate_groups" id="tls-certificate_groups" name="tls-certificate_groups">`certificate_groups`</a>: The object maps certificate names to the certificate's file path and its private key's file path. The name of each certificate should be unique and should contain no more than 64 characters, which should be `a-z`, `A-Z`, `0-9`, `-`, or `_`.
|
||||
|
||||
**Property example:**
|
||||
|
||||
```yaml
|
||||
'certificate_groups':
|
||||
'example-cert':
|
||||
'certificate': '/path/to/cert.crt'
|
||||
'key': '/path/to/cert.key'
|
||||
```
|
||||
|
||||
## <a href="#web" id="web" name="web">Web API</a>
|
||||
|
||||
The optional `web` object has the following properties:
|
||||
@@ -420,9 +440,8 @@ The optional `web` object has the following properties:
|
||||
'bind':
|
||||
- 'address': '127.0.0.1:80'
|
||||
- 'address': '127.0.0.1:443'
|
||||
'certificates':
|
||||
- 'certificate': './test/cert.crt'
|
||||
'key': './test/cert.key'
|
||||
'certificate_groups':
|
||||
- 'name': 'example-cert'
|
||||
```
|
||||
|
||||
- <a href="#web-safe_browsing" id="web-safe_browsing" name="web-safe_browsing">`safe_browsing`</a>: The optional safe browsing block-page web server configurations. Every request is responded with the content from the file to which the `block_page` property points.
|
||||
@@ -440,9 +459,8 @@ The optional `web` object has the following properties:
|
||||
'bind':
|
||||
- 'address': '127.0.0.1:80'
|
||||
- 'address': '127.0.0.1:443'
|
||||
'certificates':
|
||||
- 'certificate': './test/cert.crt'
|
||||
'key': './test/cert.key'
|
||||
'certificate_groups':
|
||||
- 'name': 'example-cert'
|
||||
'block_page': '/var/www/block_page.html'
|
||||
```
|
||||
|
||||
@@ -450,7 +468,7 @@ The optional `web` object has the following properties:
|
||||
|
||||
- <a href="#web-general_blocking" id="web-general_blocking" name="web-general_blocking">`general_blocking`</a>: The optional general block-page web server configuration. The format of the values is the same as in the [`safe_browsing`](#web-safe_browsing) object above.
|
||||
|
||||
- <a href="#web-non_doh_bind" id="web-non_doh_bind" name="web-non_doh_bind">`non_doh_bind`</a>: The optional listen addresses and optional TLS configuration for the web service in addition to the ones in the DNS-over-HTTPS handlers. The `certificates` array has the same format as the one in a server group's [TLS settings](#server_groups-*-tls). In the special case of `GET /robots.txt` requests, a special response is served; this response could be overwritten with static content.
|
||||
- <a href="#web-non_doh_bind" id="web-non_doh_bind" name="web-non_doh_bind">`non_doh_bind`</a>: The optional listen addresses and optional TLS configuration for the web service in addition to the ones in the DNS-over-HTTPS handlers. The `certificate_groups` array has the same format as [`certificate_groups`](#sg-*-tls-certificate_groups) in a server group's TLS settings. In the special case of `GET /robots.txt` requests, a special response is served; this response could be overwritten with static content.
|
||||
|
||||
**Property example:**
|
||||
|
||||
@@ -458,9 +476,8 @@ The optional `web` object has the following properties:
|
||||
'non_doh_bind':
|
||||
- 'address': '127.0.0.1:80'
|
||||
- 'address': '127.0.0.1:443'
|
||||
'certificates':
|
||||
- 'certificate': './test/cert.crt'
|
||||
'key': './test/cert.key'
|
||||
'certificate_groups':
|
||||
- 'name': 'example-cert'
|
||||
```
|
||||
|
||||
- <a href="#web-static_content" id="web-static_content" name="web-static_content">`static_content`</a>: The optional inline static content mapping. Not served on the `linked_ip`, `safe_browsing` and `adult_blocking` servers. Paths must not duplicate the ones used by the DNS-over-HTTPS server.
|
||||
@@ -757,14 +774,14 @@ The DDR configuration object. Many of these data duplicate data from objects in
|
||||
|
||||
### <a href="#server_groups-*-tls" id="server_groups-*-tls" name="server_groups-*-tls">TLS</a>
|
||||
|
||||
- <a href="#sg-*-tls-certificates" id="sg-*-tls-certificates" name="sg-*-tls-certificates">`certificates`</a>: The array of objects with paths to the certificate and the private key for this server group.
|
||||
- <a href="#sg-*-tls-certificate_groups" id="sg-*-tls-certificate_groups" name="sg-*-tls-certificate_groups">`certificate_groups`</a>: The array of objects with names of the certificates to use for servers with standard encrypted protocols within the group. See [`certificate_groups`](#tls-certificate_groups).
|
||||
<a href="#sg-*-tls-certificates" id="sg-*-tls-certificates" name="sg-*-tls-certificates"><!-- Name of this field has changed, so keep the anchor to avoid breaking old links. --></a>
|
||||
|
||||
**Property example:**
|
||||
|
||||
```yaml
|
||||
'certificates':
|
||||
- 'certificate': '/etc/dns/cert.crt'
|
||||
'key': '/etc/dns/cert.key'
|
||||
'certificate_groups':
|
||||
- 'name': 'example-cert'
|
||||
```
|
||||
|
||||
- <a href="#sg-*-tls-session_keys" id="sg-*-tls-session_keys" name="sg-*-tls-session_keys">`session_keys`</a>: The array of file paths from which the each server's TLS session keys are updated. Session ticket key files must contain at least 32 bytes.
|
||||
|
||||
75
go.mod
75
go.mod
@@ -1,48 +1,46 @@
|
||||
module github.com/AdguardTeam/AdGuardDNS
|
||||
|
||||
go 1.25.1
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
// NOTE: Do not change the pseudoversion.
|
||||
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
|
||||
github.com/AdguardTeam/golibs v0.34.1
|
||||
github.com/AdguardTeam/urlfilter v0.22.0
|
||||
github.com/AdguardTeam/golibs v0.35.2
|
||||
github.com/AdguardTeam/urlfilter v0.22.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||
github.com/axiomhq/hyperloglog v0.2.5
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/getsentry/sentry-go v0.35.1
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/getsentry/sentry-go v0.36.0
|
||||
github.com/gomodule/redigo v1.9.3
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/renameio/v2 v2.0.0
|
||||
github.com/miekg/dns v1.1.68
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
|
||||
github.com/prometheus/client_golang v1.23.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/prometheus/common v0.66.0
|
||||
github.com/quic-go/quic-go v0.54.0
|
||||
github.com/prometheus/common v0.67.1
|
||||
github.com/quic-go/quic-go v0.55.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/viktordanov/golang-lru v0.5.6
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/sys v0.36.0
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/time v0.13.0
|
||||
google.golang.org/grpc v1.75.1
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.122.0 // indirect
|
||||
cloud.google.com/go/ai v0.12.1 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.8.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.14.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/caarlos0/env/v11 v11.3.1 // indirect
|
||||
@@ -55,50 +53,47 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golangci/misspell v0.7.0 // indirect
|
||||
github.com/google/generative-ai-go v0.20.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gookit/color v1.6.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.2.0 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
|
||||
github.com/kamstrup/intmap v0.5.1 // indirect
|
||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.25.1 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.11.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.8 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.10 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.31.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20251017212417-90e834f514db // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect
|
||||
golang.org/x/term v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/vuln v1.1.4 // indirect
|
||||
google.golang.org/api v0.249.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
||||
google.golang.org/genai v1.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
|
||||
157
go.sum
157
go.sum
@@ -1,27 +1,23 @@
|
||||
cloud.google.com/go v0.122.0 h1:0JTLGrcSIs3HIGsgVPvTx3cfyFSP/k9CI8vLPHTd6Wc=
|
||||
cloud.google.com/go v0.122.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0=
|
||||
cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
|
||||
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
|
||||
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
|
||||
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
github.com/AdguardTeam/golibs v0.34.1 h1:RyBpZiXnJqlO3T+xjWldlxsEZDelmaFfKvXiJHDZZFQ=
|
||||
github.com/AdguardTeam/golibs v0.34.1/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
|
||||
github.com/AdguardTeam/urlfilter v0.22.0 h1:ybOz3FywbpGDGC+8gFFkM1LMUOSosY7CWSBXIYXnG1U=
|
||||
github.com/AdguardTeam/urlfilter v0.22.0/go.mod h1:q0lWKapXlYTA4TUWUM1YDwU6Q0PKvQEokztcvRV2OW0=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/AdguardTeam/golibs v0.35.2 h1:GVlx/CiCz5ZXQmyvFrE3JyeGsgubE8f4rJvRshYJVVs=
|
||||
github.com/AdguardTeam/golibs v0.35.2/go.mod h1:p/l6tG7QCv+Hi5yVpv1oZInoatRGOWoyD1m+Ume+ZNY=
|
||||
github.com/AdguardTeam/urlfilter v0.22.1 h1:nC2x0MSNwmTsXMTPfs1Gv6GZXKmK7prlzgjCdnE4fR8=
|
||||
github.com/AdguardTeam/urlfilter v0.22.1/go.mod h1:+wUx7GApNWvFPALjNd5fTLix4PFvQF5Gprx6JDYwxfE=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4=
|
||||
github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/axiomhq/hyperloglog v0.2.5 h1:Hefy3i8nAs8zAI/tDp+wE7N+Ltr8JnwiW3875pvl0N8=
|
||||
github.com/axiomhq/hyperloglog v0.2.5/go.mod h1:DLUK9yIzpU5B6YFLjxTIcbHu1g4Y1WQb1m5RH3radaM=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
@@ -48,8 +44,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ=
|
||||
github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/getsentry/sentry-go v0.36.0 h1:UkCk0zV28PiGf+2YIONSSYiYhxwlERE5Li3JPpZqEns=
|
||||
github.com/getsentry/sentry-go v0.36.0/go.mod h1:p5Im24mJBeruET8Q4bbcMfCQ+F+Iadc4L48tB1apo2c=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -65,10 +61,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
|
||||
github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
|
||||
github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
|
||||
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
||||
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
|
||||
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -94,8 +88,8 @@ github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
|
||||
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
|
||||
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
|
||||
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
|
||||
github.com/kamstrup/intmap v0.5.1 h1:ENGAowczZA+PJPYYlreoqJvWgQVtAmX1l899WfYFVK0=
|
||||
@@ -114,10 +108,10 @@ github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
|
||||
github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
|
||||
github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
|
||||
@@ -130,38 +124,47 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
|
||||
github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
|
||||
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
|
||||
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
|
||||
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI=
|
||||
github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto=
|
||||
github.com/securego/gosec/v2 v2.22.10 h1:ntbBqdWXnu46DUOXn+R2SvPo3PiJCDugTCgTW2g4tQg=
|
||||
github.com/securego/gosec/v2 v2.22.10/go.mod h1:9UNjK3tLpv/w2b0+7r82byV43wCJDNtEDQMeS+H/g2w=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
|
||||
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
|
||||
github.com/viktordanov/golang-lru v0.5.6 h1:wEyMgglEo5IZ7Maxeh8E2jCPskpQnt6FJAYl1/TJ6ac=
|
||||
github.com/viktordanov/golang-lru v0.5.6/go.mod h1:R91CBCcMhp6TYUy8NHP/PJ09sk5BTDwb8KMO21CELes=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.opentelemetry.io/auto/sdk v1.2.0 h1:YpRtUFjvhSymycLS2T81lT6IGhcUP+LUPtv0iv1N8bM=
|
||||
go.opentelemetry.io/auto/sdk v1.2.0/go.mod h1:1deq2zL7rwjwC8mR7XgY2N+tlIl6pjmEUoLDENMEzwk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
@@ -180,32 +183,36 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 h1:Yl4H5w2RV7L/dvSHp2GerziT5K2CORgFINPaMFxWGWw=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/exp/typeparams v0.0.0-20251017212417-90e834f514db h1:zIIKf9uYLvsQHFOJ0O+SZ9iFRMNkoXzBRxOGDgr4xkA=
|
||||
golang.org/x/exp/typeparams v0.0.0-20251017212417-90e834f514db/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8=
|
||||
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef h1:5xFtU4tmJMJSxSeDlr1dgBff2tDXrq0laLdS1EA3LYw=
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
@@ -214,25 +221,19 @@ golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
|
||||
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w=
|
||||
google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ=
|
||||
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 h1:ywCL7vA2n3vVHyf+bx1ZV/knaTPRI8GIeKY0MEhEeOc=
|
||||
google.golang.org/genproto v0.0.0-20250908214217-97024824d090/go.mod h1:zwJI9HzbJJlw2KXy0wX+lmT2JuZoaKK9JC4ppqmxxjk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/genai v1.31.0 h1:R7xDt/Dosz11vcXbZ4IgisGnzUGGau2PZOIOAnXsYjw=
|
||||
google.golang.org/genai v1.31.0/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635 h1:3uycTxukehWrxH4HtPRtn1PDABTU331ViDjyqrUbaog=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
|
||||
|
||||
169
go.work.sum
169
go.work.sum
@@ -31,10 +31,16 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ
|
||||
cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU=
|
||||
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0=
|
||||
cloud.google.com/go/accessapproval v1.8.7/go.mod h1:BFvZOW4GJjJnl6aA/YDEg0TGViFHyusa/bMdcVFmh8A=
|
||||
cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q=
|
||||
cloud.google.com/go/accesscontextmanager v1.9.6/go.mod h1:884XHwy1AQpCX5Cj2VqYse77gfLaq9f8emE2bYriilk=
|
||||
cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
|
||||
cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM=
|
||||
cloud.google.com/go/aiplatform v1.100.0/go.mod h1:oZUOTz6+cMt9eVNe62CXPfIQQQ+QjR4rW3GBGD9r6Fg=
|
||||
cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0=
|
||||
@@ -55,6 +61,16 @@ cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EI
|
||||
cloud.google.com/go/asset v1.21.1/go.mod h1:7AzY1GCC+s1O73yzLM1IpHFLHz3ws2OigmCpOQHwebk=
|
||||
cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk=
|
||||
cloud.google.com/go/assuredworkloads v1.12.6/go.mod h1:QyZHd7nH08fmZ+G4ElihV1zoZ7H0FQCpgS0YWtwjCKo=
|
||||
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
|
||||
cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
|
||||
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
|
||||
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
|
||||
cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y=
|
||||
cloud.google.com/go/automl v1.14.7/go.mod h1:8a4XbIH5pdvrReOU72oB+H3pOw2JBxo9XTk39oljObE=
|
||||
cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY=
|
||||
@@ -107,6 +123,7 @@ cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykW
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||
cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI=
|
||||
cloud.google.com/go/contactcenterinsights v1.17.3/go.mod h1:7Uu2CpxS3f6XxhRdlEzYAkrChpR5P5QfcdGAFEdHOG8=
|
||||
cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA=
|
||||
@@ -191,6 +208,8 @@ cloud.google.com/go/lifesciences v0.10.6/go.mod h1:1nnZwaZcBThDujs9wXzECnd1S5d+U
|
||||
cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
|
||||
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI=
|
||||
cloud.google.com/go/managedidentities v1.7.6/go.mod h1:pYCWPaI1AvR8Q027Vtp+SFSM/VOVgbjBF4rxp1/z5p4=
|
||||
cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI=
|
||||
@@ -405,6 +424,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
@@ -413,6 +433,7 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/anthropics/anthropic-sdk-go v1.12.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
@@ -420,6 +441,20 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
||||
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
|
||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -508,12 +543,14 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/eliben/go-sentencepiece v0.6.0/go.mod h1:nNYk4aMzgBoI6QFp4LUG8Eu1uO9fHD9L5ZEre93o9+c=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
|
||||
@@ -578,6 +615,7 @@ github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -681,6 +719,7 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs=
|
||||
github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
|
||||
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=
|
||||
@@ -725,6 +764,7 @@ github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAx
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
|
||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -735,6 +775,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
@@ -742,6 +784,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
@@ -769,6 +814,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@@ -963,6 +1010,7 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd
|
||||
github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5 h1:0KqC6/sLy7fDpBdybhVkkv4Yz+PmB7c9Dz9z3dLW804=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20250923143331-eef96233227e/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s=
|
||||
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 h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -1011,6 +1059,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
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=
|
||||
@@ -1029,11 +1078,14 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA=
|
||||
github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
@@ -1137,15 +1189,20 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=
|
||||
@@ -1227,6 +1284,7 @@ go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
|
||||
@@ -1236,11 +1294,16 @@ go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVL
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
@@ -1248,6 +1311,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQF
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
@@ -1255,6 +1319,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
@@ -1262,6 +1327,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0/go.mod h1:H9LUIM1daaeZaz91vZcfeM0fejXPmgCYE8ZhzqfJuiU=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
|
||||
@@ -1269,16 +1335,25 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
@@ -1287,17 +1362,20 @@ go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAj
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d h1:E2M5QgjZ/Jg+ObCQAudsXxuTsLj7Nl5RV/lZcQZmKSo=
|
||||
@@ -1306,9 +1384,11 @@ golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190313024323-a1f597ede03a/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-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
@@ -1323,12 +1403,17 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1355,7 +1440,11 @@ golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||
golang.org/x/exp/typeparams v0.0.0-20251009144603-d2f985daa21b/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -1391,6 +1480,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1402,10 +1492,12 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -1457,6 +1549,9 @@ golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -1484,6 +1579,11 @@ golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1491,6 +1591,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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-20190412183630-56d357773e84/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=
|
||||
@@ -1501,9 +1602,12 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1511,9 +1615,11 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/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-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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=
|
||||
@@ -1544,6 +1650,7 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1580,6 +1687,9 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g=
|
||||
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
|
||||
@@ -1588,7 +1698,10 @@ golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMe
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/telemetry v0.0.0-20250603144755-9a9ac2102d0e/go.mod h1:QNvpSH4vItB4zw8JazOv6Ba3fs1TorwPx9cCU6qTIdE=
|
||||
golang.org/x/telemetry v0.0.0-20250606142133-60998feb31a8/go.mod h1:mUcjA5g0luJpMYCLjhH91f4t4RAUNp+zq9ZmUoqPD7M=
|
||||
golang.org/x/telemetry v0.0.0-20250829165349-50b750f55de1/go.mod h1:JIJwPkb04vX0KeIBbQ7epGtgIjA8ihHbsAtW4A/lIQ4=
|
||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
@@ -1623,9 +1736,12 @@ golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
@@ -1642,6 +1758,9 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1649,8 +1768,11 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1672,6 +1794,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@@ -1717,6 +1840,9 @@ golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
@@ -1742,7 +1868,14 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
||||
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
|
||||
google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw=
|
||||
google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8=
|
||||
google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0=
|
||||
google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
|
||||
google.golang.org/api v0.237.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
|
||||
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1757,6 +1890,7 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
@@ -1806,6 +1940,10 @@ google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUE
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
|
||||
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto v0.0.0-20250826171959-ef028d996bc1 h1:Nm5SEGIguOIBDXs5rhfz2aKwEVWlgwC58UcmEnLDc8Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
@@ -1815,6 +1953,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
@@ -1822,6 +1961,13 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4 h1://y4MHaM7tNLqTeWKyfBIeoAMxwKwRm/nODb5IKA3BE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f/go.mod h1:kprOiu9Tr0JYyD6DORrc4Hfyk3RFXqkQ3ctHEum3ZbM=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:WkJpQl6Ujj3ElX4qZaNm5t6cT95ffI4K+HKQ0+1NyMw=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250414145226-207652e42e2e h1:OK8bKvRgTGs7U871RdjtCiRcQJLice8/rZkeoaZgnlc=
|
||||
@@ -1835,9 +1981,21 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20250818200422-3122310a4
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
@@ -1858,8 +2016,15 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
||||
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
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=
|
||||
@@ -1880,6 +2045,9 @@ google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1920,6 +2088,7 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
|
||||
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
|
||||
mvdan.cc/gofumpt v0.9.0/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
||||
|
||||
@@ -37,10 +37,19 @@ type Profile struct {
|
||||
// Access is the access manager for this profile. It must not be nil.
|
||||
Access access.Profile
|
||||
|
||||
// AdultBlockingMode defines the way blocked responses for adult-content are
|
||||
// constructed. In case of nil, the default BlockingMode is used.
|
||||
AdultBlockingMode dnsmsg.BlockingMode
|
||||
|
||||
// BlockingMode defines the way blocked responses are constructed. It must
|
||||
// not be nil.
|
||||
BlockingMode dnsmsg.BlockingMode
|
||||
|
||||
// SafeBrowsingBlockingMode defines the way blocked responses for
|
||||
// safe-browsing content are constructed. In case of nil, the default
|
||||
// BlockingMode is used.
|
||||
SafeBrowsingBlockingMode dnsmsg.BlockingMode
|
||||
|
||||
// Ratelimiter is the custom ratelimiter for this profile. It must not be
|
||||
// nil.
|
||||
Ratelimiter Ratelimiter
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// maxCertificateNameLen is the maximum length of a [CertificateName].
|
||||
const maxCertificateNameLen = 32
|
||||
const maxCertificateNameLen = 64
|
||||
|
||||
// CertificateName is the unique name identifying the TLS certificate.
|
||||
type CertificateName string
|
||||
|
||||
@@ -28,8 +28,8 @@ func TestNewCertificateName(t *testing.T) {
|
||||
wantErrMsg: "at index 3: bad symbol: '/'",
|
||||
}, {
|
||||
name: "too_long",
|
||||
value: "this_is_a_very_long_certificate_name",
|
||||
wantErrMsg: "length: out of range: must be no greater than 32, got 36",
|
||||
value: "this_is_a_very_long_certificate_name_which_should_be_64_symbols_long",
|
||||
wantErrMsg: "length: out of range: must be no greater than 64, got 68",
|
||||
}, {
|
||||
name: "ok",
|
||||
value: "ok_cert_name",
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
package agdnet
|
||||
|
||||
import (
|
||||
"net/http/cookiejar"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// These are suffixes of the FQDN in DNS queries for metrics that the DNS
|
||||
@@ -65,3 +68,38 @@ func NormalizeQueryDomain(host string) (norm string) {
|
||||
|
||||
return NormalizeDomain(host)
|
||||
}
|
||||
|
||||
// AppendSubdomains appends all subdomains to orig and limits result length with
|
||||
// subDomainNum. publicList must not be nil.
|
||||
func AppendSubdomains(
|
||||
orig []string,
|
||||
domain string,
|
||||
subDomainNum int,
|
||||
publicList cookiejar.PublicSuffixList,
|
||||
) (sub []string) {
|
||||
sub = orig
|
||||
pubSuf := publicList.PublicSuffix(domain)
|
||||
|
||||
dotsNum := 0
|
||||
i := strings.LastIndexFunc(domain, func(r rune) (ok bool) {
|
||||
if r == '.' {
|
||||
dotsNum++
|
||||
}
|
||||
|
||||
return dotsNum == subDomainNum
|
||||
})
|
||||
if i != -1 {
|
||||
domain = domain[i+1:]
|
||||
}
|
||||
|
||||
sub = netutil.AppendSubdomains(sub, domain)
|
||||
for i, s := range sub {
|
||||
if s == pubSuf {
|
||||
sub = sub[:i]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
@@ -1,9 +1,97 @@
|
||||
package agdnet_test
|
||||
|
||||
import "net/netip"
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Common subnets for tests.
|
||||
var (
|
||||
testSubnetIPv4 = netip.MustParsePrefix("1.2.3.0/24")
|
||||
testSubnetIPv6 = netip.MustParsePrefix("1234:5678::/64")
|
||||
)
|
||||
|
||||
func TestAppendSubdomains(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
domain string
|
||||
want []string
|
||||
subDomainNum int
|
||||
}{{
|
||||
name: "all_sub_domains",
|
||||
domain: "example.a.b.c.org",
|
||||
subDomainNum: 5,
|
||||
want: []string{
|
||||
"c.org",
|
||||
"b.c.org",
|
||||
"a.b.c.org",
|
||||
"example.a.b.c.org",
|
||||
},
|
||||
}, {
|
||||
name: "no_sub_domains",
|
||||
domain: "org",
|
||||
subDomainNum: 100,
|
||||
want: []string{},
|
||||
}, {
|
||||
name: "limit_sub_domains",
|
||||
domain: "example.a.b.c.org",
|
||||
subDomainNum: 3,
|
||||
want: []string{
|
||||
"c.org",
|
||||
"b.c.org",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := agdnet.AppendSubdomains(nil, tc.domain, tc.subDomainNum, publicsuffix.List)
|
||||
assert.ElementsMatch(t, tc.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendSubdomains(b *testing.B) {
|
||||
benchCases := []struct {
|
||||
name string
|
||||
domain string
|
||||
num int
|
||||
}{{
|
||||
name: "many_sub_domains",
|
||||
domain: "example.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.org",
|
||||
num: 100,
|
||||
}, {
|
||||
name: "no_sub_domains",
|
||||
domain: "org",
|
||||
num: 100,
|
||||
}, {
|
||||
name: "limit_sub_domains",
|
||||
domain: "example.a.b.c.org",
|
||||
num: 3,
|
||||
}}
|
||||
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
// Warmup to fill the slices.
|
||||
got := agdnet.AppendSubdomains(nil, bc.domain, bc.num, publicsuffix.List)
|
||||
|
||||
for b.Loop() {
|
||||
got = agdnet.AppendSubdomains(got[:0], bc.domain, bc.num, publicsuffix.List)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Most recent results:
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agdnet
|
||||
// cpu: Apple M3
|
||||
// BenchmarkAppendSubdomains/many_sub_domains-8 3826170 313.4 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkAppendSubdomains/no_sub_domains-8 20443263 58.75 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkAppendSubdomains/limit_sub_domains-8 10685626 112.2 ns/op 0 B/op 0 allocs/op
|
||||
}
|
||||
|
||||
32
internal/agdprotobuf/unsafe.go
Normal file
32
internal/agdprotobuf/unsafe.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package agdprotobuf
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// UnsafelyConvertStrSlice checks if []T1 can be converted to []T2 at compile
|
||||
// time and, if so, converts the slice using package unsafe.
|
||||
//
|
||||
// Slices resulting from this conversion must not be mutated.
|
||||
// TODO(f.setrakov): Generalize.
|
||||
func UnsafelyConvertStrSlice[T1, T2 ~string](s []T1) (res []T2) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// #nosec G103 -- Conversion between two slices with the same underlying
|
||||
// element type is safe.
|
||||
return *(*[]T2)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
// UnsafelyConvertUint32Slice checks if []T1 can be converted to []T2 at compile
|
||||
// time and, if so, converts the slice using package unsafe.
|
||||
//
|
||||
// Slices resulting from this conversion must not be mutated.
|
||||
func UnsafelyConvertUint32Slice[T1, T2 ~uint32](s []T1) (res []T2) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// #nosec G103 -- Conversion between two slices with the same underlying
|
||||
// element type is safe.
|
||||
return *(*[]T2)(unsafe.Pointer(&s))
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.8
|
||||
// protoc-gen-go v1.36.9
|
||||
// protoc v6.32.0
|
||||
// source: dns.proto
|
||||
|
||||
@@ -298,28 +298,28 @@ func (x *DNSProfilesRequest) GetSyncTime() *timestamppb.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
// *
|
||||
// Message DNSProfile contains both the data of the account and the data of the
|
||||
// DNS server.
|
||||
//
|
||||
// The fields are ordered in a way that optimizes the generated structures'
|
||||
// layouts.
|
||||
//
|
||||
// TODO(a.garipov): Expand the field documentation.
|
||||
type DNSProfile struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
|
||||
FilteringEnabled bool `protobuf:"varint,2,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
|
||||
QueryLogEnabled bool `protobuf:"varint,3,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"`
|
||||
Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"`
|
||||
SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,5,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"`
|
||||
Parental *ParentalSettings `protobuf:"bytes,6,opt,name=parental,proto3" json:"parental,omitempty"`
|
||||
RuleLists *RuleListsSettings `protobuf:"bytes,7,opt,name=rule_lists,json=ruleLists,proto3" json:"rule_lists,omitempty"`
|
||||
// Field devices contains the COMPLETE list of all devices in the profile.
|
||||
// Field device_changes contains only the list of changes that have happened
|
||||
// to the profile's devices since sync_time.
|
||||
//
|
||||
// devices and device_changes MUST NOT be set at the same time, but they MAY
|
||||
// both be empty.
|
||||
//
|
||||
// device_changes MUST NOT contain multiple changes for the same device.
|
||||
Devices []*DeviceSettings `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"`
|
||||
CustomRules []string `protobuf:"bytes,9,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"`
|
||||
FilteredResponseTtl *durationpb.Duration `protobuf:"bytes,10,opt,name=filtered_response_ttl,json=filteredResponseTtl,proto3" json:"filtered_response_ttl,omitempty"`
|
||||
BlockPrivateRelay bool `protobuf:"varint,11,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"`
|
||||
BlockFirefoxCanary bool `protobuf:"varint,12,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"`
|
||||
Access *AccessSettings `protobuf:"bytes,18,opt,name=access,proto3" json:"access,omitempty"`
|
||||
RateLimit *RateLimitSettings `protobuf:"bytes,20,opt,name=rate_limit,json=rateLimit,proto3" json:"rate_limit,omitempty"`
|
||||
CustomDomain *CustomDomainSettings `protobuf:"bytes,22,opt,name=custom_domain,json=customDomain,proto3" json:"custom_domain,omitempty"`
|
||||
// *
|
||||
// Field blocking_mode defines the blocking mode for general rule-list based
|
||||
// filtering. If field deleted is false, field blocking_mode MUST be
|
||||
// present.
|
||||
//
|
||||
// Types that are valid to be assigned to BlockingMode:
|
||||
//
|
||||
// *DNSProfile_BlockingModeCustomIp
|
||||
@@ -327,14 +327,64 @@ type DNSProfile struct {
|
||||
// *DNSProfile_BlockingModeNullIp
|
||||
// *DNSProfile_BlockingModeRefused
|
||||
BlockingMode isDNSProfile_BlockingMode `protobuf_oneof:"blocking_mode"`
|
||||
IpLogEnabled bool `protobuf:"varint,17,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"`
|
||||
Access *AccessSettings `protobuf:"bytes,18,opt,name=access,proto3" json:"access,omitempty"`
|
||||
AutoDevicesEnabled bool `protobuf:"varint,19,opt,name=auto_devices_enabled,json=autoDevicesEnabled,proto3" json:"auto_devices_enabled,omitempty"`
|
||||
RateLimit *RateLimitSettings `protobuf:"bytes,20,opt,name=rate_limit,json=rateLimit,proto3" json:"rate_limit,omitempty"`
|
||||
BlockChromePrefetch bool `protobuf:"varint,21,opt,name=block_chrome_prefetch,json=blockChromePrefetch,proto3" json:"block_chrome_prefetch,omitempty"`
|
||||
CustomDomain *CustomDomainSettings `protobuf:"bytes,22,opt,name=custom_domain,json=customDomain,proto3" json:"custom_domain,omitempty"`
|
||||
// *
|
||||
// Field adult_blocking_mode defines the blocking mode for the adult-content
|
||||
// filter. If absent, the default is used.
|
||||
//
|
||||
// Types that are valid to be assigned to AdultBlockingMode:
|
||||
//
|
||||
// *DNSProfile_AdultBlockingModeCustomIp
|
||||
// *DNSProfile_AdultBlockingModeNxdomain
|
||||
// *DNSProfile_AdultBlockingModeNullIp
|
||||
// *DNSProfile_AdultBlockingModeRefused
|
||||
AdultBlockingMode isDNSProfile_AdultBlockingMode `protobuf_oneof:"adult_blocking_mode"`
|
||||
// *
|
||||
// Field safe_browsing_blocking_mode defines the blocking mode for the
|
||||
// safe-browsing filter. If absent, the default is used.
|
||||
//
|
||||
// Types that are valid to be assigned to SafeBrowsingBlockingMode:
|
||||
//
|
||||
// *DNSProfile_SafeBrowsingBlockingModeCustomIp
|
||||
// *DNSProfile_SafeBrowsingBlockingModeNxdomain
|
||||
// *DNSProfile_SafeBrowsingBlockingModeNullIp
|
||||
// *DNSProfile_SafeBrowsingBlockingModeRefused
|
||||
SafeBrowsingBlockingMode isDNSProfile_SafeBrowsingBlockingMode `protobuf_oneof:"safe_browsing_blocking_mode"`
|
||||
// *
|
||||
// Field dns_id is the ID of the DNS server. Not to be confused with the ID
|
||||
// of the account, see account_id below. dns_id MUST be present.
|
||||
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
|
||||
// *
|
||||
// Field account_id is the ID of the account to which this DNS server
|
||||
// belongs. If field deleted is false, account_id MUST be present.
|
||||
AccountId string `protobuf:"bytes,23,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
|
||||
// *
|
||||
// Field devices contains the complete list of all devices in the profile, if
|
||||
// any. Fields devices and device_changes MUST NOT be set at the same time,
|
||||
// but they MAY both be empty. Field devices MUST NOT contain duplicates.
|
||||
Devices []*DeviceSettings `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"`
|
||||
// *
|
||||
// Field device_changes contains only the list of changes that have happened
|
||||
// to the profile's devices since sync_time, if any. Fields devices and
|
||||
// device_changes MUST NOT be set at the same time, but they MAY both be
|
||||
// empty. Field device_changes MUST NOT contain multiple changes for the
|
||||
// same device.
|
||||
DeviceChanges []*DeviceSettingsChange `protobuf:"bytes,24,rep,name=device_changes,json=deviceChanges,proto3" json:"device_changes,omitempty"`
|
||||
// *
|
||||
// Field custom_rules contains custom filtering rules for this DNS server, if
|
||||
// any. All items MUST contain only valid UTF-8 characters and have the size
|
||||
// less than or equal to UTF-8 1024 characters (not bytes).
|
||||
CustomRules []string `protobuf:"bytes,9,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"`
|
||||
FilteringEnabled bool `protobuf:"varint,2,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
|
||||
QueryLogEnabled bool `protobuf:"varint,3,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"`
|
||||
// *
|
||||
// Field deleted, if true, means that this DNS server has been deleted. All
|
||||
// other fields except dns_id SHOULD be absent.
|
||||
Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"`
|
||||
BlockPrivateRelay bool `protobuf:"varint,11,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"`
|
||||
BlockFirefoxCanary bool `protobuf:"varint,12,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"`
|
||||
IpLogEnabled bool `protobuf:"varint,17,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"`
|
||||
AutoDevicesEnabled bool `protobuf:"varint,19,opt,name=auto_devices_enabled,json=autoDevicesEnabled,proto3" json:"auto_devices_enabled,omitempty"`
|
||||
BlockChromePrefetch bool `protobuf:"varint,21,opt,name=block_chrome_prefetch,json=blockChromePrefetch,proto3" json:"block_chrome_prefetch,omitempty"`
|
||||
StandardAccessSettingsEnabled bool `protobuf:"varint,25,opt,name=standard_access_settings_enabled,json=standardAccessSettingsEnabled,proto3" json:"standard_access_settings_enabled,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -370,34 +420,6 @@ func (*DNSProfile) Descriptor() ([]byte, []int) {
|
||||
return file_dns_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDnsId() string {
|
||||
if x != nil {
|
||||
return x.DnsId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetFilteringEnabled() bool {
|
||||
if x != nil {
|
||||
return x.FilteringEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetQueryLogEnabled() bool {
|
||||
if x != nil {
|
||||
return x.QueryLogEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDeleted() bool {
|
||||
if x != nil {
|
||||
return x.Deleted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsing() *SafeBrowsingSettings {
|
||||
if x != nil {
|
||||
return x.SafeBrowsing
|
||||
@@ -419,20 +441,6 @@ func (x *DNSProfile) GetRuleLists() *RuleListsSettings {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDevices() []*DeviceSettings {
|
||||
if x != nil {
|
||||
return x.Devices
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetCustomRules() []string {
|
||||
if x != nil {
|
||||
return x.CustomRules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetFilteredResponseTtl() *durationpb.Duration {
|
||||
if x != nil {
|
||||
return x.FilteredResponseTtl
|
||||
@@ -440,18 +448,25 @@ func (x *DNSProfile) GetFilteredResponseTtl() *durationpb.Duration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockPrivateRelay() bool {
|
||||
func (x *DNSProfile) GetAccess() *AccessSettings {
|
||||
if x != nil {
|
||||
return x.BlockPrivateRelay
|
||||
return x.Access
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockFirefoxCanary() bool {
|
||||
func (x *DNSProfile) GetRateLimit() *RateLimitSettings {
|
||||
if x != nil {
|
||||
return x.BlockFirefoxCanary
|
||||
return x.RateLimit
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetCustomDomain() *CustomDomainSettings {
|
||||
if x != nil {
|
||||
return x.CustomDomain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockingMode() isDNSProfile_BlockingMode {
|
||||
@@ -497,48 +512,99 @@ func (x *DNSProfile) GetBlockingModeRefused() *BlockingModeREFUSED {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetIpLogEnabled() bool {
|
||||
func (x *DNSProfile) GetAdultBlockingMode() isDNSProfile_AdultBlockingMode {
|
||||
if x != nil {
|
||||
return x.IpLogEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAccess() *AccessSettings {
|
||||
if x != nil {
|
||||
return x.Access
|
||||
return x.AdultBlockingMode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAutoDevicesEnabled() bool {
|
||||
func (x *DNSProfile) GetAdultBlockingModeCustomIp() *BlockingModeCustomIP {
|
||||
if x != nil {
|
||||
return x.AutoDevicesEnabled
|
||||
if x, ok := x.AdultBlockingMode.(*DNSProfile_AdultBlockingModeCustomIp); ok {
|
||||
return x.AdultBlockingModeCustomIp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetRateLimit() *RateLimitSettings {
|
||||
if x != nil {
|
||||
return x.RateLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockChromePrefetch() bool {
|
||||
func (x *DNSProfile) GetAdultBlockingModeNxdomain() *BlockingModeNXDOMAIN {
|
||||
if x != nil {
|
||||
return x.BlockChromePrefetch
|
||||
if x, ok := x.AdultBlockingMode.(*DNSProfile_AdultBlockingModeNxdomain); ok {
|
||||
return x.AdultBlockingModeNxdomain
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetCustomDomain() *CustomDomainSettings {
|
||||
if x != nil {
|
||||
return x.CustomDomain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAdultBlockingModeNullIp() *BlockingModeNullIP {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*DNSProfile_AdultBlockingModeNullIp); ok {
|
||||
return x.AdultBlockingModeNullIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAdultBlockingModeRefused() *BlockingModeREFUSED {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*DNSProfile_AdultBlockingModeRefused); ok {
|
||||
return x.AdultBlockingModeRefused
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsingBlockingMode() isDNSProfile_SafeBrowsingBlockingMode {
|
||||
if x != nil {
|
||||
return x.SafeBrowsingBlockingMode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsingBlockingModeCustomIp() *BlockingModeCustomIP {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*DNSProfile_SafeBrowsingBlockingModeCustomIp); ok {
|
||||
return x.SafeBrowsingBlockingModeCustomIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsingBlockingModeNxdomain() *BlockingModeNXDOMAIN {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*DNSProfile_SafeBrowsingBlockingModeNxdomain); ok {
|
||||
return x.SafeBrowsingBlockingModeNxdomain
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsingBlockingModeNullIp() *BlockingModeNullIP {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*DNSProfile_SafeBrowsingBlockingModeNullIp); ok {
|
||||
return x.SafeBrowsingBlockingModeNullIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetSafeBrowsingBlockingModeRefused() *BlockingModeREFUSED {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*DNSProfile_SafeBrowsingBlockingModeRefused); ok {
|
||||
return x.SafeBrowsingBlockingModeRefused
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDnsId() string {
|
||||
if x != nil {
|
||||
return x.DnsId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAccountId() string {
|
||||
if x != nil {
|
||||
return x.AccountId
|
||||
@@ -546,6 +612,13 @@ func (x *DNSProfile) GetAccountId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDevices() []*DeviceSettings {
|
||||
if x != nil {
|
||||
return x.Devices
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDeviceChanges() []*DeviceSettingsChange {
|
||||
if x != nil {
|
||||
return x.DeviceChanges
|
||||
@@ -553,6 +626,69 @@ func (x *DNSProfile) GetDeviceChanges() []*DeviceSettingsChange {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetCustomRules() []string {
|
||||
if x != nil {
|
||||
return x.CustomRules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetFilteringEnabled() bool {
|
||||
if x != nil {
|
||||
return x.FilteringEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetQueryLogEnabled() bool {
|
||||
if x != nil {
|
||||
return x.QueryLogEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetDeleted() bool {
|
||||
if x != nil {
|
||||
return x.Deleted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockPrivateRelay() bool {
|
||||
if x != nil {
|
||||
return x.BlockPrivateRelay
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockFirefoxCanary() bool {
|
||||
if x != nil {
|
||||
return x.BlockFirefoxCanary
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetIpLogEnabled() bool {
|
||||
if x != nil {
|
||||
return x.IpLogEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetAutoDevicesEnabled() bool {
|
||||
if x != nil {
|
||||
return x.AutoDevicesEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetBlockChromePrefetch() bool {
|
||||
if x != nil {
|
||||
return x.BlockChromePrefetch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSProfile) GetStandardAccessSettingsEnabled() bool {
|
||||
if x != nil {
|
||||
return x.StandardAccessSettingsEnabled
|
||||
@@ -588,6 +724,62 @@ func (*DNSProfile_BlockingModeNullIp) isDNSProfile_BlockingMode() {}
|
||||
|
||||
func (*DNSProfile_BlockingModeRefused) isDNSProfile_BlockingMode() {}
|
||||
|
||||
type isDNSProfile_AdultBlockingMode interface {
|
||||
isDNSProfile_AdultBlockingMode()
|
||||
}
|
||||
|
||||
type DNSProfile_AdultBlockingModeCustomIp struct {
|
||||
AdultBlockingModeCustomIp *BlockingModeCustomIP `protobuf:"bytes,26,opt,name=adult_blocking_mode_custom_ip,json=adultBlockingModeCustomIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_AdultBlockingModeNxdomain struct {
|
||||
AdultBlockingModeNxdomain *BlockingModeNXDOMAIN `protobuf:"bytes,27,opt,name=adult_blocking_mode_nxdomain,json=adultBlockingModeNxdomain,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_AdultBlockingModeNullIp struct {
|
||||
AdultBlockingModeNullIp *BlockingModeNullIP `protobuf:"bytes,28,opt,name=adult_blocking_mode_null_ip,json=adultBlockingModeNullIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_AdultBlockingModeRefused struct {
|
||||
AdultBlockingModeRefused *BlockingModeREFUSED `protobuf:"bytes,29,opt,name=adult_blocking_mode_refused,json=adultBlockingModeRefused,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*DNSProfile_AdultBlockingModeCustomIp) isDNSProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_AdultBlockingModeNxdomain) isDNSProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_AdultBlockingModeNullIp) isDNSProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_AdultBlockingModeRefused) isDNSProfile_AdultBlockingMode() {}
|
||||
|
||||
type isDNSProfile_SafeBrowsingBlockingMode interface {
|
||||
isDNSProfile_SafeBrowsingBlockingMode()
|
||||
}
|
||||
|
||||
type DNSProfile_SafeBrowsingBlockingModeCustomIp struct {
|
||||
SafeBrowsingBlockingModeCustomIp *BlockingModeCustomIP `protobuf:"bytes,30,opt,name=safe_browsing_blocking_mode_custom_ip,json=safeBrowsingBlockingModeCustomIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_SafeBrowsingBlockingModeNxdomain struct {
|
||||
SafeBrowsingBlockingModeNxdomain *BlockingModeNXDOMAIN `protobuf:"bytes,31,opt,name=safe_browsing_blocking_mode_nxdomain,json=safeBrowsingBlockingModeNxdomain,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_SafeBrowsingBlockingModeNullIp struct {
|
||||
SafeBrowsingBlockingModeNullIp *BlockingModeNullIP `protobuf:"bytes,32,opt,name=safe_browsing_blocking_mode_null_ip,json=safeBrowsingBlockingModeNullIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DNSProfile_SafeBrowsingBlockingModeRefused struct {
|
||||
SafeBrowsingBlockingModeRefused *BlockingModeREFUSED `protobuf:"bytes,33,opt,name=safe_browsing_blocking_mode_refused,json=safeBrowsingBlockingModeRefused,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*DNSProfile_SafeBrowsingBlockingModeCustomIp) isDNSProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_SafeBrowsingBlockingModeNxdomain) isDNSProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_SafeBrowsingBlockingModeNullIp) isDNSProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*DNSProfile_SafeBrowsingBlockingModeRefused) isDNSProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
type DeviceSettingsChange struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Change:
|
||||
@@ -1301,9 +1493,20 @@ func (x *RuleListsSettings) GetIds() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// *
|
||||
// Message BlockingModeCustomIP contains custom IP addresses typically leading
|
||||
// to a blocking page.
|
||||
type BlockingModeCustomIP struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// *
|
||||
// Field ipv4 defines the IPv4 address to use to respond to a blocked
|
||||
// request. If absent, blocked A requests are responded with a NODATA
|
||||
// response.
|
||||
Ipv4 []byte `protobuf:"bytes,1,opt,name=ipv4,proto3" json:"ipv4,omitempty"`
|
||||
// *
|
||||
// Field ipv6 defines the IPv6 address to use to respond to a blocked
|
||||
// request. If absent, blocked AAAA requests are responded with a NODATA
|
||||
// response.
|
||||
Ipv6 []byte `protobuf:"bytes,2,opt,name=ipv6,proto3" json:"ipv6,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -2819,40 +3022,49 @@ const file_dns_proto_rawDesc = "" +
|
||||
"\x1cGlobalAccessSettingsResponse\x12+\n" +
|
||||
"\bstandard\x18\x01 \x01(\v2\x0f.AccessSettingsR\bstandard\"M\n" +
|
||||
"\x12DNSProfilesRequest\x127\n" +
|
||||
"\tsync_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xc3\n" +
|
||||
"\tsync_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\"\xf7\x10\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"DNSProfile\x12\x15\n" +
|
||||
"\x06dns_id\x18\x01 \x01(\tR\x05dnsId\x12+\n" +
|
||||
"\x11filtering_enabled\x18\x02 \x01(\bR\x10filteringEnabled\x12*\n" +
|
||||
"\x11query_log_enabled\x18\x03 \x01(\bR\x0fqueryLogEnabled\x12\x18\n" +
|
||||
"\adeleted\x18\x04 \x01(\bR\adeleted\x12:\n" +
|
||||
"DNSProfile\x12:\n" +
|
||||
"\rsafe_browsing\x18\x05 \x01(\v2\x15.SafeBrowsingSettingsR\fsafeBrowsing\x12-\n" +
|
||||
"\bparental\x18\x06 \x01(\v2\x11.ParentalSettingsR\bparental\x121\n" +
|
||||
"\n" +
|
||||
"rule_lists\x18\a \x01(\v2\x12.RuleListsSettingsR\truleLists\x12)\n" +
|
||||
"\adevices\x18\b \x03(\v2\x0f.DeviceSettingsR\adevices\x12!\n" +
|
||||
"\fcustom_rules\x18\t \x03(\tR\vcustomRules\x12M\n" +
|
||||
"rule_lists\x18\a \x01(\v2\x12.RuleListsSettingsR\truleLists\x12M\n" +
|
||||
"\x15filtered_response_ttl\x18\n" +
|
||||
" \x01(\v2\x19.google.protobuf.DurationR\x13filteredResponseTtl\x12.\n" +
|
||||
"\x13block_private_relay\x18\v \x01(\bR\x11blockPrivateRelay\x120\n" +
|
||||
"\x14block_firefox_canary\x18\f \x01(\bR\x12blockFirefoxCanary\x12N\n" +
|
||||
" \x01(\v2\x19.google.protobuf.DurationR\x13filteredResponseTtl\x12'\n" +
|
||||
"\x06access\x18\x12 \x01(\v2\x0f.AccessSettingsR\x06access\x121\n" +
|
||||
"\n" +
|
||||
"rate_limit\x18\x14 \x01(\v2\x12.RateLimitSettingsR\trateLimit\x12:\n" +
|
||||
"\rcustom_domain\x18\x16 \x01(\v2\x15.CustomDomainSettingsR\fcustomDomain\x12N\n" +
|
||||
"\x17blocking_mode_custom_ip\x18\r \x01(\v2\x15.BlockingModeCustomIPH\x00R\x14blockingModeCustomIp\x12M\n" +
|
||||
"\x16blocking_mode_nxdomain\x18\x0e \x01(\v2\x15.BlockingModeNXDOMAINH\x00R\x14blockingModeNxdomain\x12H\n" +
|
||||
"\x15blocking_mode_null_ip\x18\x0f \x01(\v2\x13.BlockingModeNullIPH\x00R\x12blockingModeNullIp\x12J\n" +
|
||||
"\x15blocking_mode_refused\x18\x10 \x01(\v2\x14.BlockingModeREFUSEDH\x00R\x13blockingModeRefused\x12$\n" +
|
||||
"\x0eip_log_enabled\x18\x11 \x01(\bR\fipLogEnabled\x12'\n" +
|
||||
"\x06access\x18\x12 \x01(\v2\x0f.AccessSettingsR\x06access\x120\n" +
|
||||
"\x14auto_devices_enabled\x18\x13 \x01(\bR\x12autoDevicesEnabled\x121\n" +
|
||||
"\x15blocking_mode_refused\x18\x10 \x01(\v2\x14.BlockingModeREFUSEDH\x00R\x13blockingModeRefused\x12Y\n" +
|
||||
"\x1dadult_blocking_mode_custom_ip\x18\x1a \x01(\v2\x15.BlockingModeCustomIPH\x01R\x19adultBlockingModeCustomIp\x12X\n" +
|
||||
"\x1cadult_blocking_mode_nxdomain\x18\x1b \x01(\v2\x15.BlockingModeNXDOMAINH\x01R\x19adultBlockingModeNxdomain\x12S\n" +
|
||||
"\x1badult_blocking_mode_null_ip\x18\x1c \x01(\v2\x13.BlockingModeNullIPH\x01R\x17adultBlockingModeNullIp\x12U\n" +
|
||||
"\x1badult_blocking_mode_refused\x18\x1d \x01(\v2\x14.BlockingModeREFUSEDH\x01R\x18adultBlockingModeRefused\x12h\n" +
|
||||
"%safe_browsing_blocking_mode_custom_ip\x18\x1e \x01(\v2\x15.BlockingModeCustomIPH\x02R safeBrowsingBlockingModeCustomIp\x12g\n" +
|
||||
"$safe_browsing_blocking_mode_nxdomain\x18\x1f \x01(\v2\x15.BlockingModeNXDOMAINH\x02R safeBrowsingBlockingModeNxdomain\x12b\n" +
|
||||
"#safe_browsing_blocking_mode_null_ip\x18 \x01(\v2\x13.BlockingModeNullIPH\x02R\x1esafeBrowsingBlockingModeNullIp\x12d\n" +
|
||||
"#safe_browsing_blocking_mode_refused\x18! \x01(\v2\x14.BlockingModeREFUSEDH\x02R\x1fsafeBrowsingBlockingModeRefused\x12\x15\n" +
|
||||
"\x06dns_id\x18\x01 \x01(\tR\x05dnsId\x12\x1d\n" +
|
||||
"\n" +
|
||||
"rate_limit\x18\x14 \x01(\v2\x12.RateLimitSettingsR\trateLimit\x122\n" +
|
||||
"\x15block_chrome_prefetch\x18\x15 \x01(\bR\x13blockChromePrefetch\x12:\n" +
|
||||
"\rcustom_domain\x18\x16 \x01(\v2\x15.CustomDomainSettingsR\fcustomDomain\x12\x1d\n" +
|
||||
"\n" +
|
||||
"account_id\x18\x17 \x01(\tR\taccountId\x12<\n" +
|
||||
"\x0edevice_changes\x18\x18 \x03(\v2\x15.DeviceSettingsChangeR\rdeviceChanges\x12G\n" +
|
||||
"account_id\x18\x17 \x01(\tR\taccountId\x12)\n" +
|
||||
"\adevices\x18\b \x03(\v2\x0f.DeviceSettingsR\adevices\x12<\n" +
|
||||
"\x0edevice_changes\x18\x18 \x03(\v2\x15.DeviceSettingsChangeR\rdeviceChanges\x12!\n" +
|
||||
"\fcustom_rules\x18\t \x03(\tR\vcustomRules\x12+\n" +
|
||||
"\x11filtering_enabled\x18\x02 \x01(\bR\x10filteringEnabled\x12*\n" +
|
||||
"\x11query_log_enabled\x18\x03 \x01(\bR\x0fqueryLogEnabled\x12\x18\n" +
|
||||
"\adeleted\x18\x04 \x01(\bR\adeleted\x12.\n" +
|
||||
"\x13block_private_relay\x18\v \x01(\bR\x11blockPrivateRelay\x120\n" +
|
||||
"\x14block_firefox_canary\x18\f \x01(\bR\x12blockFirefoxCanary\x12$\n" +
|
||||
"\x0eip_log_enabled\x18\x11 \x01(\bR\fipLogEnabled\x120\n" +
|
||||
"\x14auto_devices_enabled\x18\x13 \x01(\bR\x12autoDevicesEnabled\x122\n" +
|
||||
"\x15block_chrome_prefetch\x18\x15 \x01(\bR\x13blockChromePrefetch\x12G\n" +
|
||||
" standard_access_settings_enabled\x18\x19 \x01(\bR\x1dstandardAccessSettingsEnabledB\x0f\n" +
|
||||
"\rblocking_mode\"\xf6\x01\n" +
|
||||
"\rblocking_modeB\x15\n" +
|
||||
"\x13adult_blocking_modeB\x1d\n" +
|
||||
"\x1bsafe_browsing_blocking_mode\"\xf6\x01\n" +
|
||||
"\x14DeviceSettingsChange\x129\n" +
|
||||
"\adeleted\x18\x01 \x01(\v2\x1d.DeviceSettingsChange.DeletedH\x00R\adeleted\x12<\n" +
|
||||
"\bupserted\x18\x02 \x01(\v2\x1e.DeviceSettingsChange.UpsertedH\x00R\bupserted\x1a&\n" +
|
||||
@@ -3093,70 +3305,78 @@ var file_dns_proto_depIdxs = []int32{
|
||||
10, // 3: DNSProfile.safe_browsing:type_name -> SafeBrowsingSettings
|
||||
12, // 4: DNSProfile.parental:type_name -> ParentalSettings
|
||||
16, // 5: DNSProfile.rule_lists:type_name -> RuleListsSettings
|
||||
11, // 6: DNSProfile.devices:type_name -> DeviceSettings
|
||||
47, // 7: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
|
||||
17, // 8: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
|
||||
18, // 9: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
|
||||
19, // 10: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP
|
||||
20, // 11: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED
|
||||
22, // 12: DNSProfile.access:type_name -> AccessSettings
|
||||
32, // 13: DNSProfile.rate_limit:type_name -> RateLimitSettings
|
||||
8, // 14: DNSProfile.custom_domain:type_name -> CustomDomainSettings
|
||||
7, // 15: DNSProfile.device_changes:type_name -> DeviceSettingsChange
|
||||
42, // 16: DeviceSettingsChange.deleted:type_name -> DeviceSettingsChange.Deleted
|
||||
43, // 17: DeviceSettingsChange.upserted:type_name -> DeviceSettingsChange.Upserted
|
||||
9, // 18: CustomDomainSettings.domains:type_name -> CustomDomain
|
||||
44, // 19: CustomDomain.pending:type_name -> CustomDomain.Pending
|
||||
45, // 20: CustomDomain.current:type_name -> CustomDomain.Current
|
||||
24, // 21: DeviceSettings.authentication:type_name -> AuthenticationSettings
|
||||
13, // 22: ParentalSettings.schedule:type_name -> ScheduleSettings
|
||||
14, // 23: ScheduleSettings.weekly_range:type_name -> WeeklyRange
|
||||
15, // 24: WeeklyRange.mon:type_name -> DayRange
|
||||
15, // 25: WeeklyRange.tue:type_name -> DayRange
|
||||
15, // 26: WeeklyRange.wed:type_name -> DayRange
|
||||
15, // 27: WeeklyRange.thu:type_name -> DayRange
|
||||
15, // 28: WeeklyRange.fri:type_name -> DayRange
|
||||
15, // 29: WeeklyRange.sat:type_name -> DayRange
|
||||
15, // 30: WeeklyRange.sun:type_name -> DayRange
|
||||
47, // 31: DayRange.start:type_name -> google.protobuf.Duration
|
||||
47, // 32: DayRange.end:type_name -> google.protobuf.Duration
|
||||
46, // 33: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
|
||||
23, // 34: AccessSettings.allowlist_cidr:type_name -> CidrRange
|
||||
23, // 35: AccessSettings.blocklist_cidr:type_name -> CidrRange
|
||||
0, // 36: CreateDeviceRequest.device_type:type_name -> DeviceType
|
||||
11, // 37: CreateDeviceResponse.device:type_name -> DeviceSettings
|
||||
47, // 38: RateLimitedError.retry_delay:type_name -> google.protobuf.Duration
|
||||
23, // 39: RateLimitSettings.client_cidr:type_name -> CidrRange
|
||||
48, // 40: RemoteKVGetResponse.empty:type_name -> google.protobuf.Empty
|
||||
47, // 41: RemoteKVSetRequest.ttl:type_name -> google.protobuf.Duration
|
||||
41, // 42: SessionTicketResponse.tickets:type_name -> SessionTicket
|
||||
11, // 43: DeviceSettingsChange.Upserted.device:type_name -> DeviceSettings
|
||||
46, // 44: CustomDomain.Pending.expire:type_name -> google.protobuf.Timestamp
|
||||
46, // 45: CustomDomain.Current.not_before:type_name -> google.protobuf.Timestamp
|
||||
46, // 46: CustomDomain.Current.not_after:type_name -> google.protobuf.Timestamp
|
||||
5, // 47: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
|
||||
21, // 48: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
|
||||
25, // 49: DNSService.createDeviceByHumanId:input_type -> CreateDeviceRequest
|
||||
1, // 50: RateLimitService.getRateLimitSettings:input_type -> RateLimitSettingsRequest
|
||||
3, // 51: RateLimitService.getGlobalAccessSettings:input_type -> GlobalAccessSettingsRequest
|
||||
33, // 52: RemoteKVService.get:input_type -> RemoteKVGetRequest
|
||||
35, // 53: RemoteKVService.set:input_type -> RemoteKVSetRequest
|
||||
37, // 54: CustomDomainService.getCustomDomainCertificate:input_type -> CustomDomainCertificateRequest
|
||||
39, // 55: SessionTicketService.getSessionTickets:input_type -> SessionTicketRequest
|
||||
6, // 56: DNSService.getDNSProfiles:output_type -> DNSProfile
|
||||
48, // 57: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
|
||||
26, // 58: DNSService.createDeviceByHumanId:output_type -> CreateDeviceResponse
|
||||
2, // 59: RateLimitService.getRateLimitSettings:output_type -> RateLimitSettingsResponse
|
||||
4, // 60: RateLimitService.getGlobalAccessSettings:output_type -> GlobalAccessSettingsResponse
|
||||
34, // 61: RemoteKVService.get:output_type -> RemoteKVGetResponse
|
||||
36, // 62: RemoteKVService.set:output_type -> RemoteKVSetResponse
|
||||
38, // 63: CustomDomainService.getCustomDomainCertificate:output_type -> CustomDomainCertificateResponse
|
||||
40, // 64: SessionTicketService.getSessionTickets:output_type -> SessionTicketResponse
|
||||
56, // [56:65] is the sub-list for method output_type
|
||||
47, // [47:56] is the sub-list for method input_type
|
||||
47, // [47:47] is the sub-list for extension type_name
|
||||
47, // [47:47] is the sub-list for extension extendee
|
||||
0, // [0:47] is the sub-list for field type_name
|
||||
47, // 6: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
|
||||
22, // 7: DNSProfile.access:type_name -> AccessSettings
|
||||
32, // 8: DNSProfile.rate_limit:type_name -> RateLimitSettings
|
||||
8, // 9: DNSProfile.custom_domain:type_name -> CustomDomainSettings
|
||||
17, // 10: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
|
||||
18, // 11: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
|
||||
19, // 12: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP
|
||||
20, // 13: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED
|
||||
17, // 14: DNSProfile.adult_blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
|
||||
18, // 15: DNSProfile.adult_blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
|
||||
19, // 16: DNSProfile.adult_blocking_mode_null_ip:type_name -> BlockingModeNullIP
|
||||
20, // 17: DNSProfile.adult_blocking_mode_refused:type_name -> BlockingModeREFUSED
|
||||
17, // 18: DNSProfile.safe_browsing_blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
|
||||
18, // 19: DNSProfile.safe_browsing_blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
|
||||
19, // 20: DNSProfile.safe_browsing_blocking_mode_null_ip:type_name -> BlockingModeNullIP
|
||||
20, // 21: DNSProfile.safe_browsing_blocking_mode_refused:type_name -> BlockingModeREFUSED
|
||||
11, // 22: DNSProfile.devices:type_name -> DeviceSettings
|
||||
7, // 23: DNSProfile.device_changes:type_name -> DeviceSettingsChange
|
||||
42, // 24: DeviceSettingsChange.deleted:type_name -> DeviceSettingsChange.Deleted
|
||||
43, // 25: DeviceSettingsChange.upserted:type_name -> DeviceSettingsChange.Upserted
|
||||
9, // 26: CustomDomainSettings.domains:type_name -> CustomDomain
|
||||
44, // 27: CustomDomain.pending:type_name -> CustomDomain.Pending
|
||||
45, // 28: CustomDomain.current:type_name -> CustomDomain.Current
|
||||
24, // 29: DeviceSettings.authentication:type_name -> AuthenticationSettings
|
||||
13, // 30: ParentalSettings.schedule:type_name -> ScheduleSettings
|
||||
14, // 31: ScheduleSettings.weekly_range:type_name -> WeeklyRange
|
||||
15, // 32: WeeklyRange.mon:type_name -> DayRange
|
||||
15, // 33: WeeklyRange.tue:type_name -> DayRange
|
||||
15, // 34: WeeklyRange.wed:type_name -> DayRange
|
||||
15, // 35: WeeklyRange.thu:type_name -> DayRange
|
||||
15, // 36: WeeklyRange.fri:type_name -> DayRange
|
||||
15, // 37: WeeklyRange.sat:type_name -> DayRange
|
||||
15, // 38: WeeklyRange.sun:type_name -> DayRange
|
||||
47, // 39: DayRange.start:type_name -> google.protobuf.Duration
|
||||
47, // 40: DayRange.end:type_name -> google.protobuf.Duration
|
||||
46, // 41: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
|
||||
23, // 42: AccessSettings.allowlist_cidr:type_name -> CidrRange
|
||||
23, // 43: AccessSettings.blocklist_cidr:type_name -> CidrRange
|
||||
0, // 44: CreateDeviceRequest.device_type:type_name -> DeviceType
|
||||
11, // 45: CreateDeviceResponse.device:type_name -> DeviceSettings
|
||||
47, // 46: RateLimitedError.retry_delay:type_name -> google.protobuf.Duration
|
||||
23, // 47: RateLimitSettings.client_cidr:type_name -> CidrRange
|
||||
48, // 48: RemoteKVGetResponse.empty:type_name -> google.protobuf.Empty
|
||||
47, // 49: RemoteKVSetRequest.ttl:type_name -> google.protobuf.Duration
|
||||
41, // 50: SessionTicketResponse.tickets:type_name -> SessionTicket
|
||||
11, // 51: DeviceSettingsChange.Upserted.device:type_name -> DeviceSettings
|
||||
46, // 52: CustomDomain.Pending.expire:type_name -> google.protobuf.Timestamp
|
||||
46, // 53: CustomDomain.Current.not_before:type_name -> google.protobuf.Timestamp
|
||||
46, // 54: CustomDomain.Current.not_after:type_name -> google.protobuf.Timestamp
|
||||
5, // 55: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
|
||||
21, // 56: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
|
||||
25, // 57: DNSService.createDeviceByHumanId:input_type -> CreateDeviceRequest
|
||||
1, // 58: RateLimitService.getRateLimitSettings:input_type -> RateLimitSettingsRequest
|
||||
3, // 59: RateLimitService.getGlobalAccessSettings:input_type -> GlobalAccessSettingsRequest
|
||||
33, // 60: RemoteKVService.get:input_type -> RemoteKVGetRequest
|
||||
35, // 61: RemoteKVService.set:input_type -> RemoteKVSetRequest
|
||||
37, // 62: CustomDomainService.getCustomDomainCertificate:input_type -> CustomDomainCertificateRequest
|
||||
39, // 63: SessionTicketService.getSessionTickets:input_type -> SessionTicketRequest
|
||||
6, // 64: DNSService.getDNSProfiles:output_type -> DNSProfile
|
||||
48, // 65: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
|
||||
26, // 66: DNSService.createDeviceByHumanId:output_type -> CreateDeviceResponse
|
||||
2, // 67: RateLimitService.getRateLimitSettings:output_type -> RateLimitSettingsResponse
|
||||
4, // 68: RateLimitService.getGlobalAccessSettings:output_type -> GlobalAccessSettingsResponse
|
||||
34, // 69: RemoteKVService.get:output_type -> RemoteKVGetResponse
|
||||
36, // 70: RemoteKVService.set:output_type -> RemoteKVSetResponse
|
||||
38, // 71: CustomDomainService.getCustomDomainCertificate:output_type -> CustomDomainCertificateResponse
|
||||
40, // 72: SessionTicketService.getSessionTickets:output_type -> SessionTicketResponse
|
||||
64, // [64:73] is the sub-list for method output_type
|
||||
55, // [55:64] is the sub-list for method input_type
|
||||
55, // [55:55] is the sub-list for extension type_name
|
||||
55, // [55:55] is the sub-list for extension extendee
|
||||
0, // [0:55] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_dns_proto_init() }
|
||||
@@ -3169,6 +3389,14 @@ func file_dns_proto_init() {
|
||||
(*DNSProfile_BlockingModeNxdomain)(nil),
|
||||
(*DNSProfile_BlockingModeNullIp)(nil),
|
||||
(*DNSProfile_BlockingModeRefused)(nil),
|
||||
(*DNSProfile_AdultBlockingModeCustomIp)(nil),
|
||||
(*DNSProfile_AdultBlockingModeNxdomain)(nil),
|
||||
(*DNSProfile_AdultBlockingModeNullIp)(nil),
|
||||
(*DNSProfile_AdultBlockingModeRefused)(nil),
|
||||
(*DNSProfile_SafeBrowsingBlockingModeCustomIp)(nil),
|
||||
(*DNSProfile_SafeBrowsingBlockingModeNxdomain)(nil),
|
||||
(*DNSProfile_SafeBrowsingBlockingModeNullIp)(nil),
|
||||
(*DNSProfile_SafeBrowsingBlockingModeRefused)(nil),
|
||||
}
|
||||
file_dns_proto_msgTypes[6].OneofWrappers = []any{
|
||||
(*DeviceSettingsChange_Deleted_)(nil),
|
||||
|
||||
@@ -111,44 +111,117 @@ message DNSProfilesRequest {
|
||||
google.protobuf.Timestamp sync_time = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message DNSProfile contains both the data of the account and the data of the
|
||||
* DNS server.
|
||||
*
|
||||
* The fields are ordered in a way that optimizes the generated structures'
|
||||
* layouts.
|
||||
*
|
||||
* TODO(a.garipov): Expand the field documentation.
|
||||
*/
|
||||
message DNSProfile {
|
||||
string dns_id = 1;
|
||||
bool filtering_enabled = 2;
|
||||
bool query_log_enabled = 3;
|
||||
bool deleted = 4;
|
||||
// Message fields
|
||||
|
||||
SafeBrowsingSettings safe_browsing = 5;
|
||||
ParentalSettings parental = 6;
|
||||
RuleListsSettings rule_lists = 7;
|
||||
|
||||
/*
|
||||
* Field devices contains the COMPLETE list of all devices in the profile.
|
||||
* Field device_changes contains only the list of changes that have happened
|
||||
* to the profile's devices since sync_time.
|
||||
*
|
||||
* devices and device_changes MUST NOT be set at the same time, but they MAY
|
||||
* both be empty.
|
||||
*
|
||||
* device_changes MUST NOT contain multiple changes for the same device.
|
||||
*/
|
||||
repeated DeviceSettings devices = 8;
|
||||
repeated string custom_rules = 9;
|
||||
google.protobuf.Duration filtered_response_ttl = 10;
|
||||
bool block_private_relay = 11;
|
||||
bool block_firefox_canary = 12;
|
||||
AccessSettings access = 18;
|
||||
RateLimitSettings rate_limit = 20;
|
||||
CustomDomainSettings custom_domain = 22;
|
||||
|
||||
// One-of fields
|
||||
|
||||
/**
|
||||
* Field blocking_mode defines the blocking mode for general rule-list based
|
||||
* filtering. If field deleted is false, field blocking_mode MUST be
|
||||
* present.
|
||||
*/
|
||||
oneof blocking_mode {
|
||||
BlockingModeCustomIP blocking_mode_custom_ip = 13;
|
||||
BlockingModeNXDOMAIN blocking_mode_nxdomain = 14;
|
||||
BlockingModeNullIP blocking_mode_null_ip = 15;
|
||||
BlockingModeREFUSED blocking_mode_refused = 16;
|
||||
}
|
||||
bool ip_log_enabled = 17;
|
||||
AccessSettings access = 18;
|
||||
bool auto_devices_enabled = 19;
|
||||
RateLimitSettings rate_limit = 20;
|
||||
bool block_chrome_prefetch = 21;
|
||||
CustomDomainSettings custom_domain = 22;
|
||||
|
||||
/**
|
||||
* Field adult_blocking_mode defines the blocking mode for the adult-content
|
||||
* filter. If absent, the default is used.
|
||||
*/
|
||||
oneof adult_blocking_mode {
|
||||
BlockingModeCustomIP adult_blocking_mode_custom_ip = 26;
|
||||
BlockingModeNXDOMAIN adult_blocking_mode_nxdomain = 27;
|
||||
BlockingModeNullIP adult_blocking_mode_null_ip = 28;
|
||||
BlockingModeREFUSED adult_blocking_mode_refused = 29;
|
||||
}
|
||||
|
||||
/**
|
||||
* Field safe_browsing_blocking_mode defines the blocking mode for the
|
||||
* safe-browsing filter. If absent, the default is used.
|
||||
*/
|
||||
oneof safe_browsing_blocking_mode {
|
||||
BlockingModeCustomIP safe_browsing_blocking_mode_custom_ip = 30;
|
||||
BlockingModeNXDOMAIN safe_browsing_blocking_mode_nxdomain = 31;
|
||||
BlockingModeNullIP safe_browsing_blocking_mode_null_ip = 32;
|
||||
BlockingModeREFUSED safe_browsing_blocking_mode_refused = 33;
|
||||
}
|
||||
|
||||
// String fields
|
||||
|
||||
/**
|
||||
* Field dns_id is the ID of the DNS server. Not to be confused with the ID
|
||||
* of the account, see account_id below. dns_id MUST be present.
|
||||
*/
|
||||
string dns_id = 1;
|
||||
|
||||
/**
|
||||
* Field account_id is the ID of the account to which this DNS server
|
||||
* belongs. If field deleted is false, account_id MUST be present.
|
||||
*/
|
||||
string account_id = 23;
|
||||
|
||||
// Repeated fields
|
||||
|
||||
/**
|
||||
* Field devices contains the complete list of all devices in the profile, if
|
||||
* any. Fields devices and device_changes MUST NOT be set at the same time,
|
||||
* but they MAY both be empty. Field devices MUST NOT contain duplicates.
|
||||
*/
|
||||
repeated DeviceSettings devices = 8;
|
||||
|
||||
/**
|
||||
* Field device_changes contains only the list of changes that have happened
|
||||
* to the profile's devices since sync_time, if any. Fields devices and
|
||||
* device_changes MUST NOT be set at the same time, but they MAY both be
|
||||
* empty. Field device_changes MUST NOT contain multiple changes for the
|
||||
* same device.
|
||||
*/
|
||||
repeated DeviceSettingsChange device_changes = 24;
|
||||
|
||||
/**
|
||||
* Field custom_rules contains custom filtering rules for this DNS server, if
|
||||
* any. All items MUST contain only valid UTF-8 characters and have the size
|
||||
* less than or equal to UTF-8 1024 characters (not bytes).
|
||||
*/
|
||||
repeated string custom_rules = 9;
|
||||
|
||||
// Boolean fields
|
||||
|
||||
bool filtering_enabled = 2;
|
||||
bool query_log_enabled = 3;
|
||||
|
||||
/**
|
||||
* Field deleted, if true, means that this DNS server has been deleted. All
|
||||
* other fields except dns_id SHOULD be absent.
|
||||
*/
|
||||
bool deleted = 4;
|
||||
|
||||
bool block_private_relay = 11;
|
||||
bool block_firefox_canary = 12;
|
||||
bool ip_log_enabled = 17;
|
||||
bool auto_devices_enabled = 19;
|
||||
bool block_chrome_prefetch = 21;
|
||||
bool standard_access_settings_enabled = 25;
|
||||
}
|
||||
|
||||
@@ -248,8 +321,23 @@ message RuleListsSettings {
|
||||
repeated string ids = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message BlockingModeCustomIP contains custom IP addresses typically leading
|
||||
* to a blocking page.
|
||||
*/
|
||||
message BlockingModeCustomIP {
|
||||
/**
|
||||
* Field ipv4 defines the IPv4 address to use to respond to a blocked
|
||||
* request. If absent, blocked A requests are responded with a NODATA
|
||||
* response.
|
||||
*/
|
||||
bytes ipv4 = 1;
|
||||
|
||||
/**
|
||||
* Field ipv6 defines the IPv6 address to use to respond to a blocked
|
||||
* request. If absent, blocked AAAA requests are responded with a NODATA
|
||||
* response.
|
||||
*/
|
||||
bytes ipv6 = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -359,9 +359,82 @@ func (pbm *BlockingModeCustomIP) toInternal() (m dnsmsg.BlockingMode, err error)
|
||||
return custom, nil
|
||||
}
|
||||
|
||||
// adultBlockingModeToInternal converts a protobuf adult blocking-mode sum-type
|
||||
// to an internal one. If pbm is nil, it returns nil.
|
||||
func adultBlockingModeToInternal(
|
||||
pbm isDNSProfile_AdultBlockingMode,
|
||||
) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case *DNSProfile_AdultBlockingModeCustomIp:
|
||||
return pbm.AdultBlockingModeCustomIp.toInternal()
|
||||
case *DNSProfile_AdultBlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *DNSProfile_AdultBlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_AdultBlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// safeBrowsingBlockingModeToInternal converts a protobuf safe browsing
|
||||
// blocking-mode sum-type to an internal one. If pbm is nil, it returns nil.
|
||||
func safeBrowsingBlockingModeToInternal(
|
||||
pbm isDNSProfile_SafeBrowsingBlockingMode,
|
||||
) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case *DNSProfile_SafeBrowsingBlockingModeCustomIp:
|
||||
return pbm.SafeBrowsingBlockingModeCustomIp.toInternal()
|
||||
case *DNSProfile_SafeBrowsingBlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *DNSProfile_SafeBrowsingBlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *DNSProfile_SafeBrowsingBlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// blockingModesToInternal converts a protobuf blocking-mode sum-types to
|
||||
// internal blocking-mode objects.
|
||||
func blockingModesToInternal(p *DNSProfile) (
|
||||
m dnsmsg.BlockingMode,
|
||||
adultBlockingMode dnsmsg.BlockingMode,
|
||||
safeBrowsingBlockingMode dnsmsg.BlockingMode,
|
||||
err error,
|
||||
) {
|
||||
m, err = blockingModeToInternal(p.BlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("blocking mode: %w", err)
|
||||
}
|
||||
|
||||
adultBlockingMode, err = adultBlockingModeToInternal(p.AdultBlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("adult blocking mode: %w", err)
|
||||
}
|
||||
|
||||
safeBrowsingBlockingMode, err = safeBrowsingBlockingModeToInternal(p.SafeBrowsingBlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("safe browsing blocking mode: %w", err)
|
||||
}
|
||||
|
||||
return m, adultBlockingMode, safeBrowsingBlockingMode, nil
|
||||
}
|
||||
|
||||
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one. If pbm is nil, blockingModeToInternal returns a null-IP
|
||||
// blocking mode.
|
||||
//
|
||||
// TODO(d.kolyshev): DRY with adultBlockingModeToInternal and
|
||||
// safeBrowsingBlockingModeToInternal.
|
||||
func blockingModeToInternal(pbm isDNSProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
|
||||
@@ -230,14 +230,10 @@ func (s *ProfileStorage) newProfile(
|
||||
return nil, nil, nil, errors.ErrNoValue
|
||||
}
|
||||
|
||||
parental, err := p.Parental.toInternal(ctx, s.errColl, s.logger)
|
||||
m, adultBlockingMode, safeBrowsingBlockingMode, err := blockingModesToInternal(p)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("parental: %w", err)
|
||||
}
|
||||
|
||||
m, err := blockingModeToInternal(p.BlockingMode)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("blocking mode: %w", err)
|
||||
// Do not wrap the error, because it's informative enough as is.
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
devChg = &profiledb.StorageDeviceChange{}
|
||||
@@ -282,12 +278,62 @@ func (s *ProfileStorage) newProfile(
|
||||
return nil, nil, nil, fmt.Errorf("account id: %w", err)
|
||||
}
|
||||
|
||||
filterConf, err := s.newFilterConfig(ctx, p, profID, s.logger, s.errColl)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("filter config: %w", err)
|
||||
}
|
||||
|
||||
var fltRespTTL time.Duration
|
||||
if respTTL := p.FilteredResponseTtl; respTTL != nil {
|
||||
fltRespTTL = respTTL.AsDuration()
|
||||
}
|
||||
|
||||
customRules := rulesToInternal(ctx, p.CustomRules, s.errColl, s.logger)
|
||||
accessProf := p.Access.toInternal(
|
||||
ctx,
|
||||
s.logger,
|
||||
s.errColl,
|
||||
s.profAccessCons,
|
||||
p.StandardAccessSettingsEnabled,
|
||||
)
|
||||
|
||||
return &agd.Profile{
|
||||
CustomDomains: p.CustomDomain.toInternal(ctx, s.errColl, s.logger),
|
||||
DeviceIDs: container.NewMapSet(deviceIDs...),
|
||||
FilterConfig: filterConf,
|
||||
Access: accessProf,
|
||||
AdultBlockingMode: adultBlockingMode,
|
||||
BlockingMode: m,
|
||||
SafeBrowsingBlockingMode: safeBrowsingBlockingMode,
|
||||
Ratelimiter: p.RateLimit.toInternal(ctx, s.errColl, s.logger, s.respSzEst),
|
||||
AccountID: accID,
|
||||
ID: profID,
|
||||
FilteredResponseTTL: fltRespTTL,
|
||||
AutoDevicesEnabled: p.AutoDevicesEnabled,
|
||||
BlockChromePrefetch: p.BlockChromePrefetch,
|
||||
BlockFirefoxCanary: p.BlockFirefoxCanary,
|
||||
BlockPrivateRelay: p.BlockPrivateRelay,
|
||||
Deleted: p.Deleted,
|
||||
FilteringEnabled: p.FilteringEnabled,
|
||||
IPLogEnabled: p.IpLogEnabled,
|
||||
QueryLogEnabled: p.QueryLogEnabled,
|
||||
}, devices, devChg, nil
|
||||
}
|
||||
|
||||
// newFilterConfig creates a new filter configuration from the protobuf profile.
|
||||
// p, logger and errColl must not be nil.
|
||||
func (s *ProfileStorage) newFilterConfig(
|
||||
ctx context.Context,
|
||||
p *DNSProfile,
|
||||
profID agd.ProfileID,
|
||||
logger *slog.Logger,
|
||||
errColl errcoll.Interface,
|
||||
) (conf *filter.ConfigClient, err error) {
|
||||
parental, err := p.Parental.toInternal(ctx, s.errColl, s.logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parental: %w", err)
|
||||
}
|
||||
|
||||
customRules := rulesToInternal(ctx, p.CustomRules, errColl, logger)
|
||||
customEnabled := len(customRules) > 0
|
||||
|
||||
var customFilter filter.Custom
|
||||
@@ -304,38 +350,12 @@ func (s *ProfileStorage) newProfile(
|
||||
Enabled: customEnabled,
|
||||
}
|
||||
|
||||
accessProf := p.Access.toInternal(
|
||||
ctx,
|
||||
s.logger,
|
||||
s.errColl,
|
||||
s.profAccessCons,
|
||||
p.StandardAccessSettingsEnabled,
|
||||
)
|
||||
|
||||
return &agd.Profile{
|
||||
CustomDomains: p.CustomDomain.toInternal(ctx, s.errColl, s.logger),
|
||||
DeviceIDs: container.NewMapSet(deviceIDs...),
|
||||
FilterConfig: &filter.ConfigClient{
|
||||
return &filter.ConfigClient{
|
||||
Custom: customConf,
|
||||
Parental: parental,
|
||||
RuleList: p.RuleLists.toInternal(ctx, s.errColl, s.logger),
|
||||
RuleList: p.RuleLists.toInternal(ctx, errColl, logger),
|
||||
SafeBrowsing: p.SafeBrowsing.toInternal(),
|
||||
},
|
||||
Access: accessProf,
|
||||
BlockingMode: m,
|
||||
Ratelimiter: p.RateLimit.toInternal(ctx, s.errColl, s.logger, s.respSzEst),
|
||||
AccountID: accID,
|
||||
ID: profID,
|
||||
FilteredResponseTTL: fltRespTTL,
|
||||
AutoDevicesEnabled: p.AutoDevicesEnabled,
|
||||
BlockChromePrefetch: p.BlockChromePrefetch,
|
||||
BlockFirefoxCanary: p.BlockFirefoxCanary,
|
||||
BlockPrivateRelay: p.BlockPrivateRelay,
|
||||
Deleted: p.Deleted,
|
||||
FilteringEnabled: p.FilteringEnabled,
|
||||
IPLogEnabled: p.IpLogEnabled,
|
||||
QueryLogEnabled: p.QueryLogEnabled,
|
||||
}, devices, devChg, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toProtobuf converts a storage request structure into the protobuf structure.
|
||||
|
||||
@@ -184,7 +184,7 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"parental: pause schedule: loading timezone: unknown time zone invalid",
|
||||
"filter config: parental: pause schedule: loading timezone: unknown time zone invalid",
|
||||
err,
|
||||
)
|
||||
})
|
||||
@@ -201,7 +201,7 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"parental: pause schedule: weekday Sunday: end: out of range: 1 is less than start 16",
|
||||
"filter config: parental: pause schedule: weekday Sunday: end: out of range: 1 is less than start 16",
|
||||
err,
|
||||
)
|
||||
})
|
||||
@@ -210,7 +210,7 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_BlockingModeCustomIp](t, dp.BlockingMode)
|
||||
bm.BlockingModeCustomIp.Ipv4 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
@@ -221,7 +221,7 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_BlockingModeCustomIp](t, dp.BlockingMode)
|
||||
bm.BlockingModeCustomIp.Ipv6 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
@@ -232,7 +232,7 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := dp.BlockingMode.(*DNSProfile_BlockingModeCustomIp)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_BlockingModeCustomIp](t, dp.BlockingMode)
|
||||
bm.BlockingModeCustomIp.Ipv4 = nil
|
||||
bm.BlockingModeCustomIp.Ipv6 = nil
|
||||
|
||||
@@ -258,6 +258,144 @@ func TestProfileStorage_NewProfile(t *testing.T) {
|
||||
assert.Equal(t, wantDevChg, gotDevChg)
|
||||
})
|
||||
|
||||
t.Run("inv_adult_blocking_mode_v4", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_AdultBlockingModeCustomIp](
|
||||
t,
|
||||
dp.AdultBlockingMode,
|
||||
)
|
||||
bm.AdultBlockingModeCustomIp.Ipv4 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"adult blocking mode: bad custom ipv4: unexpected slice size",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("inv_adult_blocking_mode_v6", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_AdultBlockingModeCustomIp](
|
||||
t,
|
||||
dp.AdultBlockingMode,
|
||||
)
|
||||
bm.AdultBlockingModeCustomIp.Ipv6 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"adult blocking mode: bad custom ipv6: unexpected slice size",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("nil_ips_adult_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_AdultBlockingModeCustomIp](
|
||||
t,
|
||||
dp.AdultBlockingMode,
|
||||
)
|
||||
bm.AdultBlockingModeCustomIp.Ipv4 = nil
|
||||
bm.AdultBlockingModeCustomIp.Ipv6 = nil
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(t, "adult blocking mode: no valid custom ips found", err)
|
||||
})
|
||||
|
||||
t.Run("nil_adult_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.AdultBlockingMode = nil
|
||||
|
||||
got, gotDevices, gotDevChg, err := profileStorage.newProfile(ctx, dp, true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
wantProf := newProfile(t)
|
||||
wantProf.AdultBlockingMode = nil
|
||||
|
||||
agdtest.AssertEqualProfile(t, wantProf, got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
assert.Equal(t, wantDevChg, gotDevChg)
|
||||
})
|
||||
|
||||
t.Run("inv_safe_browsing_blocking_mode_v4", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_SafeBrowsingBlockingModeCustomIp](
|
||||
t,
|
||||
dp.SafeBrowsingBlockingMode,
|
||||
)
|
||||
bm.SafeBrowsingBlockingModeCustomIp.Ipv4 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"safe browsing blocking mode: bad custom ipv4: unexpected slice size",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("inv_safe_browsing_blocking_mode_v6", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_SafeBrowsingBlockingModeCustomIp](
|
||||
t,
|
||||
dp.SafeBrowsingBlockingMode,
|
||||
)
|
||||
bm.SafeBrowsingBlockingModeCustomIp.Ipv6 = []byte("1")
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(
|
||||
t,
|
||||
"safe browsing blocking mode: bad custom ipv6: unexpected slice size",
|
||||
err,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("nil_ips_safe_browsing_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
bm := testutil.RequireTypeAssert[*DNSProfile_SafeBrowsingBlockingModeCustomIp](
|
||||
t,
|
||||
dp.SafeBrowsingBlockingMode,
|
||||
)
|
||||
bm.SafeBrowsingBlockingModeCustomIp.Ipv4 = nil
|
||||
bm.SafeBrowsingBlockingModeCustomIp.Ipv6 = nil
|
||||
|
||||
_, _, _, err := profileStorage.newProfile(ctx, dp, true)
|
||||
testutil.AssertErrorMsg(t, "safe browsing blocking mode: no valid custom ips found", err)
|
||||
})
|
||||
|
||||
t.Run("nil_safe_browsing_blocking_mode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dp := NewTestDNSProfile(t)
|
||||
dp.SafeBrowsingBlockingMode = nil
|
||||
|
||||
got, gotDevices, gotDevChg, err := profileStorage.newProfile(ctx, dp, true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
wantProf := newProfile(t)
|
||||
wantProf.SafeBrowsingBlockingMode = nil
|
||||
|
||||
agdtest.AssertEqualProfile(t, wantProf, got)
|
||||
assert.Equal(t, newDevices(t), gotDevices)
|
||||
assert.Equal(t, wantDevChg, gotDevChg)
|
||||
})
|
||||
|
||||
t.Run("nil_access", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -457,6 +595,18 @@ func NewTestDNSProfile(tb testing.TB) (dp *DNSProfile) {
|
||||
Ipv6: ipToBytes(tb, netip.MustParseAddr("1234::cdef")),
|
||||
},
|
||||
},
|
||||
AdultBlockingMode: &DNSProfile_AdultBlockingModeCustomIp{
|
||||
AdultBlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipToBytes(tb, netip.MustParseAddr("1.1.1.1")),
|
||||
Ipv6: ipToBytes(tb, netip.MustParseAddr("1111::cdef")),
|
||||
},
|
||||
},
|
||||
SafeBrowsingBlockingMode: &DNSProfile_SafeBrowsingBlockingModeCustomIp{
|
||||
SafeBrowsingBlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipToBytes(tb, netip.MustParseAddr("2.2.2.2")),
|
||||
Ipv6: ipToBytes(tb, netip.MustParseAddr("2222::cdef")),
|
||||
},
|
||||
},
|
||||
Access: &AccessSettings{
|
||||
AllowlistCidr: []*CidrRange{{
|
||||
Address: netip.MustParseAddr("1.1.1.0").AsSlice(),
|
||||
@@ -549,11 +699,21 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
|
||||
NewlyRegisteredDomainsEnabled: false,
|
||||
}
|
||||
|
||||
wantAdultBlockingMode := &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{netip.MustParseAddr("1.1.1.1")},
|
||||
IPv6: []netip.Addr{netip.MustParseAddr("1111::cdef")},
|
||||
}
|
||||
|
||||
wantBlockingMode := &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
|
||||
IPv6: []netip.Addr{netip.MustParseAddr("1234::cdef")},
|
||||
}
|
||||
|
||||
wantSafeBrowsingBlockingMode := &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
|
||||
IPv6: []netip.Addr{netip.MustParseAddr("2222::cdef")},
|
||||
}
|
||||
|
||||
wantAccess := TestProfileAccessConstructor.New(&access.ProfileConfig{
|
||||
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
|
||||
BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
|
||||
@@ -601,7 +761,9 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
|
||||
SafeBrowsing: wantSafeBrowsing,
|
||||
},
|
||||
Access: wantAccess,
|
||||
AdultBlockingMode: wantAdultBlockingMode,
|
||||
BlockingMode: wantBlockingMode,
|
||||
SafeBrowsingBlockingMode: wantSafeBrowsingBlockingMode,
|
||||
Ratelimiter: wantRateLimiter,
|
||||
ID: TestProfileID,
|
||||
DeviceIDs: container.NewMapSet(
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
@@ -54,6 +55,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Constants that define debug identifiers for the debug HTTP service.
|
||||
@@ -74,6 +76,11 @@ const (
|
||||
debugIDPrefixPlugin = "plugin/"
|
||||
)
|
||||
|
||||
// defaultSubDomainNum is default subDomainNumValue for filters.
|
||||
//
|
||||
// TODO(a.garipov): Make configurable and validate to fit into int.
|
||||
const defaultSubDomainNum = 4
|
||||
|
||||
// builder contains the logic of configuring and combining together AdGuard DNS
|
||||
// entities.
|
||||
//
|
||||
@@ -293,6 +300,15 @@ func (b *builder) initMsgCloner(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashPrefixFilterInitFunc is a function that initializes the hash-prefix
|
||||
// filter, populates the necessary [builder] fields and returns a RefreshWorker
|
||||
// to update the initialized filter.
|
||||
type hashPrefixFilterInitFunc func(
|
||||
ctx context.Context,
|
||||
maxSize datasize.ByteSize,
|
||||
cacheDir string,
|
||||
) (refr *service.RefreshWorker, err error)
|
||||
|
||||
// initHashPrefixFilters initializes the hashprefix storages and filters.
|
||||
//
|
||||
// [builder.initMsgCloner] must be called before this method.
|
||||
@@ -301,51 +317,107 @@ func (b *builder) initHashPrefixFilters(ctx context.Context) (err error) {
|
||||
maxSize := b.conf.Filters.MaxSize
|
||||
cacheDir := b.env.FilterCachePath
|
||||
|
||||
matchers := map[string]*hashprefix.Storage{}
|
||||
|
||||
b.filterMtrc, err = metrics.NewFilter(b.mtrcNamespace, b.promRegisterer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("registering filter metrics: %w", err)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Merge the three functions below together.
|
||||
|
||||
err = b.initAdultBlocking(ctx, matchers, maxSize, cacheDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing adult-blocking filter: %w", err)
|
||||
initFuncs := []hashPrefixFilterInitFunc{
|
||||
b.initAdultBlocking,
|
||||
b.initNewRegDomains,
|
||||
b.initSafeBrowsing,
|
||||
}
|
||||
|
||||
err = b.initNewRegDomains(ctx, maxSize, cacheDir)
|
||||
workers, err := b.runHashPrefixInit(ctx, initFuncs, maxSize, cacheDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing newly-registered domain filter: %w", err)
|
||||
return fmt.Errorf("initializing hash prefixes: %w", err)
|
||||
}
|
||||
|
||||
err = b.initSafeBrowsing(ctx, matchers, maxSize, cacheDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing safe-browsing filter: %w", err)
|
||||
}
|
||||
|
||||
b.hashMatcher = hashprefix.NewMatcher(matchers)
|
||||
b.populateHashPrefix(workers)
|
||||
|
||||
b.logger.DebugContext(ctx, "initialized hash prefixes")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runHashPrefixInit runs the hash prefix filter initialization functions with
|
||||
// the specified arguments, creating a RefreshWorker for each enabled filter.
|
||||
//
|
||||
// It must be called from [builder.initHashPrefixFilters].
|
||||
func (b *builder) runHashPrefixInit(
|
||||
ctx context.Context,
|
||||
initFuncs []hashPrefixFilterInitFunc,
|
||||
maxSize datasize.ByteSize,
|
||||
cacheDir string,
|
||||
) (workers []*service.RefreshWorker, err error) {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
workers = make([]*service.RefreshWorker, len(initFuncs))
|
||||
initErrs := make([]error, len(initFuncs))
|
||||
for i, fn := range initFuncs {
|
||||
wg.Go(func() {
|
||||
defer slogutil.RecoverAndLog(ctx, b.logger)
|
||||
|
||||
workers[i], initErrs[i] = fn(ctx, maxSize, cacheDir)
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
err = errors.Join(initErrs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing hash prefixes: %w", err)
|
||||
}
|
||||
|
||||
return workers, nil
|
||||
}
|
||||
|
||||
// populateHashPrefix populates [builder.debugRefrs], [builder.hashMatcher] and
|
||||
// [builder.sigHdlr] with necessary values for each enabled hash prefix filter.
|
||||
//
|
||||
// It must be called from [builder.initHashPrefixFilters].
|
||||
func (b *builder) populateHashPrefix(workers []*service.RefreshWorker) {
|
||||
matchers := map[string]*hashprefix.Storage{}
|
||||
|
||||
if b.env.AdultBlockingEnabled {
|
||||
adultPrefix := path.Join(hashprefix.IDPrefix, string(filter.IDAdultBlocking))
|
||||
matchers[filter.AdultBlockingTXTSuffix] = b.adultBlockingHashes
|
||||
b.debugRefrs[adultPrefix] = b.adultBlocking
|
||||
}
|
||||
|
||||
if b.env.NewRegDomainsEnabled {
|
||||
newRegDomainsPrefix := path.Join(hashprefix.IDPrefix, string(filter.IDNewRegDomains))
|
||||
b.debugRefrs[newRegDomainsPrefix] = b.newRegDomains
|
||||
}
|
||||
|
||||
if b.env.SafeBrowsingEnabled {
|
||||
safeBrowsingPrefix := path.Join(hashprefix.IDPrefix, string(filter.IDSafeBrowsing))
|
||||
matchers[filter.GeneralTXTSuffix] = b.safeBrowsingHashes
|
||||
b.debugRefrs[safeBrowsingPrefix] = b.safeBrowsing
|
||||
}
|
||||
|
||||
b.hashMatcher = hashprefix.NewMatcher(matchers)
|
||||
|
||||
for _, worker := range workers {
|
||||
if worker != nil {
|
||||
b.sigHdlr.AddService(worker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initAdultBlocking initializes the adult-blocking filter and hash storage. It
|
||||
// also adds the refresher with ID
|
||||
// [hashprefix.IDPrefix]/[filter.IDAdultBlocking] to the debug refreshers.
|
||||
// also adds the refresher with ID.
|
||||
//
|
||||
// It must be called from [builder.initHashPrefixFilters].
|
||||
func (b *builder) initAdultBlocking(
|
||||
ctx context.Context,
|
||||
matchers map[string]*hashprefix.Storage,
|
||||
maxSize datasize.ByteSize,
|
||||
cacheDir string,
|
||||
) (err error) {
|
||||
) (refr *service.RefreshWorker, err error) {
|
||||
if !b.env.AdultBlockingEnabled {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
defer func() { err = errors.Annotate(err, "initializing adult-blocking filter: %w") }()
|
||||
|
||||
b.adultBlockingHashes, err = hashprefix.NewStorage(nil)
|
||||
if err != nil {
|
||||
@@ -359,13 +431,13 @@ func (b *builder) initAdultBlocking(
|
||||
|
||||
const id = filter.IDAdultBlocking
|
||||
|
||||
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
|
||||
hashPrefMtrc, err := metrics.NewHashPrefixFilter(
|
||||
b.mtrcNamespace,
|
||||
string(id),
|
||||
b.promRegisterer,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
return nil, fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
}
|
||||
|
||||
prefix := path.Join(hashprefix.IDPrefix, string(id))
|
||||
@@ -377,8 +449,9 @@ func (b *builder) initAdultBlocking(
|
||||
Hashes: b.adultBlockingHashes,
|
||||
URL: &b.env.AdultBlockingURL.URL,
|
||||
ErrColl: b.errColl,
|
||||
HashPrefixMtcs: hashPrefMtcs,
|
||||
HashPrefixMetrics: hashPrefMtrc,
|
||||
Metrics: b.filterMtrc,
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: id,
|
||||
CachePath: filepath.Join(cacheDir, string(id)),
|
||||
ReplacementHost: c.BlockHost,
|
||||
@@ -389,17 +462,18 @@ func (b *builder) initAdultBlocking(
|
||||
// entity counts to fooCount.
|
||||
CacheCount: c.CacheSize,
|
||||
MaxSize: maxSize,
|
||||
SubDomainNum: defaultSubDomainNum,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating filter: %w", err)
|
||||
return nil, fmt.Errorf("creating filter: %w", err)
|
||||
}
|
||||
|
||||
err = b.adultBlocking.RefreshInitial(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initial refresh: %w", err)
|
||||
return nil, fmt.Errorf("initial refresh: %w", err)
|
||||
}
|
||||
|
||||
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
refr = service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
// Note that we also set the same timeout for the http.Client in
|
||||
// [hashprefix.NewFilter].
|
||||
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
|
||||
@@ -413,16 +487,10 @@ func (b *builder) initAdultBlocking(
|
||||
// routines.
|
||||
err = refr.Start(context.WithoutCancel(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting refresher: %w", err)
|
||||
return nil, fmt.Errorf("starting refresher: %w", err)
|
||||
}
|
||||
|
||||
b.sigHdlr.AddService(refr)
|
||||
|
||||
matchers[filter.AdultBlockingTXTSuffix] = b.adultBlockingHashes
|
||||
|
||||
b.debugRefrs[prefix] = b.adultBlocking
|
||||
|
||||
return nil
|
||||
return refr, nil
|
||||
}
|
||||
|
||||
// newSlogErrorHandler is a convenient wrapper around
|
||||
@@ -436,18 +504,20 @@ func newSlogErrorHandler(baseLogger *slog.Logger, prefix string) (h *service.Slo
|
||||
}
|
||||
|
||||
// initNewRegDomains initializes the newly-registered domain filter and hash
|
||||
// storage. It also adds the refresher with ID
|
||||
// [hashprefix.IDPrefix]/[filter.IDNewRegDomains] to the debug refreshers.
|
||||
// storage.
|
||||
//
|
||||
// It must be called from [builder.initHashPrefixFilters].
|
||||
func (b *builder) initNewRegDomains(
|
||||
ctx context.Context,
|
||||
maxSize datasize.ByteSize,
|
||||
cacheDir string,
|
||||
) (err error) {
|
||||
) (refr *service.RefreshWorker, err error) {
|
||||
if !b.env.NewRegDomainsEnabled {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
defer func() {
|
||||
err = errors.Annotate(err, "initializing newly-registered domains filter: %w")
|
||||
}()
|
||||
|
||||
b.newRegDomainsHashes, err = hashprefix.NewStorage(nil)
|
||||
if err != nil {
|
||||
@@ -463,13 +533,13 @@ func (b *builder) initNewRegDomains(
|
||||
|
||||
const id = filter.IDNewRegDomains
|
||||
|
||||
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
|
||||
hashPrefMtrc, err := metrics.NewHashPrefixFilter(
|
||||
b.mtrcNamespace,
|
||||
string(id),
|
||||
b.promRegisterer,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
return nil, fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
}
|
||||
|
||||
prefix := path.Join(hashprefix.IDPrefix, string(id))
|
||||
@@ -481,8 +551,9 @@ func (b *builder) initNewRegDomains(
|
||||
Hashes: b.newRegDomainsHashes,
|
||||
URL: &b.env.NewRegDomainsURL.URL,
|
||||
ErrColl: b.errColl,
|
||||
HashPrefixMtcs: hashPrefMtcs,
|
||||
HashPrefixMetrics: hashPrefMtrc,
|
||||
Metrics: b.filterMtrc,
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: id,
|
||||
CachePath: filepath.Join(cacheDir, string(id)),
|
||||
ReplacementHost: c.BlockHost,
|
||||
@@ -491,17 +562,18 @@ func (b *builder) initNewRegDomains(
|
||||
CacheTTL: time.Duration(c.CacheTTL),
|
||||
CacheCount: c.CacheSize,
|
||||
MaxSize: maxSize,
|
||||
SubDomainNum: defaultSubDomainNum,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating filter: %w", err)
|
||||
return nil, fmt.Errorf("creating filter: %w", err)
|
||||
}
|
||||
|
||||
err = b.newRegDomains.RefreshInitial(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initial refresh: %w", err)
|
||||
return nil, fmt.Errorf("initial refresh: %w", err)
|
||||
}
|
||||
|
||||
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
refr = service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
// Note that we also set the same timeout for the http.Client in
|
||||
// [hashprefix.NewFilter].
|
||||
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
|
||||
@@ -512,30 +584,24 @@ func (b *builder) initNewRegDomains(
|
||||
})
|
||||
err = refr.Start(context.WithoutCancel(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting refresher: %w", err)
|
||||
return nil, fmt.Errorf("starting refresher: %w", err)
|
||||
}
|
||||
|
||||
b.sigHdlr.AddService(refr)
|
||||
|
||||
b.debugRefrs[prefix] = b.newRegDomains
|
||||
|
||||
return nil
|
||||
return refr, nil
|
||||
}
|
||||
|
||||
// initSafeBrowsing initializes the safe-browsing filter and hash storage. It
|
||||
// also adds the refresher with ID [hashprefix.IDPrefix]/[filter.IDSafeBrowsing]
|
||||
// to the debug refreshers.
|
||||
// initSafeBrowsing initializes the safe-browsing filter and hash storage.
|
||||
//
|
||||
// It must be called from [builder.initHashPrefixFilters].
|
||||
func (b *builder) initSafeBrowsing(
|
||||
ctx context.Context,
|
||||
matchers map[string]*hashprefix.Storage,
|
||||
maxSize datasize.ByteSize,
|
||||
cacheDir string,
|
||||
) (err error) {
|
||||
) (refr *service.RefreshWorker, err error) {
|
||||
if !b.env.SafeBrowsingEnabled {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
defer func() { err = errors.Annotate(err, "initializing safe-browsing filter: %w") }()
|
||||
|
||||
b.safeBrowsingHashes, err = hashprefix.NewStorage(nil)
|
||||
if err != nil {
|
||||
@@ -549,13 +615,13 @@ func (b *builder) initSafeBrowsing(
|
||||
|
||||
const id = filter.IDSafeBrowsing
|
||||
|
||||
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
|
||||
hashPrefMtrc, err := metrics.NewHashPrefixFilter(
|
||||
b.mtrcNamespace,
|
||||
string(id),
|
||||
b.promRegisterer,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
return nil, fmt.Errorf("registering hashprefix filter metrics: %w", err)
|
||||
}
|
||||
|
||||
prefix := path.Join(hashprefix.IDPrefix, string(id))
|
||||
@@ -567,8 +633,9 @@ func (b *builder) initSafeBrowsing(
|
||||
Hashes: b.safeBrowsingHashes,
|
||||
URL: &b.env.SafeBrowsingURL.URL,
|
||||
ErrColl: b.errColl,
|
||||
HashPrefixMtcs: hashPrefMtcs,
|
||||
HashPrefixMetrics: hashPrefMtrc,
|
||||
Metrics: b.filterMtrc,
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: id,
|
||||
CachePath: filepath.Join(cacheDir, string(id)),
|
||||
ReplacementHost: c.BlockHost,
|
||||
@@ -577,17 +644,18 @@ func (b *builder) initSafeBrowsing(
|
||||
CacheTTL: time.Duration(c.CacheTTL),
|
||||
CacheCount: c.CacheSize,
|
||||
MaxSize: maxSize,
|
||||
SubDomainNum: defaultSubDomainNum,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating filter: %w", err)
|
||||
return nil, fmt.Errorf("creating filter: %w", err)
|
||||
}
|
||||
|
||||
err = b.safeBrowsing.RefreshInitial(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initial refresh: %w", err)
|
||||
return nil, fmt.Errorf("initial refresh: %w", err)
|
||||
}
|
||||
|
||||
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
refr = service.NewRefreshWorker(&service.RefreshWorkerConfig{
|
||||
// Note that we also set the same timeout for the http.Client in
|
||||
// [hashprefix.NewFilter].
|
||||
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
|
||||
@@ -598,16 +666,10 @@ func (b *builder) initSafeBrowsing(
|
||||
})
|
||||
err = refr.Start(context.WithoutCancel(ctx))
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting refresher: %w", err)
|
||||
return nil, fmt.Errorf("starting refresher: %w", err)
|
||||
}
|
||||
|
||||
b.sigHdlr.AddService(refr)
|
||||
|
||||
matchers[filter.GeneralTXTSuffix] = b.safeBrowsingHashes
|
||||
|
||||
b.debugRefrs[prefix] = b.safeBrowsing
|
||||
|
||||
return nil
|
||||
return refr, nil
|
||||
}
|
||||
|
||||
// initStandardAccess initializes the standard access settings.
|
||||
@@ -1000,7 +1062,7 @@ func (b *builder) initTLSManager(ctx context.Context) (err error) {
|
||||
ErrColl: b.errColl,
|
||||
Metrics: mtrc,
|
||||
TicketDB: tickDB,
|
||||
KeyLogFilename: logFile,
|
||||
KeyLogPath: logFile,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing tls manager: %w", err)
|
||||
@@ -1009,6 +1071,11 @@ func (b *builder) initTLSManager(ctx context.Context) (err error) {
|
||||
b.tlsManager = mgr
|
||||
b.debugRefrs[debugIDTLSConfig] = mgr
|
||||
|
||||
err = b.conf.TLS.store(ctx, mgr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("storing tls certificates: %w", err)
|
||||
}
|
||||
|
||||
b.logger.DebugContext(ctx, "initialized tls manager")
|
||||
|
||||
return nil
|
||||
@@ -1066,7 +1133,9 @@ func (b *builder) newTicketDB(ctx context.Context) (db tlsconfig.TicketDB, err e
|
||||
|
||||
// initCustomDomainDB initializes the database for the custom domains.
|
||||
//
|
||||
// [builder.initTLSManager] must be called before this method.
|
||||
// The following methods must be called before this one:
|
||||
// - [builder.initTLSManager]
|
||||
// - [builder.initServerGroups]
|
||||
func (b *builder) initCustomDomainDB(ctx context.Context) (err error) {
|
||||
if !bool(b.env.CustomDomainsEnabled) || !b.profilesEnabled {
|
||||
b.logger.WarnContext(ctx, "custom domains are disabled")
|
||||
@@ -1100,6 +1169,8 @@ func (b *builder) initCustomDomainDB(ctx context.Context) (err error) {
|
||||
return fmt.Errorf("registering custom domain database metrics: %w", err)
|
||||
}
|
||||
|
||||
customDomainPrefixes := b.customDomainPrefixes()
|
||||
|
||||
b.customDomainDB, err = tlsconfig.NewCustomDomainDB(&tlsconfig.CustomDomainDBConfig{
|
||||
Logger: b.baseLogger.With(slogutil.KeyPrefix, "custom_domain_db"),
|
||||
Clock: timeutil.SystemClock{},
|
||||
@@ -1109,6 +1180,7 @@ func (b *builder) initCustomDomainDB(ctx context.Context) (err error) {
|
||||
Storage: strg,
|
||||
CacheDirPath: b.env.CustomDomainsCachePath,
|
||||
InitialRetryIvl: time.Duration(b.env.CustomDomainsRefreshIvl),
|
||||
BindPrefixes: customDomainPrefixes,
|
||||
// TODO(a.garipov): Consider making configurable.
|
||||
MaxRetryIvl: 1 * timeutil.Day,
|
||||
})
|
||||
@@ -1128,6 +1200,23 @@ func (b *builder) initCustomDomainDB(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// customDomainPrefixes returns the IP prefixes for the custom-domain
|
||||
// certificates to bind to.
|
||||
func (b *builder) customDomainPrefixes() (prefixes []netip.Prefix) {
|
||||
for _, g := range b.serverGroups {
|
||||
if !g.ProfilesEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, s := range g.Servers {
|
||||
prefixes = append(prefixes, s.BindDataPrefixes()...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return prefixes
|
||||
}
|
||||
|
||||
// initServerGroups initializes the server groups.
|
||||
//
|
||||
// The following methods must be called before this one:
|
||||
|
||||
@@ -120,10 +120,10 @@ func Main(plugins *plugin.Registry) {
|
||||
|
||||
errors.Check(b.initTLSManager(ctx))
|
||||
|
||||
errors.Check(b.initCustomDomainDB(ctx))
|
||||
|
||||
errors.Check(b.initServerGroups(ctx))
|
||||
|
||||
errors.Check(b.initCustomDomainDB(ctx))
|
||||
|
||||
errors.Check(b.initTicketRotator(ctx))
|
||||
|
||||
errors.Check(b.startBindToDevice(ctx))
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/validate"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// configuration represents the on-disk configuration of AdGuard DNS. The order
|
||||
// of the fields should generally not be altered.
|
||||
//
|
||||
// TODO(a.garipov): Consider collecting all validation errors instead of
|
||||
// quitting after the first one.
|
||||
// of the fields defines their dependencies hierarchy and should not be altered.
|
||||
type configuration struct {
|
||||
// RateLimit is the rate limiting configuration.
|
||||
RateLimit *rateLimitConfig `yaml:"ratelimit"`
|
||||
@@ -46,6 +43,9 @@ type configuration struct {
|
||||
// Check is the configuration for the DNS server checker.
|
||||
Check *checkConfig `yaml:"check"`
|
||||
|
||||
// TLS is the configuration of TLS certificates.
|
||||
TLS *tlsConfig `yaml:"tls"`
|
||||
|
||||
// Web is the configuration for the DNS-over-HTTP server.
|
||||
Web *webConfig `yaml:"web"`
|
||||
|
||||
@@ -92,6 +92,11 @@ func (c *configuration) Validate() (err error) {
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
tlsConfValidator := &tlsConfigValidator{
|
||||
state: tlsStateDisabled,
|
||||
tlsConf: c.TLS,
|
||||
}
|
||||
|
||||
// Keep this in the same order as the fields in the config.
|
||||
validators := container.KeyValues[string, validate.Interface]{{
|
||||
Key: "ratelimit",
|
||||
@@ -120,9 +125,16 @@ func (c *configuration) Validate() (err error) {
|
||||
}, {
|
||||
Key: "check",
|
||||
Value: c.Check,
|
||||
}, {
|
||||
Key: "tls",
|
||||
Value: tlsConfValidator,
|
||||
}, {
|
||||
Key: "web",
|
||||
Value: c.Web,
|
||||
Value: validatorWithTLS{
|
||||
validator: c.Web,
|
||||
tlsState: &tlsConfValidator.state,
|
||||
tlsConf: c.TLS,
|
||||
},
|
||||
}, {
|
||||
Key: "safe_browsing",
|
||||
Value: c.SafeBrowsing,
|
||||
@@ -137,7 +149,11 @@ func (c *configuration) Validate() (err error) {
|
||||
Value: c.FilteringGroups,
|
||||
}, {
|
||||
Key: "server_groups",
|
||||
Value: c.ServerGroups,
|
||||
Value: validatorWithTLS{
|
||||
validator: c.ServerGroups,
|
||||
tlsState: &tlsConfValidator.state,
|
||||
tlsConf: c.TLS,
|
||||
},
|
||||
}, {
|
||||
Key: "connectivity_check",
|
||||
Value: c.ConnectivityCheck,
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/validate"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
// dnsCryptConfig are the DNSCrypt server settings.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// setMaxThreads sets the maximum number of threads for the Go runtime, if
|
||||
// necessary. l must not be nil, envs must not be negative.
|
||||
// necessary. l must not be nil, n must not be negative.
|
||||
func setMaxThreads(ctx context.Context, l *slog.Logger, n int) {
|
||||
if n == 0 {
|
||||
l.Log(ctx, slogutil.LevelTrace, "go max threads not set")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
@@ -19,69 +20,149 @@ import (
|
||||
// toInternal returns the configuration of DNS servers for a single server
|
||||
// group. srvs and other parts of the configuration must be valid.
|
||||
func (srvs servers) toInternal(
|
||||
ctx context.Context,
|
||||
btdMgr *bindtodevice.Manager,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
ratelimitConf *rateLimitConfig,
|
||||
dnsConf *dnsConfig,
|
||||
certNames []agd.CertificateName,
|
||||
deviceDomains []string,
|
||||
) (dnsSrvs []*agd.Server, err error) {
|
||||
dnsSrvs = make([]*agd.Server, 0, len(srvs))
|
||||
for _, srv := range srvs {
|
||||
var bindData []*agd.ServerBindData
|
||||
bindData, err = srv.bindData(btdMgr)
|
||||
for i, srv := range srvs {
|
||||
var dnsSrv *agd.Server
|
||||
dnsSrv, err = srv.toInternal(
|
||||
ctx,
|
||||
tlsMgr,
|
||||
btdMgr,
|
||||
ratelimitConf,
|
||||
dnsConf,
|
||||
certNames,
|
||||
deviceDomains,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server %q: %w", srv.Name, err)
|
||||
return nil, fmt.Errorf("server %q: at index %d: %w", srv.Name, i, err)
|
||||
}
|
||||
|
||||
name := agd.ServerName(srv.Name)
|
||||
dnsSrv := &agd.Server{
|
||||
Name: name,
|
||||
ReadTimeout: time.Duration(dnsConf.ReadTimeout),
|
||||
WriteTimeout: time.Duration(dnsConf.WriteTimeout),
|
||||
LinkedIPEnabled: srv.LinkedIPEnabled,
|
||||
Protocol: srv.Protocol.toInternal(),
|
||||
}
|
||||
|
||||
tcpConf := &agd.TCPConfig{
|
||||
IdleTimeout: time.Duration(dnsConf.TCPIdleTimeout),
|
||||
MaxPipelineCount: ratelimitConf.TCP.MaxPipelineCount,
|
||||
MaxPipelineEnabled: ratelimitConf.TCP.Enabled,
|
||||
}
|
||||
|
||||
switch dnsSrv.Protocol {
|
||||
case agd.ProtoDNS:
|
||||
dnsSrv.TCPConf = tcpConf
|
||||
dnsSrv.UDPConf = &agd.UDPConfig{
|
||||
// #nosec G115 -- The value has already been validated in
|
||||
// [dnsConfig.validate].
|
||||
MaxRespSize: uint16(dnsConf.MaxUDPResponseSize.Bytes()),
|
||||
}
|
||||
case agd.ProtoDNSCrypt:
|
||||
var dcConf *agd.DNSCryptConfig
|
||||
dcConf, err = srv.DNSCrypt.toInternal()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server %q: dnscrypt: %w", srv.Name, err)
|
||||
}
|
||||
|
||||
dnsSrv.DNSCrypt = dcConf
|
||||
default:
|
||||
dnsSrv.TCPConf = tcpConf
|
||||
dnsSrv.QUICConf = &agd.QUICConfig{
|
||||
MaxStreamsPerPeer: ratelimitConf.QUIC.MaxStreamsPerPeer,
|
||||
QUICLimitsEnabled: ratelimitConf.QUIC.Enabled,
|
||||
}
|
||||
|
||||
dnsSrv.TLS = newTLSConfig(dnsSrv, tlsMgr, deviceDomains, srv)
|
||||
}
|
||||
|
||||
dnsSrv.SetBindData(bindData)
|
||||
|
||||
dnsSrvs = append(dnsSrvs, dnsSrv)
|
||||
}
|
||||
|
||||
return dnsSrvs, nil
|
||||
}
|
||||
|
||||
// toInternal returns the configuration for a single server. tlsMgr, btdMgr,
|
||||
// ratelimitConf, and dnsConf must not be nil, certNames items must be valid,
|
||||
// deviceDomains items must be a valid domain names.
|
||||
func (s *server) toInternal(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
btdMgr *bindtodevice.Manager,
|
||||
ratelimitConf *rateLimitConfig,
|
||||
dnsConf *dnsConfig,
|
||||
certNames []agd.CertificateName,
|
||||
deviceDomains []string,
|
||||
) (dnsSrv *agd.Server, err error) {
|
||||
var bindData []*agd.ServerBindData
|
||||
bindData, err = s.bindData(btdMgr)
|
||||
if err != nil {
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := agd.ServerName(s.Name)
|
||||
dnsSrv = &agd.Server{
|
||||
Name: name,
|
||||
ReadTimeout: time.Duration(dnsConf.ReadTimeout),
|
||||
WriteTimeout: time.Duration(dnsConf.WriteTimeout),
|
||||
LinkedIPEnabled: s.LinkedIPEnabled,
|
||||
Protocol: s.Protocol.toInternal(),
|
||||
}
|
||||
dnsSrv.SetBindData(bindData)
|
||||
|
||||
err = setProtoConfig(ctx, dnsSrv, s, dnsConf, ratelimitConf, tlsMgr, certNames, deviceDomains)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setting protocol-specific configuration: %w", err)
|
||||
}
|
||||
|
||||
return dnsSrv, nil
|
||||
}
|
||||
|
||||
// setProtoConfig sets the protocol-specific configuration to dnsSrv.
|
||||
func setProtoConfig(
|
||||
ctx context.Context,
|
||||
dnsSrv *agd.Server,
|
||||
s *server,
|
||||
dnsConf *dnsConfig,
|
||||
ratelimitConf *rateLimitConfig,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
certNames []agd.CertificateName,
|
||||
deviceDomains []string,
|
||||
) (err error) {
|
||||
switch dnsSrv.Protocol {
|
||||
case agd.ProtoDNS:
|
||||
dnsSrv.TCPConf = &agd.TCPConfig{
|
||||
IdleTimeout: time.Duration(dnsConf.TCPIdleTimeout),
|
||||
MaxPipelineCount: ratelimitConf.TCP.MaxPipelineCount,
|
||||
MaxPipelineEnabled: ratelimitConf.TCP.Enabled,
|
||||
}
|
||||
|
||||
dnsSrv.UDPConf = &agd.UDPConfig{
|
||||
// #nosec G115 -- The value has already been validated in
|
||||
// [dnsConfig.Validate].
|
||||
MaxRespSize: uint16(dnsConf.MaxUDPResponseSize.Bytes()),
|
||||
}
|
||||
case agd.ProtoDNSCrypt:
|
||||
var dcConf *agd.DNSCryptConfig
|
||||
dcConf, err = s.DNSCrypt.toInternal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnscrypt: %w", err)
|
||||
}
|
||||
|
||||
dnsSrv.DNSCrypt = dcConf
|
||||
default:
|
||||
dnsSrv.TCPConf = &agd.TCPConfig{
|
||||
IdleTimeout: time.Duration(dnsConf.TCPIdleTimeout),
|
||||
MaxPipelineCount: ratelimitConf.TCP.MaxPipelineCount,
|
||||
MaxPipelineEnabled: ratelimitConf.TCP.Enabled,
|
||||
}
|
||||
|
||||
dnsSrv.QUICConf = &agd.QUICConfig{
|
||||
MaxStreamsPerPeer: ratelimitConf.QUIC.MaxStreamsPerPeer,
|
||||
QUICLimitsEnabled: ratelimitConf.QUIC.Enabled,
|
||||
}
|
||||
|
||||
dnsSrv.TLS = newTLSConfig(dnsSrv, tlsMgr, deviceDomains, s)
|
||||
err = bindTLSNames(ctx, dnsSrv, tlsMgr, certNames)
|
||||
if err != nil {
|
||||
// Don't wrap the error, since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindTLSNames binds the server to the specified certificate names in the TLS
|
||||
// manager.
|
||||
func bindTLSNames(
|
||||
ctx context.Context,
|
||||
s *agd.Server,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
names []agd.CertificateName,
|
||||
) (err error) {
|
||||
var errs []error
|
||||
for _, pref := range s.BindDataPrefixes() {
|
||||
for _, name := range names {
|
||||
err = tlsMgr.Bind(ctx, name, pref)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("binding %q to %s: %w", name, pref, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// newTLSConfig returns the TLS configuration with metrics and ALPs set.
|
||||
//
|
||||
// TODO(s.chzhen): Consider moving to agd package as soon as the import cycle
|
||||
|
||||
@@ -29,21 +29,18 @@ func (srvGrps serverGroups) toInternal(
|
||||
ratelimitConf *rateLimitConfig,
|
||||
dnsConf *dnsConfig,
|
||||
) (svcSrvGrps []*dnssvc.ServerGroupConfig, err error) {
|
||||
svcSrvGrps = make([]*dnssvc.ServerGroupConfig, len(srvGrps))
|
||||
for i, g := range srvGrps {
|
||||
svcSrvGrps = make([]*dnssvc.ServerGroupConfig, 0, len(srvGrps))
|
||||
for _, g := range srvGrps {
|
||||
// TODO(e.burkov): Validate in [serverGroupsValidator.Validate].
|
||||
fltGrpID := agd.FilteringGroupID(g.FilteringGroup)
|
||||
_, ok := fltGrps[fltGrpID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("server group %q: unknown filtering group %q", g.Name, fltGrpID)
|
||||
}
|
||||
|
||||
var deviceDomains []string
|
||||
deviceDomains, err = g.TLS.toInternal(ctx, tlsMgr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tls %q: %w", g.Name, err)
|
||||
}
|
||||
certNames, deviceDomains := g.TLS.toInternal()
|
||||
|
||||
svcSrvGrps[i] = &dnssvc.ServerGroupConfig{
|
||||
groupConfig := &dnssvc.ServerGroupConfig{
|
||||
DDR: g.DDR.toInternal(messages),
|
||||
DeviceDomains: deviceDomains,
|
||||
Name: agd.ServerGroupName(g.Name),
|
||||
@@ -51,52 +48,25 @@ func (srvGrps serverGroups) toInternal(
|
||||
ProfilesEnabled: g.ProfilesEnabled,
|
||||
}
|
||||
|
||||
svcSrvGrps[i].Servers, err = g.Servers.toInternal(
|
||||
groupConfig.Servers, err = g.Servers.toInternal(
|
||||
ctx,
|
||||
btdMgr,
|
||||
tlsMgr,
|
||||
ratelimitConf,
|
||||
dnsConf,
|
||||
certNames,
|
||||
deviceDomains,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server group %q: %w", g.Name, err)
|
||||
}
|
||||
|
||||
svcSrvGrps = append(svcSrvGrps, groupConfig)
|
||||
}
|
||||
|
||||
return svcSrvGrps, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = serverGroups(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for serverGroups.
|
||||
func (srvGrps serverGroups) Validate() (err error) {
|
||||
if len(srvGrps) == 0 {
|
||||
return errors.ErrEmptyValue
|
||||
}
|
||||
|
||||
var errs []error
|
||||
names := container.NewMapSet[string]()
|
||||
for i, g := range srvGrps {
|
||||
err = g.Validate()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if names.Has(g.Name) {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w: %q", i, errors.ErrDuplicated, g.Name))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
names.Add(g.Name)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// serverGroup defines a group of DNS servers all of which use the same
|
||||
// filtering settings.
|
||||
//
|
||||
@@ -108,7 +78,7 @@ type serverGroup struct {
|
||||
DDR *ddrConfig `yaml:"ddr"`
|
||||
|
||||
// TLS are the TLS settings for this server, if any.
|
||||
TLS *tlsConfig `yaml:"tls"`
|
||||
TLS *serverGroupTLSConfig `yaml:"tls"`
|
||||
|
||||
// Name is the unique name of the server group.
|
||||
Name string `yaml:"name"`
|
||||
@@ -125,10 +95,10 @@ type serverGroup struct {
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*serverGroup)(nil)
|
||||
var _ tlsValidator = (*serverGroup)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *serverGroup.
|
||||
func (g *serverGroup) Validate() (err error) {
|
||||
// validate implements the [validatorWithTLS] interface for *serverGroup.
|
||||
func (g *serverGroup) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if g == nil {
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
@@ -145,7 +115,7 @@ func (g *serverGroup) Validate() (err error) {
|
||||
errs = append(errs, fmt.Errorf("servers: %w", err))
|
||||
}
|
||||
|
||||
err = g.TLS.validateIfNecessary(needsTLS)
|
||||
err = g.TLS.validateIfNecessary(needsTLS, tlsConf, *ts)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("tls: %w", err))
|
||||
}
|
||||
@@ -170,3 +140,34 @@ func (srvGrps serverGroups) collectSessTicketPaths() (paths []string) {
|
||||
|
||||
return set.Values()
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ tlsValidator = (serverGroups)(nil)
|
||||
|
||||
// validate implements the [tlsValidator] interface for serverGroups.
|
||||
func (srvGrps serverGroups) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if len(srvGrps) == 0 {
|
||||
return errors.ErrEmptyValue
|
||||
}
|
||||
|
||||
var errs []error
|
||||
names := container.NewMapSet[string]()
|
||||
for i, g := range srvGrps {
|
||||
err = g.validate(tlsConf, ts)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if names.Has(g.Name) {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w: %q", i, errors.ErrDuplicated, g.Name))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
names.Add(g.Name)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
@@ -18,10 +21,135 @@ const (
|
||||
sessionTicketRemote = "remote"
|
||||
)
|
||||
|
||||
// tlsConfig are the TLS settings of a DNS server, if any.
|
||||
// tlsCertificateConfig is a single TLS certificate configuration.
|
||||
type tlsCertificateConfig struct {
|
||||
// CertificatePath is the path to the TLS certificate.
|
||||
CertificatePath string `yaml:"certificate"`
|
||||
|
||||
// KeyPath is the path to the TLS private key.
|
||||
KeyPath string `yaml:"key"`
|
||||
}
|
||||
|
||||
// certificateGroupConfigs is a map of certificate group names to their
|
||||
// configurations.
|
||||
type certificateGroupConfigs map[string]*tlsCertificateConfig
|
||||
|
||||
// tlsConfig is the common configuration of TLS certificates.
|
||||
type tlsConfig struct {
|
||||
// Certificates are TLS certificates for this server.
|
||||
Certificates tlsConfigCerts `yaml:"certificates"`
|
||||
// CertificateGroups are the named groups of TLS certificates.
|
||||
CertificateGroups certificateGroupConfigs `yaml:"certificate_groups"`
|
||||
|
||||
// Enabled is true if TLS is enabled.
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// store stores the TLS certificates in the TLS manager. c must be valid,
|
||||
// tlsMgr must not be nil.
|
||||
func (c tlsConfig) store(ctx context.Context, tlsMgr tlsconfig.Manager) (err error) {
|
||||
var errs []error
|
||||
for name, conf := range c.CertificateGroups {
|
||||
err = tlsMgr.Add(ctx, &tlsconfig.AddParams{
|
||||
// The name is validated in [tlsConfig.Validate].
|
||||
Name: agd.CertificateName(name),
|
||||
CertPath: conf.CertificatePath,
|
||||
KeyPath: conf.KeyPath,
|
||||
IsCustom: false,
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("adding certificate %q: %w", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (certificateGroupConfigs)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for
|
||||
// certificateGroupConfigs.
|
||||
//
|
||||
// TODO(e.burkov): Consider checking the files existence with [os.Stat].
|
||||
func (c certificateGroupConfigs) Validate() (err error) {
|
||||
var errs []error
|
||||
for _, name := range slices.Sorted(maps.Keys(c)) {
|
||||
_, err = agd.NewCertificateName(name)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("certificate group %q: %w", name, err))
|
||||
}
|
||||
|
||||
cg := c[name]
|
||||
if cg == nil {
|
||||
errs = append(errs, fmt.Errorf("certificate group %q: %w", name, errors.ErrNoValue))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err = validate.NotEmpty("certificate", cg.CertificatePath)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("certificate group %q: %w", name, err))
|
||||
}
|
||||
|
||||
err = validate.NotEmpty("key", cg.KeyPath)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("certificate group %q: %w", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// tlsCertificateGroupConfig defines a group of certificates used by a server
|
||||
// group.
|
||||
type tlsCertificateGroupConfig struct {
|
||||
Name agd.CertificateName `yaml:"name"`
|
||||
}
|
||||
|
||||
// tlsCertificateGroupConfigs is a slice of certificate group configs.
|
||||
type tlsCertificateGroupConfigs []*tlsCertificateGroupConfig
|
||||
|
||||
// validate returns an error if the certificate group configs are invalid.
|
||||
func (c tlsCertificateGroupConfigs) validate(tlsConf *tlsConfig) (err error) {
|
||||
if c == nil {
|
||||
return errors.ErrNoValue
|
||||
} else if len(c) == 0 {
|
||||
return errors.ErrEmptyValue
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i, cg := range c {
|
||||
nameStr := string(cg.Name)
|
||||
if _, ok := tlsConf.CertificateGroups[nameStr]; !ok {
|
||||
err = fmt.Errorf("at index %d: %q: %w", i, nameStr, errors.ErrBadEnumValue)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// bind binds the certificate group configs to pref in tlsMgr.
|
||||
func (c tlsCertificateGroupConfigs) bind(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
pref netip.Prefix,
|
||||
) (err error) {
|
||||
var errs []error
|
||||
for i, cg := range c {
|
||||
err = tlsMgr.Bind(ctx, cg.Name, pref)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("at index %d: binding to %s: %w", i, pref, err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// serverGroupTLSConfig are the TLS settings of a DNS server, if any.
|
||||
type serverGroupTLSConfig struct {
|
||||
// CertificateGroups are TLS certificate groups for this server.
|
||||
CertificateGroups tlsCertificateGroupConfigs `yaml:"certificate_groups"`
|
||||
|
||||
// SessionKeys are paths to files containing the TLS session keys for this
|
||||
// server.
|
||||
@@ -40,29 +168,29 @@ type tlsConfig struct {
|
||||
|
||||
// toInternal converts c to the TLS configuration for a DNS server. c must be
|
||||
// valid.
|
||||
func (c *tlsConfig) toInternal(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
) (deviceDomains []string, err error) {
|
||||
func (c *serverGroupTLSConfig) toInternal() (certNames []agd.CertificateName, wcDomains []string) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = c.Certificates.store(ctx, tlsMgr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("certificates: %w", err)
|
||||
for _, cg := range c.CertificateGroups {
|
||||
certNames = append(certNames, cg.Name)
|
||||
}
|
||||
|
||||
for _, w := range c.DeviceIDWildcards {
|
||||
deviceDomains = append(deviceDomains, strings.TrimPrefix(w, "*."))
|
||||
wcDomains = append(wcDomains, strings.TrimPrefix(w, "*."))
|
||||
}
|
||||
|
||||
return deviceDomains, nil
|
||||
return certNames, wcDomains
|
||||
}
|
||||
|
||||
// validateIfNecessary returns an error if the TLS configuration is invalid
|
||||
// depending on whether it is necessary or not.
|
||||
func (c *tlsConfig) validateIfNecessary(needsTLS bool) (err error) {
|
||||
func (c *serverGroupTLSConfig) validateIfNecessary(
|
||||
needsTLS bool,
|
||||
tlsConf *tlsConfig,
|
||||
ts tlsState,
|
||||
) (err error) {
|
||||
switch {
|
||||
case c == nil:
|
||||
if needsTLS {
|
||||
@@ -73,15 +201,18 @@ func (c *tlsConfig) validateIfNecessary(needsTLS bool) (err error) {
|
||||
return nil
|
||||
case !needsTLS:
|
||||
return errors.Error("server group does not require tls")
|
||||
case ts == tlsStateDisabled:
|
||||
return errors.Error("tls is disabled")
|
||||
}
|
||||
|
||||
var errs []error
|
||||
if err = validate.NotEmptySlice("certificates", c.Certificates); err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
errs = validate.Append(errs, "certificates", c.Certificates)
|
||||
if ts == tlsStateValid {
|
||||
err = c.CertificateGroups.validate(tlsConf)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("certificate_groups: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
err = validateDeviceIDWildcards(c.DeviceIDWildcards)
|
||||
if err != nil {
|
||||
@@ -109,77 +240,85 @@ func validateDeviceIDWildcards(wildcards []string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// tlsConfigCert is a single TLS certificate.
|
||||
type tlsConfigCert struct {
|
||||
// Certificate is the path to the TLS certificate.
|
||||
Certificate string `yaml:"certificate"`
|
||||
// tlsState is the state of TLS validation.
|
||||
type tlsState string
|
||||
|
||||
// Key is the path to the TLS private key.
|
||||
Key string `yaml:"key"`
|
||||
}
|
||||
// Valid tlsState values.
|
||||
const (
|
||||
// tlsStateDisabled is the state of TLS validation when the TLS
|
||||
// configuration is not specified.
|
||||
tlsStateDisabled tlsState = "disabled"
|
||||
|
||||
// tlsConfigCerts are TLS certificates. A valid instance of tlsConfigCerts has
|
||||
// no nil items.
|
||||
type tlsConfigCerts []*tlsConfigCert
|
||||
// tlsStateInvalid is the state of TLS validation when the result is
|
||||
// negative.
|
||||
tlsStateInvalid tlsState = "invalid"
|
||||
|
||||
// store stores the TLS certificates in the TLS manager. certs must be valid.
|
||||
func (certs tlsConfigCerts) store(ctx context.Context, tlsMgr tlsconfig.Manager) (err error) {
|
||||
var errs []error
|
||||
for i, c := range certs {
|
||||
err = tlsMgr.Add(ctx, c.Certificate, c.Key, false)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("adding certificate at index %d: %w", i, err))
|
||||
}
|
||||
}
|
||||
// tlsStateValid is the state of TLS validation when the result is positive.
|
||||
tlsStateValid tlsState = "valid"
|
||||
)
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
// tlsConfigValidator validates the TLS configuration and updates the result's
|
||||
type tlsConfigValidator struct {
|
||||
// tlsConf is the configuration to validate.
|
||||
tlsConf *tlsConfig
|
||||
|
||||
// toInternal is like [tlsConfigCerts.store] but it also returns the TLS
|
||||
// configuration. certs must be valid.
|
||||
func (certs tlsConfigCerts) toInternal(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
) (conf *tls.Config, err error) {
|
||||
if len(certs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = certs.store(ctx, tlsMgr)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsMgr.Clone(), nil
|
||||
// state is the state of TLS validation. It must not be used until
|
||||
// [tlsConfigValidator.Validate] returns.
|
||||
state tlsState
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = tlsConfigCerts(nil)
|
||||
var _ validate.Interface = (*tlsConfigValidator)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for
|
||||
// tlsConfigValidator. It sets the state field of v to the corresponding
|
||||
// tlsState value.
|
||||
func (v *tlsConfigValidator) Validate() (err error) {
|
||||
if v.tlsConf == nil {
|
||||
v.state = tlsStateInvalid
|
||||
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
if !v.tlsConf.Enabled {
|
||||
v.state = tlsStateDisabled
|
||||
|
||||
// Validate implements the [validate.Interface] interface for tlsConfigCerts.
|
||||
// certs may be nil.
|
||||
func (certs tlsConfigCerts) Validate() (err error) {
|
||||
if len(certs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i, c := range certs {
|
||||
if c == nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, errors.ErrNoValue))
|
||||
|
||||
continue
|
||||
err = v.tlsConf.CertificateGroups.Validate()
|
||||
if err != nil {
|
||||
v.state = tlsStateInvalid
|
||||
} else {
|
||||
v.state = tlsStateValid
|
||||
}
|
||||
|
||||
if err = validate.NotEmpty("certificate", c.Certificate); err != nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
|
||||
return err
|
||||
}
|
||||
|
||||
if err = validate.NotEmpty("key", c.Key); err != nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
|
||||
}
|
||||
// tlsValidator is like [validate.Interface], but accepts TLS configuration
|
||||
// and state to validate the web module configuration with.
|
||||
type tlsValidator interface {
|
||||
// validate returns error if the configuration is not valid. tlsConf must
|
||||
// correspond to tlsState.
|
||||
validate(tlsConf *tlsConfig, tlsState *tlsState) (err error)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
type validatorWithTLS struct {
|
||||
// validator is the entity to validate with TLS configuration.
|
||||
validator tlsValidator
|
||||
|
||||
// tlsConf is the TLS configuration to validate with.
|
||||
tlsConf *tlsConfig
|
||||
|
||||
// tlsState is the state of TLS validation.
|
||||
tlsState *tlsState
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*validatorWithTLS)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for validatorWithTLS.
|
||||
func (v validatorWithTLS) Validate() (err error) {
|
||||
return v.validator.validate(v.tlsConf, v.tlsState)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/websvc"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
@@ -145,6 +146,46 @@ func (c *webConfig) toInternal(
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// validate implements the [tlsValidator] interface for *webConfig.
|
||||
func (c *webConfig) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := []error{
|
||||
validate.Positive("timeout", c.Timeout),
|
||||
}
|
||||
errs = validate.Append(errs, "static_content", c.StaticContent)
|
||||
|
||||
withTLS := validatorWithTLS{
|
||||
tlsState: ts,
|
||||
tlsConf: tlsConf,
|
||||
}
|
||||
validators := container.KeyValues[string, tlsValidator]{{
|
||||
Key: "linked_ip",
|
||||
Value: c.LinkedIP,
|
||||
}, {
|
||||
Key: "adult_blocking",
|
||||
Value: c.AdultBlocking,
|
||||
}, {
|
||||
Key: "general_blocking",
|
||||
Value: c.GeneralBlocking,
|
||||
}, {
|
||||
Key: "safe_browsing",
|
||||
Value: c.SafeBrowsing,
|
||||
}, {
|
||||
Key: "non_doh_bind",
|
||||
Value: c.NonDoHBind,
|
||||
}}
|
||||
|
||||
for _, v := range validators {
|
||||
withTLS.validator = v.Value
|
||||
errs = validate.Append(errs, v.Key, withTLS)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// readErrorPages returns the contents for the error pages in the configuration
|
||||
// file and any errors encountered while reading them.
|
||||
func (c *webConfig) readErrorPages() (error404, error500 []byte, err error) {
|
||||
@@ -181,29 +222,6 @@ func (c *webConfig) setStaticContent(envs *environment, conf *websvc.Config) (er
|
||||
return nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*webConfig)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *webConfig.
|
||||
func (c *webConfig) Validate() (err error) {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := []error{
|
||||
validate.Positive("timeout", c.Timeout),
|
||||
}
|
||||
|
||||
errs = validate.Append(errs, "linked_ip", c.LinkedIP)
|
||||
errs = validate.Append(errs, "adult_blocking", c.AdultBlocking)
|
||||
errs = validate.Append(errs, "general_blocking", c.GeneralBlocking)
|
||||
errs = validate.Append(errs, "safe_browsing", c.SafeBrowsing)
|
||||
errs = validate.Append(errs, "static_content", c.StaticContent)
|
||||
errs = validate.Append(errs, "non_doh_bind", c.NonDoHBind)
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// linkedIPServer is the linked IP web server configuration.
|
||||
type linkedIPServer struct {
|
||||
// Bind are the bind addresses and optional TLS configuration for the linked
|
||||
@@ -237,10 +255,10 @@ func (s *linkedIPServer) toInternal(
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*linkedIPServer)(nil)
|
||||
var _ tlsValidator = (*linkedIPServer)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *linkedIPServer.
|
||||
func (s *linkedIPServer) Validate() (err error) {
|
||||
// validate implements the [tlsValidator] interface for *linkedIPServer.
|
||||
func (s *linkedIPServer) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -249,7 +267,10 @@ func (s *linkedIPServer) Validate() (err error) {
|
||||
validate.NotEmptySlice("bind", s.Bind),
|
||||
}
|
||||
|
||||
errs = validate.Append(errs, "bind", s.Bind)
|
||||
err = s.Bind.validate(tlsConf, ts)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("bind: %w", err))
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
@@ -287,10 +308,10 @@ func (s *blockPageServer) toInternal(
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*blockPageServer)(nil)
|
||||
var _ tlsValidator = (*blockPageServer)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *blockPageServer.
|
||||
func (s *blockPageServer) Validate() (err error) {
|
||||
// validate implements the [tlsValidator] interface for *blockPageServer.
|
||||
func (s *blockPageServer) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -300,7 +321,10 @@ func (s *blockPageServer) Validate() (err error) {
|
||||
validate.NotEmptySlice("bind", s.Bind),
|
||||
}
|
||||
|
||||
errs = validate.Append(errs, "bind", s.Bind)
|
||||
err = s.Bind.validate(tlsConf, ts)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("bind: %w", err))
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
@@ -314,30 +338,41 @@ func (bd bindData) toInternal(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
) (data []*websvc.BindData, err error) {
|
||||
data = make([]*websvc.BindData, len(bd))
|
||||
var errs []error
|
||||
data = make([]*websvc.BindData, 0, len(bd))
|
||||
|
||||
for i, d := range bd {
|
||||
data[i], err = d.toInternal(ctx, tlsMgr)
|
||||
var datum *websvc.BindData
|
||||
datum, err = d.toInternal(ctx, tlsMgr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bind data at index %d: %w", i, err)
|
||||
errs = append(errs, fmt.Errorf("bind data: at index %d: %w", i, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
data = append(data, datum)
|
||||
}
|
||||
|
||||
err = errors.Join(errs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = bindData(nil)
|
||||
var _ tlsValidator = bindData(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for bindData.
|
||||
func (bd bindData) Validate() (err error) {
|
||||
// validate implements the [tlsValidator] interface for bindData.
|
||||
func (bd bindData) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if len(bd) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i, d := range bd {
|
||||
err = d.Validate()
|
||||
err = d.validate(tlsConf, ts)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
|
||||
}
|
||||
@@ -351,8 +386,9 @@ type bindItem struct {
|
||||
// Address is the binding address.
|
||||
Address netip.AddrPort `yaml:"address"`
|
||||
|
||||
// Certificates are the optional TLS certificates for this HTTP(S) server.
|
||||
Certificates tlsConfigCerts `yaml:"certificates"`
|
||||
// CertificateGroups are the optional TLS certificates configuration for
|
||||
// this HTTP(S) server.
|
||||
CertificateGroups tlsCertificateGroupConfigs `yaml:"certificate_groups"`
|
||||
}
|
||||
|
||||
// toInternal converts i to bind data for the AdGuard DNS web service. i must
|
||||
@@ -361,22 +397,35 @@ func (i *bindItem) toInternal(
|
||||
ctx context.Context,
|
||||
tlsMgr tlsconfig.Manager,
|
||||
) (data *websvc.BindData, err error) {
|
||||
tlsConf, err := i.Certificates.toInternal(ctx, tlsMgr)
|
||||
if len(i.CertificateGroups) == 0 {
|
||||
return &websvc.BindData{
|
||||
Address: i.Address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
addr := i.Address.Addr()
|
||||
|
||||
pref, err := addr.Prefix(addr.BitLen())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prefix: %w", err)
|
||||
}
|
||||
|
||||
err = i.CertificateGroups.bind(ctx, tlsMgr, pref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("certificates: %w", err)
|
||||
}
|
||||
|
||||
return &websvc.BindData{
|
||||
TLS: tlsConf,
|
||||
TLS: tlsMgr.Clone(),
|
||||
Address: i.Address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ validate.Interface = (*bindItem)(nil)
|
||||
var _ tlsValidator = (*bindItem)(nil)
|
||||
|
||||
// Validate implements the [validate.Interface] interface for *bindItem.
|
||||
func (i *bindItem) Validate() (err error) {
|
||||
// validate implements the [tlsValidator] interface for *bindItem.
|
||||
func (i *bindItem) validate(tlsConf *tlsConfig, ts *tlsState) (err error) {
|
||||
if i == nil {
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
@@ -385,7 +434,22 @@ func (i *bindItem) Validate() (err error) {
|
||||
validate.NotEmpty("address", i.Address),
|
||||
}
|
||||
|
||||
errs = validate.Append(errs, "certificates", i.Certificates)
|
||||
switch *ts {
|
||||
case tlsStateValid:
|
||||
if i.CertificateGroups == nil {
|
||||
// No TLS.
|
||||
break
|
||||
}
|
||||
|
||||
err = i.CertificateGroups.validate(tlsConf)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("certificate_groups: %w", err))
|
||||
}
|
||||
case tlsStateDisabled:
|
||||
errs = append(errs, validate.EmptySlice("certificate_groups", i.CertificateGroups))
|
||||
default:
|
||||
// Ignore TLS configuration.
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ func (c *Constructor) NewResp(req *dns.Msg) (resp *dns.Msg) {
|
||||
}).SetReply(req)
|
||||
}
|
||||
|
||||
// NewBlockedResp returns a blocked response DNS message based on the
|
||||
// constructor's blocking mode.
|
||||
func (c *Constructor) NewBlockedResp(req *dns.Msg) (msg *dns.Msg, err error) {
|
||||
switch m := c.blockingMode.(type) {
|
||||
// NewBlockedResp returns a blocked response DNS message based on the given
|
||||
// blocking mode. If mode is nil, the constructor's blocking mode is used.
|
||||
func (c *Constructor) NewBlockedResp(req *dns.Msg, mode BlockingMode) (msg *dns.Msg, err error) {
|
||||
if mode == nil {
|
||||
mode = c.blockingMode
|
||||
}
|
||||
|
||||
switch m := mode.(type) {
|
||||
case *BlockingModeCustomIP:
|
||||
return c.newBlockedCustomIPResp(req, m)
|
||||
case *BlockingModeNullIP:
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestConstructor_NewBlockedResp_nullIP(t *testing.T) {
|
||||
|
||||
req := dnsservertest.NewReq(testFQDN, tc.qt, dns.ClassINET, reqExtra)
|
||||
|
||||
resp, respErr := msgs.NewBlockedResp(req)
|
||||
resp, respErr := msgs.NewBlockedResp(req, nil)
|
||||
require.NoError(t, respErr)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestConstructor_NewBlockedResp_nullIP(t *testing.T) {
|
||||
func TestConstructor_NewBlockedResp_customIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cloner := agdtest.NewCloner()
|
||||
msgs := agdtest.NewConstructor(t)
|
||||
|
||||
// TODO(a.garipov): Test the forged extra as well if the EDE with that code
|
||||
// is used again.
|
||||
@@ -139,20 +139,11 @@ func TestConstructor_NewBlockedResp_customIP(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msgs, err := dnsmsg.NewConstructor(&dnsmsg.ConstructorConfig{
|
||||
Cloner: cloner,
|
||||
BlockingMode: tc.blockingMode,
|
||||
StructuredErrors: agdtest.NewSDEConfig(true),
|
||||
FilteredResponseTTL: agdtest.FilteredResponseTTL,
|
||||
EDEEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("a", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET, reqExtra)
|
||||
resp, respErr := msgs.NewBlockedResp(req)
|
||||
resp, respErr := msgs.NewBlockedResp(req, tc.blockingMode)
|
||||
require.NoError(t, respErr)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
@@ -165,7 +156,7 @@ func TestConstructor_NewBlockedResp_customIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := dnsservertest.NewReq(testFQDN, dns.TypeAAAA, dns.ClassINET, reqExtra)
|
||||
resp, respErr := msgs.NewBlockedResp(req)
|
||||
resp, respErr := msgs.NewBlockedResp(req, tc.blockingMode)
|
||||
require.NoError(t, respErr)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
@@ -180,10 +171,11 @@ func TestConstructor_NewBlockedResp_customIP(t *testing.T) {
|
||||
func TestConstructor_NewBlockedResp_nodata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msgs := agdtest.NewConstructor(t)
|
||||
|
||||
req := dnsservertest.NewReq(testFQDN, dns.TypeA, dns.ClassINET, dnsservertest.SectionExtra{
|
||||
dnsservertest.NewOPT(true, dns.MaxMsgSize, &dns.EDNS0_EDE{}),
|
||||
})
|
||||
cloner := agdtest.NewCloner()
|
||||
|
||||
wantExtra := []dns.RR{dnsservertest.NewOPT(true, dns.MaxMsgSize, &dns.EDNS0_EDE{
|
||||
InfoCode: dns.ExtendedErrorCodeFiltered,
|
||||
@@ -208,16 +200,7 @@ func TestConstructor_NewBlockedResp_nodata(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msgs, err := dnsmsg.NewConstructor(&dnsmsg.ConstructorConfig{
|
||||
Cloner: cloner,
|
||||
BlockingMode: tc.blockingMode,
|
||||
StructuredErrors: agdtest.NewSDEConfig(true),
|
||||
FilteredResponseTTL: agdtest.FilteredResponseTTL,
|
||||
EDEEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := msgs.NewBlockedResp(req)
|
||||
resp, err := msgs.NewBlockedResp(req, tc.blockingMode)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
@@ -312,7 +295,7 @@ func TestConstructor_NewBlockedResp_sde(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := msgs.NewBlockedResp(tc.req)
|
||||
resp, err := msgs.NewBlockedResp(tc.req, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
|
||||
@@ -232,6 +232,27 @@ func NewSRV(name string, ttl uint32, target string, prio, weight, port uint16) (
|
||||
}
|
||||
}
|
||||
|
||||
// NewSVCB constructs the new resource record of type SVCB.
|
||||
func NewSVCB(
|
||||
name string,
|
||||
ttl uint32,
|
||||
target string,
|
||||
prio uint16,
|
||||
values ...dns.SVCBKeyValue,
|
||||
) (rr dns.RR) {
|
||||
return &dns.SVCB{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(name),
|
||||
Rrtype: dns.TypeSVCB,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: ttl,
|
||||
},
|
||||
Priority: prio,
|
||||
Target: target,
|
||||
Value: values,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTXT constructs the new resource record of type TXT. txts are put into the
|
||||
// TXT record as is.
|
||||
func NewTXT(name string, ttl uint32, txts ...string) (rr dns.RR) {
|
||||
@@ -246,6 +267,20 @@ func NewTXT(name string, ttl uint32, txts ...string) (rr dns.RR) {
|
||||
}
|
||||
}
|
||||
|
||||
// NewMX constructs the new resource record of type MX.
|
||||
func NewMX(name string, ttl uint32, preference uint16, mx string) (rr dns.RR) {
|
||||
return &dns.MX{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(name),
|
||||
Rrtype: dns.TypeMX,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: ttl,
|
||||
},
|
||||
Preference: preference,
|
||||
Mx: mx,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSOA constructs the new resource record of type SOA.
|
||||
func NewSOA(name string, ttl uint32, ns, mbox string) (rr dns.RR) {
|
||||
return &dns.SOA{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
|
||||
|
||||
go 1.25.1
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.34.1
|
||||
github.com/AdguardTeam/golibs v0.35.2
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||
github.com/ameshkov/dnsstamps v1.0.3
|
||||
github.com/bluele/gcache v0.0.2
|
||||
@@ -11,34 +11,33 @@ require (
|
||||
github.com/miekg/dns v1.1.68
|
||||
github.com/panjf2000/ants/v2 v2.11.3
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
|
||||
github.com/prometheus/client_golang v1.23.1
|
||||
github.com/quic-go/quic-go v0.54.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/quic-go/quic-go v0.55.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/sys v0.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.0 // indirect
|
||||
github.com/prometheus/common v0.67.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
github.com/AdguardTeam/golibs v0.34.1 h1:RyBpZiXnJqlO3T+xjWldlxsEZDelmaFfKvXiJHDZZFQ=
|
||||
github.com/AdguardTeam/golibs v0.35.0 h1:O990+tbZ5W5yB0ybtaUJy4FUb0bXxyzeUC7t8cr1pCg=
|
||||
github.com/AdguardTeam/golibs v0.35.0/go.mod h1:y552twxCtvOD8KKQ7ESjo10KZBAE+HSj24yAuAvz9IA=
|
||||
github.com/AdguardTeam/golibs v0.35.2 h1:GVlx/CiCz5ZXQmyvFrE3JyeGsgubE8f4rJvRshYJVVs=
|
||||
github.com/AdguardTeam/golibs v0.35.2/go.mod h1:p/l6tG7QCv+Hi5yVpv1oZInoatRGOWoyD1m+Ume+ZNY=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
@@ -16,7 +19,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -31,16 +33,18 @@ github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
|
||||
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
|
||||
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
|
||||
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
@@ -51,18 +55,35 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -84,7 +84,9 @@ func newTestService(
|
||||
SafeBrowsing: &filter.ConfigSafeBrowsing{},
|
||||
},
|
||||
Access: access.EmptyProfile{},
|
||||
AdultBlockingMode: nil,
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: nil,
|
||||
ID: dnssvctest.ProfileID,
|
||||
DeviceIDs: container.NewMapSet(dnssvctest.DeviceID),
|
||||
FilteredResponseTTL: agdtest.FilteredResponseTTL,
|
||||
|
||||
@@ -124,14 +124,18 @@ var (
|
||||
// Common profiles, devices, and results for tests.
|
||||
var (
|
||||
profNormal = &agd.Profile{
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: dnssvctest.ProfileID,
|
||||
DeviceIDs: container.NewMapSet(dnssvctest.DeviceID),
|
||||
Deleted: false,
|
||||
}
|
||||
|
||||
profDeleted = &agd.Profile{
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: dnssvctest.ProfileID,
|
||||
DeviceIDs: container.NewMapSet(dnssvctest.DeviceID),
|
||||
Deleted: true,
|
||||
|
||||
@@ -232,46 +232,45 @@ func (mw *Middleware) handleBadResolverARPA(
|
||||
return errors.Annotate(err, "writing nodata resp for %q: %w", ri.Host)
|
||||
}
|
||||
|
||||
// specialDomainHandler returns a handler that can handle a special-domain
|
||||
// query for Apple Private Relay or Firefox canary domain based on the request
|
||||
// or profile information, as well as the handler's name for debugging.
|
||||
// specialDomainHandler, based on the request or profile information, returns a
|
||||
// handler for a special-domain query for the following third-party services:
|
||||
// - [Apple Private Relay]
|
||||
// - [Chrome private prefetch proxy]
|
||||
// - [Firefox DNS-over-HTTPS disabled detector]
|
||||
//
|
||||
// It also returns the handler's name for debugging. ri must not be nil.
|
||||
//
|
||||
// [Apple Private Relay]: https://developer.apple.com/icloud/prepare-your-network-for-icloud-private-relay
|
||||
// [Chrome private prefetch proxy]: https://developer.chrome.com/docs/privacy-security/private-prefetch-proxy-for-network-admins
|
||||
// [Firefox DNS-over-HTTPS disabled detector]: https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https
|
||||
func (mw *Middleware) specialDomainHandler(
|
||||
ri *agd.RequestInfo,
|
||||
) (f reqInfoHandlerFunc, name string) {
|
||||
switch {
|
||||
case shouldBlockPrivateRelay(ri):
|
||||
return mw.handlePrivateRelay, "apple_private_relay"
|
||||
case shouldBlockChromePrefetch(ri):
|
||||
return mw.handleChromePrefetch, "chrome_prefetch"
|
||||
case shouldBlockFirefoxCanary(ri):
|
||||
return mw.handleFirefoxCanary, "firefox"
|
||||
default:
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
// shouldBlockChromePrefetch returns true if ri or the associated profile
|
||||
// indicates that the Chrome prefetch domain should be blocked.
|
||||
func shouldBlockChromePrefetch(ri *agd.RequestInfo) (ok bool) {
|
||||
qt := ri.QType
|
||||
if qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
return nil, ""
|
||||
return false
|
||||
}
|
||||
|
||||
if ri.Host != ChromePrefetchHost {
|
||||
return false
|
||||
}
|
||||
|
||||
host := ri.Host
|
||||
prof, _ := ri.DeviceData()
|
||||
|
||||
switch host {
|
||||
case
|
||||
ApplePrivateRelayMaskHost,
|
||||
ApplePrivateRelayMaskH2Host,
|
||||
ApplePrivateRelayMaskCanaryHost:
|
||||
if shouldBlockPrivateRelay(ri, prof) {
|
||||
return mw.handlePrivateRelay, "apple_private_relay"
|
||||
}
|
||||
case ChromePrefetchHost:
|
||||
if shouldBlockChromePrefetch(ri, prof) {
|
||||
return mw.handleChromePrefetch, "chrome_prefetch"
|
||||
}
|
||||
case FirefoxCanaryHost:
|
||||
if shouldBlockFirefoxCanary(ri, prof) {
|
||||
return mw.handleFirefoxCanary, "firefox"
|
||||
}
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// shouldBlockChromePrefetch returns true request information or profile
|
||||
// indicate that the Chrome prefetch domain should be blocked.
|
||||
func shouldBlockChromePrefetch(ri *agd.RequestInfo, prof *agd.Profile) (ok bool) {
|
||||
if prof != nil {
|
||||
return prof.BlockChromePrefetch
|
||||
}
|
||||
@@ -295,9 +294,19 @@ func (mw *Middleware) handleChromePrefetch(
|
||||
return errors.Annotate(err, "writing chrome prefetch resp: %w")
|
||||
}
|
||||
|
||||
// shouldBlockFirefoxCanary returns true request information or profile indicate
|
||||
// that the Firefox canary domain should be blocked.
|
||||
func shouldBlockFirefoxCanary(ri *agd.RequestInfo, prof *agd.Profile) (ok bool) {
|
||||
// shouldBlockFirefoxCanary returns true if ri or the associated profile
|
||||
// indicates that the Firefox canary domain should be blocked.
|
||||
func shouldBlockFirefoxCanary(ri *agd.RequestInfo) (ok bool) {
|
||||
qt := ri.QType
|
||||
if qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
return false
|
||||
}
|
||||
|
||||
if ri.Host != FirefoxCanaryHost {
|
||||
return false
|
||||
}
|
||||
|
||||
prof, _ := ri.DeviceData()
|
||||
if prof != nil {
|
||||
return prof.BlockFirefoxCanary
|
||||
}
|
||||
@@ -321,9 +330,20 @@ func (mw *Middleware) handleFirefoxCanary(
|
||||
return errors.Annotate(err, "writing firefox canary resp: %w")
|
||||
}
|
||||
|
||||
// shouldBlockPrivateRelay returns true request information or profile indicate
|
||||
// that the Apple Private Relay domain should be blocked.
|
||||
func shouldBlockPrivateRelay(ri *agd.RequestInfo, prof *agd.Profile) (ok bool) {
|
||||
// shouldBlockPrivateRelay returns true if ri or the associated profile
|
||||
// indicates that the Apple Private Relay domain should be blocked.
|
||||
func shouldBlockPrivateRelay(ri *agd.RequestInfo) (ok bool) {
|
||||
switch ri.Host {
|
||||
case
|
||||
ApplePrivateRelayMaskHost,
|
||||
ApplePrivateRelayMaskH2Host,
|
||||
ApplePrivateRelayMaskCanaryHost:
|
||||
// Go on.
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
prof, _ := ri.DeviceData()
|
||||
if prof != nil {
|
||||
return prof.BlockPrivateRelay
|
||||
}
|
||||
|
||||
@@ -71,8 +71,16 @@ func TestMiddleware_Wrap_specialDomain(t *testing.T) {
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
reqInfo: newSpecDomReqInfo(t, nil, fltGrpBlocked, appleHost, dns.TypeTXT),
|
||||
name: "no_private_relay_qtype",
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
name: "private_relay_blocked_txt",
|
||||
wantRCode: dns.RcodeNameError,
|
||||
}, {
|
||||
reqInfo: newSpecDomReqInfo(t, nil, fltGrpBlocked, appleHost, dns.TypeCNAME),
|
||||
name: "private_relay_blocked_cname",
|
||||
wantRCode: dns.RcodeNameError,
|
||||
}, {
|
||||
reqInfo: newSpecDomReqInfo(t, nil, fltGrpBlocked, appleHost, dns.TypeHTTPS),
|
||||
name: "private_relay_blocked_https",
|
||||
wantRCode: dns.RcodeNameError,
|
||||
}, {
|
||||
reqInfo: newSpecDomReqInfo(t, profBlocked, fltGrpAllowed, appleHost, dns.TypeA),
|
||||
name: "private_relay_blocked_by_prof",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package mainmw
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/miekg/dns"
|
||||
@@ -220,7 +222,8 @@ func resultData(
|
||||
|
||||
// setFilteredResponse sets the response in fctx if the filtering results
|
||||
// require that. After calling setFilteredResponse, fctx.filteredResponse will
|
||||
// not be nil. All errors are reported using [Middleware.reportf].
|
||||
// not be nil. All errors are reported using [Middleware.reportf]. fctx and ri
|
||||
// must not be nil.
|
||||
func (mw *Middleware) setFilteredResponse(
|
||||
ctx context.Context,
|
||||
fctx *filteringContext,
|
||||
@@ -230,21 +233,28 @@ func (mw *Middleware) setFilteredResponse(
|
||||
case nil:
|
||||
mw.setFilteredResponseNoReq(ctx, fctx, ri)
|
||||
case *filter.ResultBlocked:
|
||||
var err error
|
||||
fctx.filteredResponse, err = ri.Messages.NewBlockedResp(fctx.originalRequest)
|
||||
if err != nil {
|
||||
errcoll.Collect(
|
||||
ctx,
|
||||
mw.errColl,
|
||||
mw.logger,
|
||||
"creating blocked resp for filtered req",
|
||||
err,
|
||||
)
|
||||
blockingMode := resultBlockingMode(ri, reqRes)
|
||||
|
||||
mw.setFilteredResponseFromBlockingMode(ctx, fctx, ri, blockingMode)
|
||||
case *filter.ResultAllowed:
|
||||
fctx.filteredResponse = fctx.originalResponse
|
||||
case *filter.ResultModifiedRequest:
|
||||
blockingMode := filterBlockingMode(ri, reqRes)
|
||||
if blockingMode != nil {
|
||||
mw.setFilteredResponseFromBlockingMode(ctx, fctx, ri, blockingMode)
|
||||
|
||||
return
|
||||
}
|
||||
case *filter.ResultAllowed, *filter.ResultModifiedRequest:
|
||||
|
||||
fctx.filteredResponse = fctx.originalResponse
|
||||
case *filter.ResultModifiedResponse:
|
||||
blockingMode := filterBlockingMode(ri, reqRes)
|
||||
if blockingMode != nil {
|
||||
mw.setFilteredResponseFromBlockingMode(ctx, fctx, ri, blockingMode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Only use the request filtering result in case it's already a
|
||||
// response. Otherwise, it's a CNAME rewrite result, which isn't
|
||||
// filtered after resolving.
|
||||
@@ -259,6 +269,25 @@ func (mw *Middleware) setFilteredResponse(
|
||||
}
|
||||
}
|
||||
|
||||
// setFilteredResponseFromBlockingMode sets the response in fctx if for the
|
||||
// given blocking mode. After calling, fctx.filteredResponse will not be nil.
|
||||
// All errors are reported using [Middleware.reportf]. fctx and ri must not be
|
||||
// nil.
|
||||
func (mw *Middleware) setFilteredResponseFromBlockingMode(
|
||||
ctx context.Context,
|
||||
fctx *filteringContext,
|
||||
ri *agd.RequestInfo,
|
||||
blockingMode dnsmsg.BlockingMode,
|
||||
) {
|
||||
var err error
|
||||
fctx.filteredResponse, err = ri.Messages.NewBlockedResp(fctx.originalRequest, blockingMode)
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, mw.errColl, mw.logger, "creating blocked resp for filtered req", err)
|
||||
|
||||
fctx.filteredResponse = fctx.originalResponse
|
||||
}
|
||||
}
|
||||
|
||||
// setFilteredResponseNoReq sets the response in fctx if the response filtering
|
||||
// results require that. After calling setFilteredResponseNoReq,
|
||||
// fctx.filteredResponse will not be nil. All errors are reported using
|
||||
@@ -273,8 +302,10 @@ func (mw *Middleware) setFilteredResponseNoReq(
|
||||
case nil, *filter.ResultAllowed:
|
||||
fctx.filteredResponse = fctx.originalResponse
|
||||
case *filter.ResultBlocked:
|
||||
blockingMode := resultBlockingMode(ri, respRes)
|
||||
|
||||
var err error
|
||||
fctx.filteredResponse, err = ri.Messages.NewBlockedResp(fctx.originalRequest)
|
||||
fctx.filteredResponse, err = ri.Messages.NewBlockedResp(fctx.originalRequest, blockingMode)
|
||||
if err != nil {
|
||||
errcoll.Collect(
|
||||
ctx,
|
||||
@@ -296,3 +327,47 @@ func (mw *Middleware) setFilteredResponseNoReq(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// resultBlockingMode returns the blocking mode for the given filtering result,
|
||||
// returns profile's blocking mode if the result is not related to adult
|
||||
// blocking or safe browsing filters. ri must not be nil.
|
||||
//
|
||||
// TODO(a.garipov): Remove this temp solution by improving blocking mode API.
|
||||
func resultBlockingMode(ri *agd.RequestInfo, res filter.Result) (m dnsmsg.BlockingMode) {
|
||||
profile, _ := ri.DeviceData()
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fltID, _ := res.MatchedRule()
|
||||
switch fltID {
|
||||
case filter.IDAdultBlocking:
|
||||
return cmp.Or(profile.AdultBlockingMode, profile.BlockingMode)
|
||||
case filter.IDSafeBrowsing:
|
||||
return cmp.Or(profile.SafeBrowsingBlockingMode, profile.BlockingMode)
|
||||
}
|
||||
|
||||
return profile.BlockingMode
|
||||
}
|
||||
|
||||
// filterBlockingMode returns the blocking mode for the given filtering result,
|
||||
// returns nil if the result is not related to adult blocking or safe browsing
|
||||
// filters. ri must not be nil.
|
||||
//
|
||||
// TODO(a.garipov): Remove this temp solution by improving blocking mode API.
|
||||
func filterBlockingMode(ri *agd.RequestInfo, res filter.Result) (m dnsmsg.BlockingMode) {
|
||||
profile, _ := ri.DeviceData()
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fltID, _ := res.MatchedRule()
|
||||
switch fltID {
|
||||
case filter.IDAdultBlocking:
|
||||
return profile.AdultBlockingMode
|
||||
case filter.IDSafeBrowsing:
|
||||
return profile.SafeBrowsingBlockingMode
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/dnssvctest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
@@ -19,14 +21,18 @@ import (
|
||||
|
||||
// TODO(a.garipov): Rewrite into cases in external tests.
|
||||
func TestMiddleware_setFilteredResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
respTTL = 60
|
||||
fltRespTTL = agdtest.FilteredResponseTTLSec
|
||||
)
|
||||
|
||||
const fltRespTTL = agdtest.FilteredResponseTTLSec
|
||||
respIP := netip.MustParseAddr("1.2.3.4")
|
||||
rewrIP := netip.MustParseAddr("5.6.7.8")
|
||||
blockIP := netip.IPv4Unspecified()
|
||||
blockIPAdult := netip.MustParseAddr("2.2.2.2")
|
||||
blockIPSafeBrowsing := netip.MustParseAddr("3.3.3.3")
|
||||
|
||||
const domain = "example.com"
|
||||
origReq := dnsservertest.NewReq(domain, dns.TypeA, dns.ClassINET)
|
||||
@@ -61,6 +67,22 @@ func TestMiddleware_setFilteredResponse(t *testing.T) {
|
||||
wantIP: blockIP,
|
||||
name: "blocked_req",
|
||||
wantTTL: fltRespTTL,
|
||||
}, {
|
||||
reqRes: &filter.ResultBlocked{
|
||||
List: filter.IDAdultBlocking,
|
||||
},
|
||||
respRes: nil,
|
||||
wantIP: blockIPAdult,
|
||||
name: "blocked_req_adult",
|
||||
wantTTL: fltRespTTL,
|
||||
}, {
|
||||
reqRes: &filter.ResultBlocked{
|
||||
List: filter.IDSafeBrowsing,
|
||||
},
|
||||
respRes: nil,
|
||||
wantIP: blockIPSafeBrowsing,
|
||||
name: "blocked_req_safe_browsing",
|
||||
wantTTL: fltRespTTL,
|
||||
}, {
|
||||
reqRes: &filter.ResultModifiedResponse{Msg: rewrResp},
|
||||
respRes: nil,
|
||||
@@ -79,14 +101,46 @@ func TestMiddleware_setFilteredResponse(t *testing.T) {
|
||||
wantIP: blockIP,
|
||||
name: "blocked_resp",
|
||||
wantTTL: fltRespTTL,
|
||||
}, {
|
||||
reqRes: nil,
|
||||
respRes: &filter.ResultBlocked{
|
||||
List: filter.IDAdultBlocking,
|
||||
},
|
||||
wantIP: blockIPAdult,
|
||||
name: "blocked_resp_adult",
|
||||
wantTTL: fltRespTTL,
|
||||
}, {
|
||||
reqRes: nil,
|
||||
respRes: &filter.ResultBlocked{
|
||||
List: filter.IDSafeBrowsing,
|
||||
},
|
||||
wantIP: blockIPSafeBrowsing,
|
||||
name: "blocked_resp_safe_browsing",
|
||||
wantTTL: fltRespTTL,
|
||||
}}
|
||||
|
||||
device := &agd.Device{ID: dnssvctest.DeviceID}
|
||||
profile := &agd.Profile{
|
||||
ID: dnssvctest.ProfileID,
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{blockIPAdult},
|
||||
},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: []netip.Addr{blockIPSafeBrowsing},
|
||||
},
|
||||
}
|
||||
ri := &agd.RequestInfo{
|
||||
DeviceResult: &agd.DeviceResultOK{
|
||||
Device: device,
|
||||
Profile: profile,
|
||||
},
|
||||
Messages: agdtest.NewConstructor(t),
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
origResp := dnsservertest.NewResp(dns.RcodeSuccess, origReq)
|
||||
origResp.Answer = append(origResp.Answer, dnsservertest.NewA(domain, respTTL, respIP))
|
||||
|
||||
@@ -115,6 +169,8 @@ func TestMiddleware_setFilteredResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("modified_resp", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantPanicMsg := (&agd.ArgumentError{
|
||||
Name: "respRes",
|
||||
Message: fmt.Sprintf("unexpected type %T", &filter.ResultModifiedResponse{}),
|
||||
|
||||
@@ -14,14 +14,16 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/optslog"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// recordQueryInfo extracts loggable information from request, response, and
|
||||
// filtering data and writes them to the query log, billing, and filtering-rule
|
||||
// statistics, handling non-critical errors.
|
||||
// statistics, handling non-critical errors. fctx and ri must not be nil.
|
||||
func (mw *Middleware) recordQueryInfo(
|
||||
ctx context.Context,
|
||||
fctx *filteringContext,
|
||||
@@ -39,8 +41,8 @@ func (mw *Middleware) recordQueryInfo(
|
||||
|
||||
var reqCtry geoip.Country
|
||||
var reqASN geoip.ASN
|
||||
if g := ri.Location; g != nil {
|
||||
reqCtry, reqASN = g.Country, g.ASN
|
||||
if loc := ri.Location; loc != nil {
|
||||
reqCtry, reqASN = loc.Country, loc.ASN
|
||||
}
|
||||
|
||||
reqInfo := dnsserver.MustRequestInfoFromContext(ctx)
|
||||
@@ -51,21 +53,6 @@ func (mw *Middleware) recordQueryInfo(
|
||||
return
|
||||
}
|
||||
|
||||
rcode, respIP, respDNSSEC := mw.responseData(ctx, fctx.filteredResponse)
|
||||
if blocked {
|
||||
// If the request or the response were blocked, resp may contain an
|
||||
// unspecified IP address, a rewritten IP address, or none at all, while
|
||||
// the original response may contain an actual IP address that should be
|
||||
// used to determine the response country.
|
||||
_, respIP, _ = mw.responseData(ctx, fctx.originalResponse)
|
||||
}
|
||||
|
||||
var clientIP netip.Addr
|
||||
if prof.IPLogEnabled {
|
||||
clientIP = ri.RemoteIP
|
||||
}
|
||||
|
||||
q := fctx.originalRequest.Question[0]
|
||||
e := &querylog.Entry{
|
||||
RequestResult: fctx.requestResult,
|
||||
ResponseResult: fctx.responseResult,
|
||||
@@ -74,20 +61,50 @@ func (mw *Middleware) recordQueryInfo(
|
||||
ProfileID: prof.ID,
|
||||
DeviceID: devID,
|
||||
ClientCountry: reqCtry,
|
||||
ResponseCountry: mw.responseCountry(ctx, fctx, ri.Host, respIP, rcode),
|
||||
DomainFQDN: q.Name,
|
||||
DomainFQDN: fctx.originalRequest.Question[0].Name,
|
||||
Elapsed: time.Since(start),
|
||||
ClientASN: reqASN,
|
||||
RequestType: ri.QType,
|
||||
ResponseCode: rcode,
|
||||
Protocol: ri.ServerInfo.Protocol,
|
||||
DNSSEC: respDNSSEC,
|
||||
RemoteIP: clientIP,
|
||||
}
|
||||
|
||||
mw.writeQueryLogEntry(ctx, fctx, ri, e, blocked, prof.IPLogEnabled)
|
||||
}
|
||||
|
||||
// writeQueryLogEntry adds properties to e and writes it to the query log.
|
||||
// fctx, ri, and e must not be nil.
|
||||
func (mw *Middleware) writeQueryLogEntry(
|
||||
ctx context.Context,
|
||||
fctx *filteringContext,
|
||||
ri *agd.RequestInfo,
|
||||
e *querylog.Entry,
|
||||
blocked bool,
|
||||
logIP bool,
|
||||
) {
|
||||
var respIP netip.Addr
|
||||
e.ResponseCode, respIP, e.DNSSEC = mw.responseData(ctx, fctx.filteredResponse)
|
||||
if blocked {
|
||||
// If the request or the response were blocked, resp may contain an
|
||||
// unspecified IP address, a rewritten IP address, or none at all, while
|
||||
// the original response may contain an actual IP address that should be
|
||||
// used to determine the response country.
|
||||
_, respIP, _ = mw.responseData(ctx, fctx.originalResponse)
|
||||
}
|
||||
|
||||
if logIP {
|
||||
e.RemoteIP = ri.RemoteIP
|
||||
}
|
||||
|
||||
e.ResponseCountry = mw.responseCountry(ctx, fctx, ri.Host, respIP, e.ResponseCode)
|
||||
|
||||
err := mw.queryLog.Write(ctx, e)
|
||||
if err != nil {
|
||||
// Consider query logging errors non-critical.
|
||||
// Consider query logging errors non-critical and don't collect timeouts.
|
||||
switch {
|
||||
case err == nil:
|
||||
// Go on.
|
||||
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
|
||||
mw.logger.DebugContext(ctx, "writing query log", slogutil.KeyError, err)
|
||||
default:
|
||||
errcoll.Collect(ctx, mw.errColl, mw.logger, "writing query log", err)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http/cookiejar"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
@@ -18,9 +19,9 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
"github.com/AdguardTeam/golibs/syncutil"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// FilterConfig is the hash-prefix filter configuration structure.
|
||||
@@ -43,12 +44,16 @@ type FilterConfig struct {
|
||||
// ErrColl is used to collect non-critical and rare errors.
|
||||
ErrColl errcoll.Interface
|
||||
|
||||
// HashPrefixMtcs are the specific metrics for the hashprefix filter.
|
||||
HashPrefixMtcs Metrics
|
||||
// HashPrefixMetrics are the specific metrics for the hashprefix filter.
|
||||
HashPrefixMetrics Metrics
|
||||
|
||||
// Metrics are the metrics for the hashprefix filter.
|
||||
Metrics filter.Metrics
|
||||
|
||||
// PublicSuffixList is used for obtaining public suffix for specified
|
||||
// domain.
|
||||
PublicSuffixList cookiejar.PublicSuffixList
|
||||
|
||||
// ID is the ID of this hash storage for logging and error reporting.
|
||||
ID filter.ID
|
||||
|
||||
@@ -79,6 +84,10 @@ type FilterConfig struct {
|
||||
|
||||
// MaxSize is the maximum size of the downloadable rule-list.
|
||||
MaxSize datasize.ByteSize
|
||||
|
||||
// SubDomainNum defines how many labels should be hashed to match against a
|
||||
// hash prefix filter. It must be positive and fit into int.
|
||||
SubDomainNum uint
|
||||
}
|
||||
|
||||
// Filter is a filter that matches hosts by their hashes based on a hash-prefix
|
||||
@@ -88,13 +97,16 @@ type Filter struct {
|
||||
cloner *dnsmsg.Cloner
|
||||
hashes *Storage
|
||||
refr *refreshable.Refreshable
|
||||
subDomainsPool *syncutil.Pool[[]string]
|
||||
errColl errcoll.Interface
|
||||
hashprefixMtcs Metrics
|
||||
hashprefixMtrc Metrics
|
||||
publicSuffixList cookiejar.PublicSuffixList
|
||||
metrics filter.Metrics
|
||||
resCache agdcache.Interface[rulelist.CacheKey, filter.Result]
|
||||
id filter.ID
|
||||
repIP netip.Addr
|
||||
repFQDN string
|
||||
subDomainNum int
|
||||
}
|
||||
|
||||
// IDPrefix is a common prefix for cache IDs, logging, and refreshes of
|
||||
@@ -119,11 +131,19 @@ func NewFilter(c *FilterConfig) (f *Filter, err error) {
|
||||
logger: c.Logger,
|
||||
cloner: c.Cloner,
|
||||
hashes: c.Hashes,
|
||||
// #nosec G115 -- Assume that c.SubDomainNum is always less then or
|
||||
// equal to 63.
|
||||
//
|
||||
// TODO(f.setrakov): Validate c.SubDomainsNum.
|
||||
subDomainsPool: syncutil.NewSlicePool[string](int(c.SubDomainNum)),
|
||||
errColl: c.ErrColl,
|
||||
hashprefixMtcs: c.HashPrefixMtcs,
|
||||
hashprefixMtrc: c.HashPrefixMetrics,
|
||||
publicSuffixList: c.PublicSuffixList,
|
||||
metrics: c.Metrics,
|
||||
resCache: resCache,
|
||||
id: id,
|
||||
// #nosec G115 -- The value is a constant less than int accommodates.
|
||||
subDomainNum: int(c.SubDomainNum),
|
||||
}
|
||||
|
||||
repHost := c.ReplacementHost
|
||||
@@ -165,7 +185,7 @@ func (f *Filter) FilterRequest(
|
||||
|
||||
cacheKey := rulelist.NewCacheKey(host, qt, cl, false)
|
||||
item, ok := f.resCache.Get(cacheKey)
|
||||
f.hashprefixMtcs.IncrementLookups(ctx, ok)
|
||||
f.hashprefixMtrc.IncrementLookups(ctx, ok)
|
||||
if ok {
|
||||
return f.clonedResult(req.DNS, item), nil
|
||||
}
|
||||
@@ -176,8 +196,11 @@ func (f *Filter) FilterRequest(
|
||||
}
|
||||
|
||||
var matched string
|
||||
sub := hashableSubdomains(host)
|
||||
for _, s := range sub {
|
||||
subPtr := f.subDomainsPool.Get()
|
||||
defer f.subDomainsPool.Put(subPtr)
|
||||
|
||||
*subPtr = agdnet.AppendSubdomains((*subPtr)[:0], host, f.subDomainNum, f.publicSuffixList)
|
||||
for _, s := range *subPtr {
|
||||
if f.hashes.Matches(s) {
|
||||
matched = s
|
||||
|
||||
@@ -199,7 +222,7 @@ func (f *Filter) FilterRequest(
|
||||
|
||||
f.setInCache(cacheKey, r)
|
||||
|
||||
f.hashprefixMtcs.UpdateCacheSize(ctx, f.resCache.Len())
|
||||
f.hashprefixMtrc.UpdateCacheSize(ctx, f.resCache.Len())
|
||||
|
||||
return r, nil
|
||||
}
|
||||
@@ -232,7 +255,7 @@ func (f *Filter) clonedResult(req *dns.Msg, r filter.Result) (clone filter.Resul
|
||||
}
|
||||
}
|
||||
|
||||
// filteredResult returns a filtered request or response.
|
||||
// filteredResult returns a filtered request or response. req must not be nil.
|
||||
func (f *Filter) filteredResult(
|
||||
req *filter.Request,
|
||||
matched string,
|
||||
@@ -274,7 +297,7 @@ func (f *Filter) respForFamily(
|
||||
//
|
||||
// TODO(ameshkov): Consider putting the resolved IP addresses into hints
|
||||
// to show the blocked page here as well?
|
||||
return req.Messages.NewBlockedResp(req.DNS)
|
||||
return req.Messages.NewBlockedResp(req.DNS, nil)
|
||||
}
|
||||
|
||||
ip := f.repIP
|
||||
@@ -368,40 +391,3 @@ func (f *Filter) refresh(ctx context.Context, acceptStale bool) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// subDomainNum defines how many labels should be hashed to match against a hash
|
||||
// prefix filter.
|
||||
const subDomainNum = 4
|
||||
|
||||
// hashableSubdomains returns all subdomains that should be checked by the hash
|
||||
// prefix filter.
|
||||
func hashableSubdomains(domain string) (sub []string) {
|
||||
pubSuf, icann := publicsuffix.PublicSuffix(domain)
|
||||
if !icann {
|
||||
// Check the full private domain space.
|
||||
pubSuf = ""
|
||||
}
|
||||
|
||||
dotsNum := 0
|
||||
i := strings.LastIndexFunc(domain, func(r rune) (ok bool) {
|
||||
if r == '.' {
|
||||
dotsNum++
|
||||
}
|
||||
|
||||
return dotsNum == subDomainNum
|
||||
})
|
||||
if i != -1 {
|
||||
domain = domain[i+1:]
|
||||
}
|
||||
|
||||
sub = netutil.Subdomains(domain)
|
||||
for i, s := range sub {
|
||||
if s == pubSuf {
|
||||
sub = sub[:i]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return sub
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ composite.RequestFilter = (*hashprefix.Filter)(nil)
|
||||
|
||||
func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
func TestFilter_FilterRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msgs := agdtest.NewConstructor(t)
|
||||
@@ -115,7 +116,7 @@ func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
var wantRes filter.Result
|
||||
if tc.wantResult {
|
||||
if tc.replHost == filtertest.HostAdultContentRepl {
|
||||
wantRes = newModReqResult(req, tc.wantRule)
|
||||
wantRes = newModReqResult(t, req, tc.wantRule)
|
||||
} else {
|
||||
wantRes = newModRespResult(t, req, msgs, filtertest.IPv4AdultContentRepl)
|
||||
}
|
||||
@@ -124,9 +125,15 @@ func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_cache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking)
|
||||
|
||||
require.True(t, t.Run("cached_success", func(t *testing.T) {
|
||||
f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking)
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewARequest(t, filtertest.HostAdultContent)
|
||||
|
||||
@@ -141,21 +148,27 @@ func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("cached_no_match", func(t *testing.T) {
|
||||
f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking)
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewARequest(t, filtertest.Host)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
r, err := f.FilterRequest(ctx, req)
|
||||
original, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
cached, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
filtertest.AssertEqualResult(t, cached, r)
|
||||
filtertest.AssertEqualResult(t, cached, original)
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_https(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.True(t, t.Run("https", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := filtertest.NewHashprefixFilter(t, filter.IDAdultBlocking)
|
||||
|
||||
req := filtertest.NewRequest(
|
||||
@@ -171,11 +184,13 @@ func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, r)
|
||||
|
||||
wantRes := newModReqResult(req.DNS, filtertest.HostAdultContent)
|
||||
wantRes := newModReqResult(t, req.DNS, filtertest.HostAdultContent)
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("https_ip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := filtertest.NewHashprefixFilterWithRepl(
|
||||
t,
|
||||
filter.IDAdultBlocking,
|
||||
@@ -204,37 +219,6 @@ func TestFilter_FilterRequest_host(t *testing.T) {
|
||||
}))
|
||||
}
|
||||
|
||||
// newModRespResult is a helper for creating modified results for tests.
|
||||
func newModRespResult(
|
||||
tb testing.TB,
|
||||
req *dns.Msg,
|
||||
messages *dnsmsg.Constructor,
|
||||
replIP netip.Addr,
|
||||
) (r *filter.ResultModifiedResponse) {
|
||||
tb.Helper()
|
||||
|
||||
resp, err := messages.NewRespIP(req, replIP)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return &filter.ResultModifiedResponse{
|
||||
Msg: resp,
|
||||
List: filter.IDAdultBlocking,
|
||||
Rule: filtertest.HostAdultContent,
|
||||
}
|
||||
}
|
||||
|
||||
// newModReqResult is a helper for creating modified results for tests.
|
||||
func newModReqResult(req *dns.Msg, rule filter.RuleText) (r *filter.ResultModifiedRequest) {
|
||||
req = dnsmsg.Clone(req)
|
||||
req.Question[0].Name = filtertest.FQDNAdultContentRepl
|
||||
|
||||
return &filter.ResultModifiedRequest{
|
||||
Msg: req,
|
||||
List: filter.IDAdultBlocking,
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -251,8 +235,9 @@ func TestFilter_Refresh(t *testing.T) {
|
||||
Hashes: strg,
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
HashPrefixMtcs: hashprefix.EmptyMetrics{},
|
||||
HashPrefixMetrics: hashprefix.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: filter.IDAdultBlocking,
|
||||
CachePath: cachePath,
|
||||
ReplacementHost: filtertest.HostAdultContentRepl,
|
||||
@@ -260,6 +245,7 @@ func TestFilter_Refresh(t *testing.T) {
|
||||
CacheTTL: filtertest.CacheTTL,
|
||||
CacheCount: filtertest.CacheCount,
|
||||
MaxSize: filtertest.FilterMaxSize,
|
||||
SubDomainNum: filtertest.SubDomainNum,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -304,8 +290,9 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) {
|
||||
Hashes: strg,
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
HashPrefixMtcs: hashprefix.EmptyMetrics{},
|
||||
HashPrefixMetrics: hashprefix.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: filter.IDAdultBlocking,
|
||||
CachePath: cachePath,
|
||||
ReplacementHost: filtertest.HostAdultContentRepl,
|
||||
@@ -313,6 +300,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) {
|
||||
CacheTTL: filtertest.CacheTTL,
|
||||
CacheCount: filtertest.CacheCount,
|
||||
MaxSize: filtertest.FilterMaxSize,
|
||||
SubDomainNum: filtertest.SubDomainNum,
|
||||
}
|
||||
f, err := hashprefix.NewFilter(fconf)
|
||||
require.NoError(t, err)
|
||||
@@ -339,7 +327,7 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) {
|
||||
r, err = f.FilterRequest(ctx, otherHostReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantRes := newModReqResult(otherHostReq.DNS, filtertest.Host)
|
||||
wantRes := newModReqResult(t, otherHostReq.DNS, filtertest.Host)
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
}))
|
||||
|
||||
@@ -374,7 +362,46 @@ func TestFilter_FilterRequest_staleCache(t *testing.T) {
|
||||
r, err = f.FilterRequest(ctx, hostReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantRes := newModReqResult(hostReq.DNS, filtertest.HostAdultContent)
|
||||
wantRes := newModReqResult(t, hostReq.DNS, filtertest.HostAdultContent)
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// newModRespResult is a helper for creating modified response result for tests.
|
||||
// req must not be nil.
|
||||
func newModRespResult(
|
||||
tb testing.TB,
|
||||
req *dns.Msg,
|
||||
messages *dnsmsg.Constructor,
|
||||
replIP netip.Addr,
|
||||
) (r *filter.ResultModifiedResponse) {
|
||||
tb.Helper()
|
||||
|
||||
resp, err := messages.NewRespIP(req, replIP)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return &filter.ResultModifiedResponse{
|
||||
Msg: resp,
|
||||
List: filter.IDAdultBlocking,
|
||||
Rule: filtertest.HostAdultContent,
|
||||
}
|
||||
}
|
||||
|
||||
// newModReqResult is a helper for creating modified request result for tests.
|
||||
// req must not be nil.
|
||||
func newModReqResult(
|
||||
tb testing.TB,
|
||||
req *dns.Msg,
|
||||
rule filter.RuleText,
|
||||
) (r *filter.ResultModifiedRequest) {
|
||||
tb.Helper()
|
||||
|
||||
req = dnsmsg.Clone(req)
|
||||
req.Question[0].Name = filtertest.FQDNAdultContentRepl
|
||||
|
||||
return &filter.ResultModifiedRequest{
|
||||
Msg: req,
|
||||
List: filter.IDAdultBlocking,
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ func newComposite(tb testing.TB, c *composite.Config) (f *composite.Filter) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_customWithClientName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := newComposite(t, &composite.Config{
|
||||
Custom: custom.New(&custom.Config{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
@@ -96,6 +98,8 @@ func TestFilter_FilterRequest_customWithClientName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_badfilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
blockRule = filtertest.RuleBlockStr
|
||||
badFilterRule = filtertest.RuleBlockStr + "$badfilter"
|
||||
@@ -127,6 +131,8 @@ func TestFilter_FilterRequest_badfilter(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := newComposite(t, &composite.Config{
|
||||
RuleLists: tc.ruleLists,
|
||||
})
|
||||
@@ -149,6 +155,8 @@ func newFromStr(tb testing.TB, text string, id filter.ID) (rl *rulelist.Refresha
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_customAllow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const allowRule = "@@" + filtertest.RuleBlock
|
||||
|
||||
blockingRL := newFromStr(t, filtertest.RuleBlockStr, filtertest.RuleListID1)
|
||||
@@ -174,86 +182,53 @@ func TestFilter_FilterRequest_customAllow(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_dnsrewrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
blockRule = filtertest.RuleBlockStr
|
||||
dnsRewriteRuleRefused = filtertest.RuleBlockStr + "$dnsrewrite=REFUSED"
|
||||
dnsRewriteRuleCname = filtertest.RuleBlockStr + "$dnsrewrite=new-cname.example"
|
||||
dnsRewriteRuleCNAME = filtertest.RuleBlockStr + "$dnsrewrite=" + filtertest.HostCNAME
|
||||
dnsRewrite2Rules = filtertest.RuleBlockStr + "$dnsrewrite=1.2.3.4\n" +
|
||||
filtertest.RuleBlockStr + "$dnsrewrite=1.2.3.5"
|
||||
dnsRewriteRuleTXT = filtertest.RuleBlockStr + "$dnsrewrite=NOERROR;TXT;abcdefg"
|
||||
dnsRewriteRuleSOA = filtertest.RuleBlockStr + "$dnsrewrite=NOERROR;SOA;ns1." +
|
||||
filtertest.FQDNBlocked + " hostmaster." + filtertest.FQDNBlocked +
|
||||
" 1 3600 1800 604800 86400"
|
||||
dnsRewriteTypedRules = dnsRewriteRuleTXT + "\n" + dnsRewriteRuleSOA
|
||||
)
|
||||
|
||||
var (
|
||||
rlNonRewrite = newFromStr(t, blockRule, filtertest.RuleListID1)
|
||||
rlNonRewrite = newFromStr(t, filtertest.RuleBlockStr, filtertest.RuleListID1)
|
||||
rlCustomRefused = newCustom(t, dnsRewriteRuleRefused)
|
||||
rlCustomCname = newCustom(t, dnsRewriteRuleCname)
|
||||
rlCustomCNAME = newCustom(t, dnsRewriteRuleCNAME)
|
||||
rlCustom2Rules = newCustom(t, dnsRewrite2Rules)
|
||||
rlCustomTyped = newCustom(t, dnsRewriteTypedRules)
|
||||
)
|
||||
|
||||
req := dnsservertest.NewReq(filtertest.FQDNBlocked, dns.TypeA, dns.ClassINET)
|
||||
|
||||
// Create a CNAME-modified request.
|
||||
modifiedReq := dnsmsg.Clone(req)
|
||||
modifiedReq.Question[0].Name = "new-cname.example."
|
||||
|
||||
txtReq := dnsmsg.Clone(req)
|
||||
txtReq.Question[0].Qtype = dns.TypeTXT
|
||||
|
||||
soaReq := dnsmsg.Clone(req)
|
||||
soaReq.Question[0].Qtype = dns.TypeSOA
|
||||
|
||||
testCases := []struct {
|
||||
custom filter.Custom
|
||||
req *dns.Msg
|
||||
wantRes filter.Result
|
||||
name string
|
||||
ruleLists []*rulelist.Refreshable
|
||||
}{{
|
||||
custom: nil,
|
||||
req: req,
|
||||
wantRes: &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: blockRule,
|
||||
Rule: filtertest.RuleBlockStr,
|
||||
},
|
||||
name: "block",
|
||||
ruleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
}, {
|
||||
custom: nil,
|
||||
req: req,
|
||||
wantRes: &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: blockRule,
|
||||
},
|
||||
name: "dnsrewrite_no_effect",
|
||||
ruleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
}, {
|
||||
custom: rlCustomRefused,
|
||||
req: req,
|
||||
wantRes: &filter.ResultModifiedResponse{
|
||||
Msg: dnsservertest.NewResp(dns.RcodeRefused, req),
|
||||
List: filter.IDCustom,
|
||||
Rule: dnsRewriteRuleRefused,
|
||||
},
|
||||
name: "dnsrewrite_block",
|
||||
ruleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
}, {
|
||||
custom: rlCustomCname,
|
||||
req: req,
|
||||
custom: rlCustomCNAME,
|
||||
wantRes: &filter.ResultModifiedRequest{
|
||||
Msg: modifiedReq,
|
||||
Msg: dnsservertest.NewReq(filtertest.FQDNCname, dns.TypeA, dns.ClassINET),
|
||||
List: filter.IDCustom,
|
||||
Rule: dnsRewriteRuleCname,
|
||||
Rule: dnsRewriteRuleCNAME,
|
||||
},
|
||||
name: "dnsrewrite_cname",
|
||||
ruleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
}, {
|
||||
custom: rlCustom2Rules,
|
||||
req: req,
|
||||
wantRes: &filter.ResultModifiedResponse{
|
||||
Msg: dnsservertest.NewResp(dns.RcodeSuccess, req, dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewA(
|
||||
@@ -271,9 +246,52 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) {
|
||||
Rule: "",
|
||||
},
|
||||
name: "dnsrewrite_answers",
|
||||
ruleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
}, {
|
||||
custom: rlCustomTyped,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := newComposite(t, &composite.Config{
|
||||
Custom: tc.custom,
|
||||
RuleLists: []*rulelist.Refreshable{rlNonRewrite},
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
res, err := f.FilterRequest(ctx, &filter.Request{
|
||||
DNS: req,
|
||||
Messages: agdtest.NewConstructor(t),
|
||||
Host: filtertest.HostBlocked,
|
||||
QType: req.Question[0].Qtype,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
filtertest.AssertEqualResult(t, tc.wantRes, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_dnsrewriteQType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
dnsRewriteRuleTXT = filtertest.RuleBlockStr + "$dnsrewrite=NOERROR;TXT;abcdefg"
|
||||
dnsRewriteRuleSOA = filtertest.RuleBlockStr + "$dnsrewrite=NOERROR;SOA;ns1." +
|
||||
filtertest.FQDNBlocked + " hostmaster." + filtertest.FQDNBlocked +
|
||||
" 1 3600 1800 604800 86400"
|
||||
|
||||
dnsRewriteTypedRules = dnsRewriteRuleTXT + "\n" + dnsRewriteRuleSOA
|
||||
)
|
||||
|
||||
txtReq := dnsservertest.NewReq(filtertest.FQDNBlocked, dns.TypeTXT, dns.ClassINET)
|
||||
soaReq := dnsservertest.NewReq(filtertest.FQDNBlocked, dns.TypeSOA, dns.ClassINET)
|
||||
|
||||
testCases := []struct {
|
||||
req *dns.Msg
|
||||
wantRes filter.Result
|
||||
name string
|
||||
}{{
|
||||
req: txtReq,
|
||||
wantRes: &filter.ResultModifiedResponse{
|
||||
Msg: dnsservertest.NewResp(dns.RcodeSuccess, txtReq, dnsservertest.SectionAnswer{
|
||||
@@ -287,34 +305,30 @@ func TestFilter_FilterRequest_dnsrewrite(t *testing.T) {
|
||||
Rule: "",
|
||||
},
|
||||
name: "dnsrewrite_txt",
|
||||
ruleLists: []*rulelist.Refreshable{},
|
||||
}, {
|
||||
custom: rlCustomTyped,
|
||||
req: soaReq,
|
||||
wantRes: &filter.ResultModifiedResponse{
|
||||
Msg: dnsservertest.NewResp(dns.RcodeSuccess, soaReq),
|
||||
List: filter.IDCustom,
|
||||
},
|
||||
name: "dnsrewrite_soa",
|
||||
ruleLists: []*rulelist.Refreshable{},
|
||||
}}
|
||||
|
||||
f := newComposite(t, &composite.Config{
|
||||
Custom: newCustom(t, dnsRewriteTypedRules),
|
||||
RuleLists: []*rulelist.Refreshable{},
|
||||
})
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := newComposite(t, &composite.Config{
|
||||
Custom: tc.custom,
|
||||
RuleLists: tc.ruleLists,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
res, fltErr := f.FilterRequest(ctx, &filter.Request{
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
res, err := f.FilterRequest(ctx, &filter.Request{
|
||||
DNS: tc.req,
|
||||
Messages: agdtest.NewConstructor(t),
|
||||
Host: filtertest.HostBlocked,
|
||||
QType: tc.req.Question[0].Qtype,
|
||||
})
|
||||
|
||||
require.NoError(t, fltErr)
|
||||
require.NoError(t, err)
|
||||
|
||||
filtertest.AssertEqualResult(t, tc.wantRes, res)
|
||||
})
|
||||
@@ -334,6 +348,8 @@ func newCustom(tb testing.TB, text string) (f *custom.Filter) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_hostsRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
reqHost4 = "www.example.com"
|
||||
reqHost6 = "www.example.net"
|
||||
@@ -420,6 +436,8 @@ func TestFilter_FilterRequest_hostsRules(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_safeSearch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const rewriteRule = filtertest.RuleSafeSearchGeneralIPv4Str + "\n"
|
||||
cachePath, srvURL := filtertest.PrepareRefreshable(t, nil, rewriteRule, http.StatusOK)
|
||||
|
||||
@@ -469,6 +487,8 @@ func TestFilter_FilterRequest_safeSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_services(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svcRL := rulelist.NewImmutable(
|
||||
[]byte(filtertest.RuleBlockStr),
|
||||
filter.IDBlockedService,
|
||||
@@ -492,23 +512,18 @@ func TestFilter_FilterRequest_services(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_FilterResponse(t *testing.T) {
|
||||
const cnameReqFQDN = "sub." + filtertest.FQDNBlocked
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
passedIPv4Str = "1.1.1.1"
|
||||
blockedIPv4Str = "1.2.3.4"
|
||||
blockedIPv6Str = "1234::cdef"
|
||||
|
||||
blockRules = filtertest.HostBlocked + "\n" +
|
||||
blockedIPv4Str + "\n" +
|
||||
blockedIPv6Str + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
passedIPv4 = netip.MustParseAddr(passedIPv4Str)
|
||||
blockedIPv4 = netip.MustParseAddr(blockedIPv4Str)
|
||||
blockedIPv6 = netip.MustParseAddr(blockedIPv6Str)
|
||||
)
|
||||
|
||||
blockingRL := newFromStr(t, blockRules, filtertest.RuleListID1)
|
||||
f := newComposite(t, &composite.Config{
|
||||
RuleLists: []*rulelist.Refreshable{blockingRL},
|
||||
@@ -517,48 +532,101 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
const ttl = agdtest.FilteredResponseTTLSec
|
||||
|
||||
testCases := []struct {
|
||||
want filter.Result
|
||||
name string
|
||||
reqFQDN string
|
||||
wantRule filter.RuleText
|
||||
respAns dnsservertest.SectionAnswer
|
||||
}{{
|
||||
want: nil,
|
||||
name: "pass",
|
||||
reqFQDN: filtertest.FQDN,
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewA(filtertest.FQDN, ttl, netip.MustParseAddr(passedIPv4Str)),
|
||||
},
|
||||
}, {
|
||||
want: &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: filtertest.HostBlocked,
|
||||
},
|
||||
name: "cname",
|
||||
reqFQDN: filtertest.FQDNCname,
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewCNAME(filtertest.FQDNCname, ttl, filtertest.FQDNBlocked),
|
||||
dnsservertest.NewA(filtertest.FQDNBlocked, ttl, netip.MustParseAddr(blockedIPv4Str)),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, req := newReqDataWithFQDN(t, tc.reqFQDN)
|
||||
res, err := f.FilterResponse(ctx, &filter.Response{
|
||||
DNS: dnsservertest.NewResp(dns.RcodeSuccess, req.DNS, tc.respAns),
|
||||
RemoteIP: filtertest.IPv4Client,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_FilterResponse_blocked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
passedIPv4Str = "1.1.1.1"
|
||||
blockedIPv4Str = "1.2.3.4"
|
||||
blockedIPv6Str = "1234::cdef"
|
||||
|
||||
blockRules = filtertest.HostBlocked + "\n" +
|
||||
blockedIPv4Str + "\n" +
|
||||
blockedIPv6Str + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
blockedIPv4 = netip.MustParseAddr(blockedIPv4Str)
|
||||
blockedIPv6 = netip.MustParseAddr(blockedIPv6Str)
|
||||
)
|
||||
|
||||
resBlocked4 := &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: blockedIPv4Str,
|
||||
}
|
||||
|
||||
resBlocked6 := &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: blockedIPv6Str,
|
||||
}
|
||||
|
||||
blockingRL := newFromStr(t, blockRules, filtertest.RuleListID1)
|
||||
f := newComposite(t, &composite.Config{
|
||||
RuleLists: []*rulelist.Refreshable{blockingRL},
|
||||
})
|
||||
|
||||
const ttl = agdtest.FilteredResponseTTLSec
|
||||
|
||||
testCases := []struct {
|
||||
want filter.Result
|
||||
name string
|
||||
respAns dnsservertest.SectionAnswer
|
||||
qType dnsmsg.RRType
|
||||
}{{
|
||||
name: "pass",
|
||||
reqFQDN: filtertest.FQDN,
|
||||
wantRule: "",
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewA(filtertest.FQDN, ttl, passedIPv4),
|
||||
},
|
||||
qType: dns.TypeA,
|
||||
}, {
|
||||
name: "cname",
|
||||
reqFQDN: cnameReqFQDN,
|
||||
wantRule: filtertest.HostBlocked,
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewCNAME(cnameReqFQDN, ttl, filtertest.FQDNBlocked),
|
||||
dnsservertest.NewA(filtertest.FQDNBlocked, ttl, netip.MustParseAddr("1.2.3.4")),
|
||||
},
|
||||
qType: dns.TypeA,
|
||||
}, {
|
||||
want: resBlocked4,
|
||||
name: "ipv4",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: blockedIPv4Str,
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewA(filtertest.FQDNBlocked, ttl, blockedIPv4),
|
||||
},
|
||||
qType: dns.TypeA,
|
||||
}, {
|
||||
want: resBlocked6,
|
||||
name: "ipv6",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: blockedIPv6Str,
|
||||
respAns: dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewAAAA(filtertest.FQDNBlocked, ttl, blockedIPv6),
|
||||
},
|
||||
qType: dns.TypeAAAA,
|
||||
}, {
|
||||
want: resBlocked4,
|
||||
name: "ipv4hint",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: blockedIPv4Str,
|
||||
respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS(
|
||||
filtertest.FQDNBlocked,
|
||||
ttl,
|
||||
@@ -567,9 +635,8 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
)},
|
||||
qType: dns.TypeHTTPS,
|
||||
}, {
|
||||
want: resBlocked6,
|
||||
name: "ipv6hint",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: blockedIPv6Str,
|
||||
respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS(
|
||||
filtertest.FQDNBlocked,
|
||||
ttl,
|
||||
@@ -578,9 +645,8 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
)},
|
||||
qType: dns.TypeHTTPS,
|
||||
}, {
|
||||
want: resBlocked4,
|
||||
name: "ipv4_ipv6_hints",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: blockedIPv4Str,
|
||||
respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS(
|
||||
filtertest.FQDNBlocked,
|
||||
ttl,
|
||||
@@ -589,13 +655,12 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
)},
|
||||
qType: dns.TypeHTTPS,
|
||||
}, {
|
||||
want: nil,
|
||||
name: "pass_hints",
|
||||
reqFQDN: filtertest.FQDNBlocked,
|
||||
wantRule: "",
|
||||
respAns: dnsservertest.SectionAnswer{dnsservertest.NewHTTPS(
|
||||
filtertest.FQDNBlocked,
|
||||
ttl,
|
||||
[]netip.Addr{passedIPv4},
|
||||
[]netip.Addr{netip.MustParseAddr(passedIPv4Str)},
|
||||
[]netip.Addr{},
|
||||
)},
|
||||
qType: dns.TypeHTTPS,
|
||||
@@ -603,7 +668,7 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx, req := newReqDataWithFQDN(t, tc.reqFQDN)
|
||||
ctx, req := newReqDataWithFQDN(t, filtertest.FQDNBlocked)
|
||||
req.DNS.Question[0].Qtype = tc.qType
|
||||
|
||||
res, err := f.FilterResponse(ctx, &filter.Response{
|
||||
@@ -612,17 +677,7 @@ func TestFilter_FilterResponse(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.wantRule == "" {
|
||||
assert.Nil(t, res)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
want := &filter.ResultBlocked{
|
||||
List: filtertest.RuleListID1,
|
||||
Rule: tc.wantRule,
|
||||
}
|
||||
assert.Equal(t, want, res)
|
||||
assert.Equal(t, tc.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
350
internal/filter/internal/domain/filter.go
Normal file
350
internal/filter/internal/domain/filter.go
Normal file
@@ -0,0 +1,350 @@
|
||||
// Package domain implements a domain filter based on domain table.
|
||||
package domain
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/refreshable"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
"github.com/AdguardTeam/golibs/syncutil"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// FilterConfig is the domain filter configuration structure.
|
||||
type FilterConfig struct {
|
||||
// Logger is used for logging the operation of the filter.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Cloner is used to clone messages taken from filtering-result cache.
|
||||
Cloner *dnsmsg.Cloner
|
||||
|
||||
// CacheManager is the global cache manager. CacheManager must not be nil.
|
||||
CacheManager agdcache.Manager
|
||||
|
||||
// URL is the URL used to update the filter.
|
||||
URL *url.URL
|
||||
|
||||
// ErrColl is used to collect non-critical and rare errors.
|
||||
ErrColl errcoll.Interface
|
||||
|
||||
// DomainMetrics are the specific metrics for the domain filter.
|
||||
DomainMetrics Metrics
|
||||
|
||||
// Metrics are the metrics for the domain filter.
|
||||
Metrics filter.Metrics
|
||||
|
||||
// PublicSuffixList is used for obtaining public suffix for specified
|
||||
// domain.
|
||||
PublicSuffixList cookiejar.PublicSuffixList
|
||||
|
||||
// ID is the ID of this storage for logging and error reporting.
|
||||
ID filter.ID
|
||||
|
||||
// CachePath is the path to the file containing the cached filtered
|
||||
// hostnames, one per line.
|
||||
CachePath string
|
||||
|
||||
// Staleness is the time after which a file is considered stale.
|
||||
Staleness time.Duration
|
||||
|
||||
// CacheTTL is the time-to-live value used to cache the results of the
|
||||
// filter.
|
||||
//
|
||||
// TODO(a.garipov): Currently unused. See AGDNS-398.
|
||||
CacheTTL time.Duration
|
||||
|
||||
// RefreshTimeout is the timeout for the filter update operation.
|
||||
RefreshTimeout time.Duration
|
||||
|
||||
// CacheCount is the count of the elements in the filter's result cache.
|
||||
CacheCount int
|
||||
|
||||
// MaxSize is the maximum size of the downloadable rule-list.
|
||||
MaxSize datasize.ByteSize
|
||||
|
||||
// SubDomainNum defines how many subdomains will be checked for one domain.
|
||||
// It must be positive and fit into int.
|
||||
SubDomainNum uint
|
||||
}
|
||||
|
||||
// Filter is a domain table based filter.
|
||||
//
|
||||
// TODO(f.setrakov): Consider DRYing it with the hasprefix filter.
|
||||
type Filter struct {
|
||||
logger *slog.Logger
|
||||
cloner *dnsmsg.Cloner
|
||||
domains *atomic.Pointer[container.MapSet[string]]
|
||||
refr *refreshable.Refreshable
|
||||
subDomainsPool *syncutil.Pool[[]string]
|
||||
errColl errcoll.Interface
|
||||
domainMtrc Metrics
|
||||
publicSuffixList cookiejar.PublicSuffixList
|
||||
metrics filter.Metrics
|
||||
resCache agdcache.Interface[rulelist.CacheKey, filter.Result]
|
||||
id filter.ID
|
||||
subDomainNum int
|
||||
}
|
||||
|
||||
// IDPrefix is a common prefix for cache IDs, logging, and refreshes of
|
||||
// domain filters.
|
||||
//
|
||||
// TODO(a.garipov): Consider better names.
|
||||
const IDPrefix = "filters/domain"
|
||||
|
||||
// NewFilter returns a new domain filter. It also adds the caches with IDs
|
||||
// [FilterListIDAdultBlocking], [FilterListIDSafeBrowsing], and
|
||||
// [FilterListIDNewRegDomains] to the cache manager. c must not be nil.
|
||||
func NewFilter(c *FilterConfig) (f *Filter, err error) {
|
||||
id := c.ID
|
||||
|
||||
resCache := agdcache.NewLRU[rulelist.CacheKey, filter.Result](&agdcache.LRUConfig{
|
||||
Count: c.CacheCount,
|
||||
})
|
||||
|
||||
c.CacheManager.Add(path.Join(IDPrefix, string(id)), resCache)
|
||||
|
||||
f = &Filter{
|
||||
logger: c.Logger,
|
||||
cloner: c.Cloner,
|
||||
domains: &atomic.Pointer[container.MapSet[string]]{},
|
||||
errColl: c.ErrColl,
|
||||
// #nosec G115 -- Assume that c.SubDomainNum is always less then or
|
||||
// equal to 63.
|
||||
//
|
||||
// TODO(f.setrakov): Validate c.SubDomainsNum.
|
||||
subDomainsPool: syncutil.NewSlicePool[string](int(c.SubDomainNum)),
|
||||
domainMtrc: c.DomainMetrics,
|
||||
metrics: c.Metrics,
|
||||
publicSuffixList: c.PublicSuffixList,
|
||||
resCache: resCache,
|
||||
id: id,
|
||||
// #nosec G115 -- The value is a constant less than int accommodates.
|
||||
subDomainNum: int(c.SubDomainNum),
|
||||
}
|
||||
|
||||
f.refr, err = refreshable.New(&refreshable.Config{
|
||||
Logger: f.logger,
|
||||
URL: c.URL,
|
||||
ID: id,
|
||||
CachePath: c.CachePath,
|
||||
Staleness: c.Staleness,
|
||||
Timeout: c.RefreshTimeout,
|
||||
MaxSize: c.MaxSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating refreshable: %w", err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// FilterRequest implements the [composite.RequestFilter] interface for *Filter.
|
||||
// It modifies the request or response if host matches f.
|
||||
func (f *Filter) FilterRequest(
|
||||
ctx context.Context,
|
||||
req *filter.Request,
|
||||
) (r filter.Result, err error) {
|
||||
host, qt, cl := req.Host, req.QType, req.QClass
|
||||
|
||||
cacheKey := rulelist.NewCacheKey(host, qt, cl, false)
|
||||
item, ok := f.resCache.Get(cacheKey)
|
||||
f.domainMtrc.IncrementLookups(ctx, ok)
|
||||
if ok {
|
||||
return f.clonedResult(req.DNS, item), nil
|
||||
}
|
||||
|
||||
if !isFilterable(qt) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var matched string
|
||||
subPtr := f.subDomainsPool.Get()
|
||||
defer f.subDomainsPool.Put(subPtr)
|
||||
|
||||
*subPtr = agdnet.AppendSubdomains((*subPtr)[:0], host, f.subDomainNum, f.publicSuffixList)
|
||||
|
||||
domains := *f.domains.Load()
|
||||
for _, s := range *subPtr {
|
||||
if domains.Has(s) {
|
||||
matched = s
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched == "" {
|
||||
f.resCache.Set(cacheKey, nil)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r, err = f.filteredResult(req, matched)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.setInCache(cacheKey, r)
|
||||
|
||||
f.domainMtrc.UpdateCacheSize(ctx, f.resCache.Len())
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// isFilterable returns true if the question type is filterable.
|
||||
func isFilterable(qt dnsmsg.RRType) (ok bool) {
|
||||
fam := netutil.AddrFamilyFromRRType(qt)
|
||||
|
||||
return qt == dns.TypeHTTPS || fam != netutil.AddrFamilyNone
|
||||
}
|
||||
|
||||
// clonedResult returns a clone of the result based on its type. r must be nil,
|
||||
// [*filter.ResultModifiedRequest], or [*filter.ResultModifiedResponse].
|
||||
func (f *Filter) clonedResult(req *dns.Msg, r filter.Result) (clone filter.Result) {
|
||||
switch r := r.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *filter.ResultModifiedRequest:
|
||||
return r.Clone(f.cloner)
|
||||
case *filter.ResultModifiedResponse:
|
||||
return r.CloneForReq(f.cloner, req)
|
||||
default:
|
||||
panic(fmt.Errorf("domain: unexpected type for result: %T(%[1]v)", r))
|
||||
}
|
||||
}
|
||||
|
||||
// filteredResult returns a filtered request or response.
|
||||
func (f *Filter) filteredResult(
|
||||
req *filter.Request,
|
||||
matched string,
|
||||
) (r filter.Result, err error) {
|
||||
resp, err := req.Messages.NewBlockedResp(req.DNS, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filter %s: creating modified result: %w", f.id, err)
|
||||
}
|
||||
|
||||
return &filter.ResultModifiedResponse{
|
||||
Msg: resp,
|
||||
List: f.id,
|
||||
Rule: filter.RuleText(matched),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setInCache sets r in cache. It clones the result to make sure that
|
||||
// modifications to the result message down the pipeline don't interfere with
|
||||
// the cached value. r must be either [*filter.ResultModifiedRequest] or
|
||||
// [*filter.ResultModifiedResponse].
|
||||
//
|
||||
// See AGDNS-359.
|
||||
func (f *Filter) setInCache(k rulelist.CacheKey, r filter.Result) {
|
||||
switch r := r.(type) {
|
||||
case *filter.ResultModifiedRequest:
|
||||
f.resCache.Set(k, r.Clone(f.cloner))
|
||||
case *filter.ResultModifiedResponse:
|
||||
f.resCache.Set(k, r.Clone(f.cloner))
|
||||
default:
|
||||
panic(fmt.Errorf("domain: unexpected type for result: %T(%[1]v)", r))
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ service.Refresher = (*Filter)(nil)
|
||||
|
||||
// Refresh implements the [service.Refresher] interface for *Filter.
|
||||
func (f *Filter) Refresh(ctx context.Context) (err error) {
|
||||
f.logger.InfoContext(ctx, "refresh started")
|
||||
defer f.logger.InfoContext(ctx, "refresh finished")
|
||||
|
||||
err = f.refresh(ctx, false)
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, f.errColl, f.logger, fmt.Sprintf("refreshing %q", f.id), err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RefreshInitial loads the content of the filter, using cached files if any,
|
||||
// regardless of their staleness.
|
||||
func (f *Filter) RefreshInitial(ctx context.Context) (err error) {
|
||||
f.logger.InfoContext(ctx, "initial refresh started")
|
||||
defer f.logger.InfoContext(ctx, "initial refresh finished")
|
||||
|
||||
err = f.refresh(ctx, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refreshing domain filter initially: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// refresh reloads and resets domain data. If acceptStale is true, do not try
|
||||
// to load the list from its URL when there is already a file in the cache
|
||||
// directory, regardless of its staleness.
|
||||
func (f *Filter) refresh(ctx context.Context, acceptStale bool) (err error) {
|
||||
var count int
|
||||
defer func() {
|
||||
// TODO(a.garipov): Consider using [agdtime.Clock].
|
||||
f.metrics.SetFilterStatus(ctx, string(f.id), time.Now(), count, err)
|
||||
}()
|
||||
|
||||
b, err := f.refr.Refresh(ctx, acceptStale)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
count, err = f.resetDomains(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: resetting: %w", f.id, err)
|
||||
}
|
||||
|
||||
f.resCache.Clear()
|
||||
|
||||
f.logger.InfoContext(ctx, "reset hosts", "num", count)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetDomains populates storage with domains from domainData.
|
||||
func (f *Filter) resetDomains(domainData []byte) (n int, err error) {
|
||||
next := container.NewMapSet[string]()
|
||||
|
||||
sc := bufio.NewScanner(bytes.NewReader(domainData))
|
||||
for sc.Scan() {
|
||||
domain := sc.Text()
|
||||
if len(domain) == 0 || domain[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
next.Add(strings.ToLower(domain))
|
||||
n++
|
||||
}
|
||||
|
||||
err = sc.Err()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("scanning domains: %w", err)
|
||||
}
|
||||
|
||||
f.domains.Store(next)
|
||||
|
||||
return n, nil
|
||||
}
|
||||
306
internal/filter/internal/domain/filter_test.go
Normal file
306
internal/filter/internal/domain/filter_test.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/domain"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// type check
|
||||
//
|
||||
// TODO(f.setrakov): Use export via internal test to avoid loop.
|
||||
var _ composite.RequestFilter = (*domain.Filter)(nil)
|
||||
|
||||
// testDomains is the host data for tests.
|
||||
const testDomains = filtertest.HostCategory + "\n"
|
||||
|
||||
func TestFilter_FilterRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msgs := agdtest.NewConstructor(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
qType dnsmsg.RRType
|
||||
wantResult bool
|
||||
}{{
|
||||
name: "host_not_a_or_aaaa",
|
||||
host: filtertest.HostCategory,
|
||||
qType: dns.TypeTXT,
|
||||
wantResult: false,
|
||||
}, {
|
||||
name: "host_success",
|
||||
host: filtertest.HostCategory,
|
||||
qType: dns.TypeA,
|
||||
wantResult: true,
|
||||
}, {
|
||||
name: "host_success_subdomain",
|
||||
host: filtertest.HostCategorySub,
|
||||
qType: dns.TypeA,
|
||||
wantResult: true,
|
||||
}, {
|
||||
name: "host_no_match",
|
||||
host: filtertest.Host,
|
||||
qType: dns.TypeA,
|
||||
wantResult: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := filtertest.NewDomainFilter(t, testDomains)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
req := dnsservertest.NewReq(dns.Fqdn(tc.host), tc.qType, dns.ClassINET)
|
||||
|
||||
r, err := f.FilterRequest(ctx, &filter.Request{
|
||||
DNS: req,
|
||||
Messages: msgs,
|
||||
Host: tc.host,
|
||||
QType: tc.qType,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var wantRes filter.Result
|
||||
if tc.wantResult {
|
||||
wantRes = newModRespResult(
|
||||
t,
|
||||
req,
|
||||
msgs,
|
||||
netip.IPv4Unspecified(),
|
||||
filtertest.HostCategory,
|
||||
)
|
||||
}
|
||||
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_cache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := filtertest.NewDomainFilter(t, testDomains)
|
||||
|
||||
require.True(t, t.Run("cached_success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewARequest(t, filtertest.HostCategory)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
original, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
cached, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
filtertest.AssertEqualResult(t, cached, original)
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("cached_no_match", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewARequest(t, filtertest.Host)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
original, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
cached, err := f.FilterRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
filtertest.AssertEqualResult(t, cached, original)
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFilter_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
refrCh := make(chan struct{}, 1)
|
||||
cachePath, srvURL := filtertest.PrepareRefreshable(t, refrCh, testDomains, http.StatusOK)
|
||||
|
||||
f, err := domain.NewFilter(&domain.FilterConfig{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Cloner: agdtest.NewCloner(),
|
||||
CacheManager: agdcache.EmptyManager{},
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
DomainMetrics: domain.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: filtertest.RuleListIDDomain,
|
||||
CachePath: cachePath,
|
||||
Staleness: filtertest.Staleness,
|
||||
CacheTTL: filtertest.CacheTTL,
|
||||
CacheCount: filtertest.CacheCount,
|
||||
MaxSize: filtertest.FilterMaxSize,
|
||||
SubDomainNum: filtertest.SubDomainNum,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
require.NoError(t, f.RefreshInitial(ctx))
|
||||
|
||||
testutil.RequireReceive(t, refrCh, filtertest.Timeout)
|
||||
|
||||
ctx = testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
err = f.Refresh(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Empty(t, refrCh)
|
||||
}
|
||||
|
||||
func TestFilter_FilterRequest_staleCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
refrCh := make(chan struct{}, 1)
|
||||
cachePath, srvURL := filtertest.PrepareRefreshable(t, refrCh, testDomains, http.StatusOK)
|
||||
|
||||
msgs := agdtest.NewConstructor(t)
|
||||
|
||||
// Put some initial data into the cache to avoid the first refresh.
|
||||
|
||||
cf, err := os.OpenFile(cachePath, os.O_WRONLY|os.O_APPEND, os.ModeAppend)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = cf.WriteString(filtertest.Host + "\n")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cf.Close())
|
||||
|
||||
// Create the filter.
|
||||
|
||||
cloner := agdtest.NewCloner()
|
||||
|
||||
fconf := &domain.FilterConfig{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Cloner: cloner,
|
||||
CacheManager: agdcache.EmptyManager{},
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
DomainMetrics: domain.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: filtertest.RuleListIDDomain,
|
||||
CachePath: cachePath,
|
||||
Staleness: filtertest.Staleness,
|
||||
CacheTTL: filtertest.CacheTTL,
|
||||
CacheCount: filtertest.CacheCount,
|
||||
MaxSize: filtertest.FilterMaxSize,
|
||||
SubDomainNum: filtertest.SubDomainNum,
|
||||
}
|
||||
f, err := domain.NewFilter(fconf)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
require.NoError(t, f.RefreshInitial(ctx))
|
||||
|
||||
assert.Empty(t, refrCh)
|
||||
|
||||
// Test the following:
|
||||
//
|
||||
// 1. Check that the stale rules cache is used.
|
||||
// 2. Refresh the stale rules cache.
|
||||
// 3. Ensure the result cache is cleared.
|
||||
// 4. Ensure the stale rules aren't used.
|
||||
|
||||
hostReq := filtertest.NewARequest(t, filtertest.HostCategory)
|
||||
otherHostReq := filtertest.NewARequest(t, filtertest.Host)
|
||||
|
||||
require.True(t, t.Run("hit_cached_host", func(t *testing.T) {
|
||||
ctx = testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
|
||||
var r filter.Result
|
||||
r, err = f.FilterRequest(ctx, otherHostReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantRes := newModRespResult(
|
||||
t,
|
||||
otherHostReq.DNS,
|
||||
msgs,
|
||||
netip.IPv4Unspecified(),
|
||||
filtertest.Host,
|
||||
)
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("refresh", func(t *testing.T) {
|
||||
// Make the cache stale.
|
||||
now := time.Now()
|
||||
err = os.Chtimes(cachePath, now, now.Add(-2*fconf.Staleness))
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx = testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
|
||||
err = f.Refresh(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testutil.RequireReceive(t, refrCh, filtertest.Timeout)
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("previously_cached", func(t *testing.T) {
|
||||
ctx = testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
|
||||
var r filter.Result
|
||||
r, err = f.FilterRequest(ctx, otherHostReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, r)
|
||||
}))
|
||||
|
||||
require.True(t, t.Run("new_host", func(t *testing.T) {
|
||||
ctx = testutil.ContextWithTimeout(t, filtertest.Timeout)
|
||||
|
||||
var r filter.Result
|
||||
r, err = f.FilterRequest(ctx, hostReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantRes := newModRespResult(
|
||||
t,
|
||||
hostReq.DNS,
|
||||
msgs,
|
||||
netip.IPv4Unspecified(),
|
||||
filtertest.HostCategory,
|
||||
)
|
||||
filtertest.AssertEqualResult(t, wantRes, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// newModRespResult is a helper for creating modified response result for tests.
|
||||
// req must not be nil.
|
||||
func newModRespResult(
|
||||
tb testing.TB,
|
||||
req *dns.Msg,
|
||||
messages *dnsmsg.Constructor,
|
||||
replIP netip.Addr,
|
||||
rule string,
|
||||
) (r *filter.ResultModifiedResponse) {
|
||||
tb.Helper()
|
||||
|
||||
resp, err := messages.NewRespIP(req, replIP)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return &filter.ResultModifiedResponse{
|
||||
Msg: resp,
|
||||
List: filtertest.RuleListIDDomain,
|
||||
Rule: filter.RuleText(rule),
|
||||
}
|
||||
}
|
||||
28
internal/filter/internal/domain/metrics.go
Normal file
28
internal/filter/internal/domain/metrics.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Metrics is an interface used for collection if the domain filter
|
||||
// statistics.
|
||||
type Metrics interface {
|
||||
// IncrementLookups increments the number of lookups. hit is true if the
|
||||
// lookup returned a value.
|
||||
IncrementLookups(ctx context.Context, hit bool)
|
||||
|
||||
// UpdateCacheSize is called when the cache size is updated.
|
||||
UpdateCacheSize(ctx context.Context, cacheLen int)
|
||||
}
|
||||
|
||||
// EmptyMetrics is the implementation of the [Metrics] interface that does nothing.
|
||||
type EmptyMetrics struct{}
|
||||
|
||||
// type check
|
||||
var _ Metrics = EmptyMetrics{}
|
||||
|
||||
// IncrementLookups implements the [Metrics] interface for EmptyMetrics.
|
||||
func (EmptyMetrics) IncrementLookups(_ context.Context, _ bool) {}
|
||||
|
||||
// UpdateCacheSize implements the [Metrics] interface for EmptyMetrics.
|
||||
func (EmptyMetrics) UpdateCacheSize(_ context.Context, _ int) {}
|
||||
46
internal/filter/internal/filtertest/domain.go
Normal file
46
internal/filter/internal/filtertest/domain.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package filtertest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/domain"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// NewDomainFilter is a helper constructor of domain filters for tests.
|
||||
func NewDomainFilter(tb testing.TB, data string) (f *domain.Filter) {
|
||||
tb.Helper()
|
||||
|
||||
cachePath, srvURL := PrepareRefreshable(tb, nil, data, http.StatusOK)
|
||||
f, err := domain.NewFilter(&domain.FilterConfig{
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
Cloner: agdtest.NewCloner(),
|
||||
CacheManager: agdcache.EmptyManager{},
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
DomainMetrics: domain.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: RuleListIDDomain,
|
||||
CachePath: cachePath,
|
||||
Staleness: Staleness,
|
||||
CacheTTL: CacheTTL,
|
||||
CacheCount: CacheCount,
|
||||
MaxSize: FilterMaxSize,
|
||||
SubDomainNum: SubDomainNum,
|
||||
})
|
||||
|
||||
require.NoError(tb, err)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(tb, Timeout)
|
||||
require.NoError(tb, f.RefreshInitial(ctx))
|
||||
|
||||
return f
|
||||
}
|
||||
@@ -57,6 +57,7 @@ const (
|
||||
HostBlockedForClientIP = "blocked-for-client-ip.example"
|
||||
HostBlockedForClientName = "blocked-for-client-name.example"
|
||||
HostBlockedService1 = "service-1.example"
|
||||
HostCNAME = "new-cname.example"
|
||||
HostDangerous = "dangerous-domain.example"
|
||||
HostDangerousRepl = "dangerous-domain-repl.example"
|
||||
HostNewlyRegistered = "newly-registered.example"
|
||||
@@ -67,12 +68,15 @@ const (
|
||||
HostSafeSearchGeneralRepl = "safe.search.example"
|
||||
HostSafeSearchYouTube = "video.example"
|
||||
HostSafeSearchYouTubeRepl = "safe.video.example"
|
||||
HostCategory = "blocked.category.example"
|
||||
HostCategorySub = "a.b.c." + HostCategory
|
||||
|
||||
FQDN = Host + "."
|
||||
FQDNAdultContent = HostAdultContent + "."
|
||||
FQDNAdultContentRepl = HostAdultContentRepl + "."
|
||||
FQDNBlocked = HostBlocked + "."
|
||||
FQDNBlockedForClientName = HostBlockedForClientName + "."
|
||||
FQDNCname = HostCNAME + "."
|
||||
FQDNDangerous = HostDangerous + "."
|
||||
FQDNDangerousRepl = HostDangerousRepl + "."
|
||||
FQDNNewlyRegistered = HostNewlyRegistered + "."
|
||||
@@ -82,6 +86,7 @@ const (
|
||||
FQDNSafeSearchGeneralIPv6 = HostSafeSearchGeneralIPv6 + "."
|
||||
FQDNSafeSearchYouTube = HostSafeSearchYouTube + "."
|
||||
FQDNSafeSearchYouTubeRepl = HostSafeSearchYouTubeRepl + "."
|
||||
FQDNCategory = HostCategory + "."
|
||||
)
|
||||
|
||||
// Common blocked-service IDs for tests.
|
||||
@@ -122,9 +127,11 @@ const BlockedServiceIndex string = `{
|
||||
const (
|
||||
RuleListID1Str = "rule_list_1"
|
||||
RuleListID2Str = "rule_list_2"
|
||||
RuleListIDDomainStr = "blocked-category"
|
||||
|
||||
RuleListID1 filter.ID = RuleListID1Str
|
||||
RuleListID2 filter.ID = RuleListID2Str
|
||||
RuleListIDDomain filter.ID = RuleListIDDomainStr
|
||||
)
|
||||
|
||||
// NewRuleListIndex returns a rule-list index containing a record for a filter
|
||||
|
||||
@@ -13,8 +13,12 @@ import (
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// SubDomainNum is a common subDomainNum value for tests.
|
||||
const SubDomainNum = 4
|
||||
|
||||
// NewHashprefixFilter is like [NewHashprefixFilterWithRepl], but the
|
||||
// replacement host is also set in accordance with id.
|
||||
func NewHashprefixFilter(tb testing.TB, id filter.ID) (f *hashprefix.Filter) {
|
||||
@@ -68,8 +72,9 @@ func NewHashprefixFilterWithRepl(
|
||||
Hashes: strg,
|
||||
URL: srvURL,
|
||||
ErrColl: agdtest.NewErrorCollector(),
|
||||
HashPrefixMtcs: hashprefix.EmptyMetrics{},
|
||||
HashPrefixMetrics: hashprefix.EmptyMetrics{},
|
||||
Metrics: filter.EmptyMetrics{},
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
ID: id,
|
||||
CachePath: cachePath,
|
||||
ReplacementHost: replHost,
|
||||
@@ -77,6 +82,7 @@ func NewHashprefixFilterWithRepl(
|
||||
CacheTTL: CacheTTL,
|
||||
CacheCount: CacheCount,
|
||||
MaxSize: FilterMaxSize,
|
||||
SubDomainNum: SubDomainNum,
|
||||
})
|
||||
require.NoError(tb, err)
|
||||
|
||||
|
||||
279
internal/filter/internal/rulelist/dnsrewrite_test.go
Normal file
279
internal/filter/internal/rulelist/dnsrewrite_test.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package rulelist_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/filtertest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testDNSRequestID is the common message ID for all DNS messages.
|
||||
const testDNSRequestID = 1
|
||||
|
||||
// testListID is common list id for all network rules.
|
||||
const testListID = rules.ListID(testDNSRequestID)
|
||||
|
||||
// Common rewrite values for tests.
|
||||
const (
|
||||
exchange = "mail.com"
|
||||
exchangeFQDN = exchange + "."
|
||||
|
||||
targetHost = "target.com"
|
||||
targetFQDN = targetHost + "."
|
||||
)
|
||||
|
||||
// newDNSRequest is a helper that returns a DNS request with the given
|
||||
// data and overrides its ID with testDNSRequestID.
|
||||
func newDNSRequest(tb testing.TB, host string, qt dnsmsg.RRType) (req *dns.Msg) {
|
||||
tb.Helper()
|
||||
|
||||
req = dnsservertest.NewReq(host, qt, dns.ClassINET)
|
||||
req.Id = testDNSRequestID
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// newRule is a helper, that returns a NetworkRule generated from string, and
|
||||
// sets its list ID to a common value.
|
||||
func newRule(tb testing.TB, rule string) (r *rules.NetworkRule) {
|
||||
tb.Helper()
|
||||
|
||||
r, err := rules.NewNetworkRule(rule, testListID)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// newResultModifiedResponse is a helper that returns a ResultModifiedResponse
|
||||
// with the default list ID and a dns.Msg containing the given data. request
|
||||
// must not be nil.
|
||||
func newResultModifiedResponse(
|
||||
tb testing.TB,
|
||||
rcode rules.RCode,
|
||||
request *dns.Msg,
|
||||
ans ...dnsservertest.RRSection,
|
||||
) (resp *filter.ResultModifiedResponse) {
|
||||
tb.Helper()
|
||||
|
||||
return &filter.ResultModifiedResponse{
|
||||
List: filtertest.RuleListID1,
|
||||
Msg: dnsservertest.NewResp(rcode, request, ans...),
|
||||
}
|
||||
}
|
||||
|
||||
func TestProccessDNSRewrites_RRTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
reqA = newDNSRequest(t, filtertest.HostSafeSearchGeneralIPv4, dns.TypeA)
|
||||
reqAAAA = newDNSRequest(t, filtertest.HostSafeSearchGeneralIPv6, dns.TypeAAAA)
|
||||
reqTXT = newDNSRequest(t, filtertest.Host, dns.TypeTXT)
|
||||
reqSRV = newDNSRequest(t, filtertest.Host, dns.TypeSRV)
|
||||
reqSVCB = newDNSRequest(t, filtertest.Host, dns.TypeSVCB)
|
||||
reqMX = newDNSRequest(t, filtertest.Host, dns.TypeMX)
|
||||
|
||||
ansA = dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewA(
|
||||
filtertest.FQDNSafeSearchGeneralIPv4,
|
||||
10,
|
||||
filtertest.IPv4SafeSearchRepl,
|
||||
),
|
||||
}
|
||||
ansAAAA = dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewAAAA(
|
||||
filtertest.FQDNSafeSearchGeneralIPv6,
|
||||
10,
|
||||
filtertest.IPv6SafeSearchRepl,
|
||||
),
|
||||
}
|
||||
|
||||
srv = dnsservertest.NewSRV(filtertest.Host, 10, targetFQDN, 1, 1, 29)
|
||||
svcb = dnsservertest.NewSVCB(filtertest.Host, 10, targetFQDN, 1)
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
result filter.Result
|
||||
rule *rules.NetworkRule
|
||||
name string
|
||||
host string
|
||||
qtype dnsmsg.RRType
|
||||
}{{
|
||||
name: "type_a",
|
||||
host: filtertest.HostSafeSearchGeneralIPv4,
|
||||
qtype: dns.TypeA,
|
||||
rule: newRule(t, filtertest.RuleSafeSearchGeneralIPv4Str),
|
||||
result: newResultModifiedResponse(t, dns.RcodeSuccess, reqA, ansA),
|
||||
}, {
|
||||
name: "type_aaaa",
|
||||
host: filtertest.HostSafeSearchGeneralIPv6,
|
||||
qtype: dns.TypeAAAA,
|
||||
rule: newRule(t, filtertest.RuleSafeSearchGeneralIPv6Str),
|
||||
result: newResultModifiedResponse(t, dns.RcodeSuccess, reqAAAA, ansAAAA),
|
||||
}, {
|
||||
name: "type_txt",
|
||||
host: filtertest.Host,
|
||||
qtype: dns.TypeTXT,
|
||||
rule: newRule(t, "||"+filtertest.Host+"^$dnsrewrite=NOERROR;TXT;rr_value"),
|
||||
result: newResultModifiedResponse(
|
||||
t,
|
||||
dns.RcodeSuccess,
|
||||
reqTXT,
|
||||
dnsservertest.SectionAnswer{dnsservertest.NewTXT(filtertest.FQDN, 10, "rr_value")},
|
||||
),
|
||||
}, {
|
||||
name: "type_mx",
|
||||
host: filtertest.Host,
|
||||
qtype: dns.TypeMX,
|
||||
rule: newRule(t, "||"+filtertest.Host+"^$dnsrewrite=NOERROR;MX;1 "+exchange),
|
||||
result: newResultModifiedResponse(
|
||||
t,
|
||||
dns.RcodeSuccess,
|
||||
reqMX,
|
||||
dnsservertest.SectionAnswer{
|
||||
dnsservertest.NewMX(filtertest.FQDN, 10, 1, exchangeFQDN),
|
||||
},
|
||||
),
|
||||
}, {
|
||||
name: "type_srv",
|
||||
host: filtertest.Host,
|
||||
qtype: dns.TypeSRV,
|
||||
rule: newRule(t, "||"+filtertest.Host+" ^$dnsrewrite=NOERROR;SRV;1 1 29 "+targetHost),
|
||||
result: newResultModifiedResponse(
|
||||
t,
|
||||
dns.RcodeSuccess,
|
||||
reqSRV,
|
||||
dnsservertest.SectionAnswer{srv},
|
||||
),
|
||||
}, {
|
||||
name: "type_svcb",
|
||||
host: filtertest.Host,
|
||||
qtype: dns.TypeSVCB,
|
||||
rule: newRule(t, "||"+filtertest.Host+"^$dnsrewrite=NOERROR;SVCB;1 "+targetHost),
|
||||
result: newResultModifiedResponse(
|
||||
t,
|
||||
dns.RcodeSuccess,
|
||||
reqSVCB,
|
||||
dnsservertest.SectionAnswer{svcb},
|
||||
),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewRequest(t, "", tc.host, filtertest.IPv4Client, tc.qtype)
|
||||
req.DNS.Id = testDNSRequestID
|
||||
dnsr := []*rules.NetworkRule{tc.rule}
|
||||
res := rulelist.ProcessDNSRewrites(req, dnsr, filtertest.RuleListID1)
|
||||
assert.Equal(t, tc.result, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProccessDNSRewrites_Other(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
cnameRule = "||" + filtertest.HostDangerous + "^$dnsrewrite=" + filtertest.Host
|
||||
rcodeRule = "||" + filtertest.Host + "^$dnsrewrite=REFUSED;;"
|
||||
)
|
||||
|
||||
reqA := newDNSRequest(t, filtertest.Host, dns.TypeA)
|
||||
testCases := []struct {
|
||||
result filter.Result
|
||||
rule *rules.NetworkRule
|
||||
name string
|
||||
host string
|
||||
}{{
|
||||
name: "cname",
|
||||
host: filtertest.HostDangerous,
|
||||
rule: newRule(t, cnameRule),
|
||||
result: &filter.ResultModifiedRequest{
|
||||
List: filtertest.RuleListID1,
|
||||
Msg: newDNSRequest(t, filtertest.FQDN, dns.TypeA),
|
||||
Rule: filter.RuleText(cnameRule),
|
||||
},
|
||||
}, {
|
||||
name: "empty_rules",
|
||||
host: filtertest.Host,
|
||||
rule: nil,
|
||||
result: nil,
|
||||
}, {
|
||||
name: "equal_cname",
|
||||
host: filtertest.Host,
|
||||
rule: newRule(t, "||"+filtertest.Host+"^$dnsrewrite="+filtertest.Host),
|
||||
result: nil,
|
||||
}, {
|
||||
name: "rcode",
|
||||
host: filtertest.Host,
|
||||
rule: newRule(t, rcodeRule),
|
||||
result: &filter.ResultModifiedResponse{
|
||||
List: filtertest.RuleListID1,
|
||||
Msg: dnsservertest.NewResp(dns.RcodeRefused, reqA),
|
||||
Rule: filter.RuleText(rcodeRule),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := filtertest.NewRequest(t, "", tc.host, filtertest.IPv4Client, dns.TypeA)
|
||||
req.DNS.Id = testDNSRequestID
|
||||
var dnsr []*rules.NetworkRule
|
||||
if tc.rule != nil {
|
||||
dnsr = []*rules.NetworkRule{tc.rule}
|
||||
}
|
||||
|
||||
res := rulelist.ProcessDNSRewrites(req, dnsr, filtertest.RuleListID1)
|
||||
assert.Equal(t, tc.result, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkProcessDNSRewrite(b *testing.B) {
|
||||
benchCases := []struct {
|
||||
rule *rules.NetworkRule
|
||||
name string
|
||||
}{{
|
||||
name: "cname",
|
||||
rule: newRule(b, "||"+filtertest.Host+"^$dnsrewrite="+filtertest.HostDangerous),
|
||||
}, {
|
||||
name: "rcode",
|
||||
rule: newRule(b, "||"+filtertest.Host+"^$dnsrewrite=REFUSED;;"),
|
||||
}, {
|
||||
name: "type_a",
|
||||
rule: newRule(b, "||"+filtertest.Host+"^$dnsrewrite="+filtertest.IPv4ClientStr),
|
||||
}}
|
||||
|
||||
req := filtertest.NewRequest(b, "", filtertest.Host, filtertest.IPv4Client, dns.TypeA)
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
var result filter.Result
|
||||
dnsr := []*rules.NetworkRule{bc.rule}
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
result = rulelist.ProcessDNSRewrites(req, dnsr, filtertest.RuleListID1)
|
||||
}
|
||||
|
||||
assert.NotNil(b, result)
|
||||
})
|
||||
}
|
||||
|
||||
// Most recent results:
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist
|
||||
// cpu: Apple M3
|
||||
// BenchmarkProcessDNSRewrite/cname-8 9117697 111.7 ns/op 280 B/op 5 allocs/op
|
||||
// BenchmarkProcessDNSRewrite/rcode-8 16215256 75.06 ns/op 264 B/op 4 allocs/op
|
||||
// BenchmarkProcessDNSRewrite/type_a-8 5742392 214.6 ns/op 656 B/op 9 allocs/op
|
||||
}
|
||||
@@ -124,64 +124,83 @@ func TestRefreshable_Refresh(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkRefreshable_SetURLFilterResult(b *testing.B) {
|
||||
ctx := b.Context()
|
||||
|
||||
benchCases := []struct {
|
||||
request *urlfilter.DNSRequest
|
||||
cache rulelist.ResultCache
|
||||
want require.BoolAssertionFunc
|
||||
name string
|
||||
}{{
|
||||
name: "blocked",
|
||||
cache: rulelist.EmptyResultCache{},
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.HostBlocked,
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
want: require.True,
|
||||
}, {
|
||||
name: "other",
|
||||
cache: rulelist.EmptyResultCache{},
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.Host,
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
want: require.False,
|
||||
}, {
|
||||
name: "blocked_with_cache",
|
||||
cache: rulelist.NewResultCache(filtertest.CacheCount, true),
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.HostBlocked,
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
want: require.True,
|
||||
}, {
|
||||
name: "other_with_cache",
|
||||
cache: rulelist.NewResultCache(filtertest.CacheCount, true),
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.Host,
|
||||
DNSType: dns.TypeA,
|
||||
},
|
||||
want: require.False,
|
||||
}}
|
||||
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
rl := rulelist.NewFromString(
|
||||
filtertest.RuleBlockStr,
|
||||
filtertest.RuleListID1,
|
||||
"",
|
||||
rulelist.EmptyResultCache{},
|
||||
bc.cache,
|
||||
)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(b, filtertest.Timeout)
|
||||
|
||||
b.Run("blocked", func(b *testing.B) {
|
||||
req := &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.HostBlocked,
|
||||
DNSType: dns.TypeA,
|
||||
}
|
||||
|
||||
res := &urlfilter.DNSResult{}
|
||||
|
||||
// Warmup to fill the slices.
|
||||
ok := rl.SetURLFilterResult(ctx, req, res)
|
||||
require.True(b, ok)
|
||||
ok := rl.SetURLFilterResult(ctx, bc.request, res)
|
||||
bc.want(b, ok)
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
res.Reset()
|
||||
ok = rl.SetURLFilterResult(ctx, req, res)
|
||||
ok = rl.SetURLFilterResult(ctx, bc.request, res)
|
||||
}
|
||||
|
||||
require.True(b, ok)
|
||||
bc.want(b, ok)
|
||||
})
|
||||
|
||||
b.Run("other", func(b *testing.B) {
|
||||
req := &urlfilter.DNSRequest{
|
||||
ClientIP: filtertest.IPv4Client,
|
||||
Hostname: filtertest.Host,
|
||||
DNSType: dns.TypeA,
|
||||
}
|
||||
|
||||
res := &urlfilter.DNSResult{}
|
||||
|
||||
// Warmup to fill the slices.
|
||||
ok := rl.SetURLFilterResult(ctx, req, res)
|
||||
require.False(b, ok)
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
res.Reset()
|
||||
ok = rl.SetURLFilterResult(ctx, req, res)
|
||||
}
|
||||
|
||||
require.False(b, ok)
|
||||
})
|
||||
|
||||
// Most recent results:
|
||||
// goos: linux
|
||||
// goarch: amd64
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkRefreshable_SetURLFilterResult/blocked-16 1352236 887.3 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkRefreshable_SetURLFilterResult/other-16 2772519 432.6 ns/op 24 B/op 1 allocs/op
|
||||
// cpu: Apple M3
|
||||
// BenchmarkRefreshable_SetURLFilterResult/blocked-8 2762634 428.2 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkRefreshable_SetURLFilterResult/other-8 5687978 242.0 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkRefreshable_SetURLFilterResult/blocked_with_cache-8 33770551 35.38 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkRefreshable_SetURLFilterResult/other_with_cache-8 43484037 31.63 ns/op 0 B/op 0 allocs/op
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package rulelist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
@@ -18,6 +17,9 @@ const (
|
||||
|
||||
// testHostOther is the other request host for tests.
|
||||
testHostOther = "other.example"
|
||||
|
||||
// cacheCount is the common count of cache items for filtering tests.
|
||||
cacheCount = 1
|
||||
)
|
||||
|
||||
// testRemoteIP is the client IP for tests
|
||||
@@ -29,68 +31,80 @@ const testFltListID filter.ID = "fl1"
|
||||
// testBlockRule is the common blocking rule for tests.
|
||||
const testBlockRule = "||" + testHostBlocked + "\n"
|
||||
|
||||
// TODO(a.garipov): Add benchmarks with a cache.
|
||||
func BenchmarkBaseFilter_SetURLFilterResult(b *testing.B) {
|
||||
f := newBaseFilter(
|
||||
[]byte(testBlockRule),
|
||||
testFltListID,
|
||||
"",
|
||||
EmptyResultCache{},
|
||||
)
|
||||
|
||||
const qt = dns.TypeA
|
||||
|
||||
ctx := context.Background()
|
||||
ctx := b.Context()
|
||||
|
||||
b.Run("blocked", func(b *testing.B) {
|
||||
req := &urlfilter.DNSRequest{
|
||||
benchCases := []struct {
|
||||
request *urlfilter.DNSRequest
|
||||
cache ResultCache
|
||||
want require.BoolAssertionFunc
|
||||
name string
|
||||
}{{
|
||||
name: "blocked",
|
||||
cache: EmptyResultCache{},
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: testRemoteIP,
|
||||
Hostname: testHostBlocked,
|
||||
DNSType: qt,
|
||||
}
|
||||
|
||||
res := &urlfilter.DNSResult{}
|
||||
|
||||
// Warmup to fill the slices.
|
||||
ok := f.SetURLFilterResult(ctx, req, res)
|
||||
require.True(b, ok)
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
res.Reset()
|
||||
ok = f.SetURLFilterResult(ctx, req, res)
|
||||
}
|
||||
|
||||
require.True(b, ok)
|
||||
})
|
||||
|
||||
b.Run("other", func(b *testing.B) {
|
||||
req := &urlfilter.DNSRequest{
|
||||
},
|
||||
want: require.True,
|
||||
}, {
|
||||
name: "other",
|
||||
cache: EmptyResultCache{},
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: testRemoteIP,
|
||||
Hostname: testHostOther,
|
||||
DNSType: qt,
|
||||
}
|
||||
},
|
||||
want: require.False,
|
||||
}, {
|
||||
name: "blocked_with_cache",
|
||||
cache: NewResultCache(cacheCount, true),
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: testRemoteIP,
|
||||
Hostname: testHostBlocked,
|
||||
DNSType: qt,
|
||||
},
|
||||
want: require.True,
|
||||
}, {
|
||||
name: "other_with_cache",
|
||||
cache: NewResultCache(cacheCount, true),
|
||||
request: &urlfilter.DNSRequest{
|
||||
ClientIP: testRemoteIP,
|
||||
Hostname: testHostOther,
|
||||
DNSType: qt,
|
||||
},
|
||||
want: require.False,
|
||||
}}
|
||||
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
f := newBaseFilter([]byte(testBlockRule), testFltListID, "", bc.cache)
|
||||
res := &urlfilter.DNSResult{}
|
||||
|
||||
// Warmup to fill the slices.
|
||||
ok := f.SetURLFilterResult(ctx, req, res)
|
||||
require.False(b, ok)
|
||||
ok := f.SetURLFilterResult(ctx, bc.request, res)
|
||||
bc.want(b, ok)
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
res.Reset()
|
||||
ok = f.SetURLFilterResult(ctx, req, res)
|
||||
ok = f.SetURLFilterResult(ctx, bc.request, res)
|
||||
}
|
||||
|
||||
require.False(b, ok)
|
||||
bc.want(b, ok)
|
||||
})
|
||||
}
|
||||
|
||||
// Most recent results:
|
||||
// goos: linux
|
||||
// goarch: amd64
|
||||
// goos: darwin
|
||||
// goarch: arm64
|
||||
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist
|
||||
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/blocked-16 911409 1315 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/other-16 2824462 425.0 ns/op 24 B/op 1 allocs/op
|
||||
// cpu: Apple M3
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/blocked-8 1793678 670.9 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/other-8 5599238 222.0 ns/op 24 B/op 1 allocs/op
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/blocked_with_cache-8 38971425 31.01 ns/op 0 B/op 0 allocs/op
|
||||
// BenchmarkBaseFilter_SetURLFilterResult/other_with_cache-8 57606105 21.05 ns/op 0 B/op 0 allocs/op
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@ func main() {
|
||||
|
||||
// countriesASNURL is the default URL to get the per-country top ASN statistics
|
||||
// from.
|
||||
const countriesASNURL = `https://static.adtidy.org/dns/countries_asn.json`
|
||||
const countriesASNURL = `https://filters.adtidy.org/dns/countries_asn.json`
|
||||
|
||||
// tmplStr is the template of the generated Go code.
|
||||
const tmplStr = `// Code generated by go run ./asntops_generate.go; DO NOT EDIT.
|
||||
|
||||
97
internal/metrics/domainfilter.go
Normal file
97
internal/metrics/domainfilter.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// DomainFilter is the Prometheus-based implementation of the
|
||||
// [domain.Metrics] interface.
|
||||
type DomainFilter struct {
|
||||
// cacheSize is a gauge with the total count of records in the DomainStorage
|
||||
// cache.
|
||||
cacheSize prometheus.Gauge
|
||||
|
||||
// hits is a counter of the total number of lookups to the DomainStorage
|
||||
// cache that succeeded.
|
||||
hits prometheus.Counter
|
||||
|
||||
// misses is a counter of the total number of lookups to the DomainStorage
|
||||
// cache that resulted in a miss.
|
||||
misses prometheus.Counter
|
||||
}
|
||||
|
||||
// NewDomainFilter registers the filtering metrics in reg and returns a
|
||||
// properly initialized *DomainFilter. filterName must be a valid label
|
||||
// name.
|
||||
func NewDomainFilter(
|
||||
namespace string,
|
||||
filterName string,
|
||||
reg prometheus.Registerer,
|
||||
) (m *DomainFilter, err error) {
|
||||
const (
|
||||
cacheLookups = "domain_filter_cache_lookups"
|
||||
cacheSize = "domain_filter_cache_size"
|
||||
)
|
||||
|
||||
labels := prometheus.Labels{"filter": filterName}
|
||||
|
||||
lookups := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: cacheLookups,
|
||||
Subsystem: subsystemFilter,
|
||||
Namespace: namespace,
|
||||
Help: "Total number of lookups to DomainFilter host cache lookups. " +
|
||||
"Label hit is the lookup result, either 1 for hit or 0 for miss.",
|
||||
ConstLabels: labels,
|
||||
}, []string{"hit"})
|
||||
|
||||
m = &DomainFilter{
|
||||
cacheSize: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: cacheSize,
|
||||
Subsystem: subsystemFilter,
|
||||
Namespace: namespace,
|
||||
Help: "The total number of items in the DomainFilter cache.",
|
||||
ConstLabels: labels,
|
||||
}),
|
||||
hits: lookups.WithLabelValues("1"),
|
||||
misses: lookups.WithLabelValues("0"),
|
||||
}
|
||||
|
||||
var errs []error
|
||||
collectors := container.KeyValues[string, prometheus.Collector]{{
|
||||
Key: cacheSize,
|
||||
Value: m.cacheSize,
|
||||
}, {
|
||||
Key: cacheLookups,
|
||||
Value: lookups,
|
||||
}}
|
||||
|
||||
for _, c := range collectors {
|
||||
err = reg.Register(c.Value)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err = errors.Join(errs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// IncrementLookups implements the [domain.Metrics] interface for
|
||||
// *DomainFilter.
|
||||
func (m *DomainFilter) IncrementLookups(_ context.Context, hit bool) {
|
||||
IncrementCond(hit, m.hits, m.misses)
|
||||
}
|
||||
|
||||
// UpdateCacheSize implements the [domain.Metrics] interface for
|
||||
// *DomainFilter.
|
||||
func (m *DomainFilter) UpdateCacheSize(_ context.Context, size int) {
|
||||
m.cacheSize.Set(float64(size))
|
||||
}
|
||||
@@ -429,7 +429,9 @@ func TestDefault_CreateAutoDevice(t *testing.T) {
|
||||
}
|
||||
wantProf := &agd.Profile{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
DeviceIDs: nil,
|
||||
AutoDevicesEnabled: true,
|
||||
@@ -496,7 +498,9 @@ func TestDefault_deviceChanges(t *testing.T) {
|
||||
|
||||
profBefore := &agd.Profile{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
DeviceIDs: container.NewMapSet(
|
||||
profiledbtest.DeviceID,
|
||||
@@ -507,7 +511,9 @@ func TestDefault_deviceChanges(t *testing.T) {
|
||||
|
||||
profAfter := &agd.Profile{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
DeviceIDs: container.NewMapSet(
|
||||
profiledbtest.DeviceID,
|
||||
@@ -600,7 +606,9 @@ func TestDefault_noDeviceChanges(t *testing.T) {
|
||||
|
||||
profBefore := &agd.Profile{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
FilteringEnabled: true,
|
||||
DeviceIDs: container.NewMapSet(profiledbtest.DeviceID),
|
||||
@@ -608,7 +616,9 @@ func TestDefault_noDeviceChanges(t *testing.T) {
|
||||
|
||||
profAfter := &agd.Profile{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
FilteringEnabled: false,
|
||||
DeviceIDs: container.NewMapSet(profiledbtest.DeviceID),
|
||||
|
||||
24
internal/profiledb/internal/fcpb/fcpb.go
Normal file
24
internal/profiledb/internal/fcpb/fcpb.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Package fcpb contains the new opaque api protobuf structures for the profile
|
||||
// cache.
|
||||
package fcpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// CIDRRangesToPrefixes is a helper that converts a slice of CidrRange to the
|
||||
// slice of [netip.Prefix].
|
||||
func CIDRRangesToPrefixes(cidrs []*CidrRange) (out []netip.Prefix) {
|
||||
for _, c := range cidrs {
|
||||
addr, ok := netip.AddrFromSlice(c.GetAddress())
|
||||
if !ok {
|
||||
// Should never happen.
|
||||
panic(fmt.Errorf("bad address: %v", c.GetAddress()))
|
||||
}
|
||||
|
||||
out = append(out, netip.PrefixFrom(addr, int(c.GetPrefix())))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
3493
internal/profiledb/internal/fcpb/filecache.pb.go
Normal file
3493
internal/profiledb/internal/fcpb/filecache.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
3
internal/profiledb/internal/filecacheopb/filecacheopb.go
Normal file
3
internal/profiledb/internal/filecacheopb/filecacheopb.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package filecacheopb contains encoding and decoding logic for opaque file
|
||||
// cache.
|
||||
package filecacheopb
|
||||
472
internal/profiledb/internal/filecacheopb/profile.go
Normal file
472
internal/profiledb/internal/filecacheopb/profile.go
Normal file
@@ -0,0 +1,472 @@
|
||||
package filecacheopb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/access"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdprotobuf"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/filter/custom"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/fcpb"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/c2h5oh/datasize"
|
||||
)
|
||||
|
||||
// profilesToInternal converts protobuf profile structures into internal ones.
|
||||
// baseCustomLogger and cons must not be nil.
|
||||
//
|
||||
//lint:ignore U1000 TODO(f.setrakov): Use.
|
||||
func profilesToInternal(
|
||||
pbProfiles []*fcpb.Profile,
|
||||
baseCustomLogger *slog.Logger,
|
||||
cons *access.ProfileConstructor,
|
||||
respSzEst datasize.ByteSize,
|
||||
) (profiles []*agd.Profile, err error) {
|
||||
profiles = make([]*agd.Profile, 0, len(pbProfiles))
|
||||
for i, pbProf := range pbProfiles {
|
||||
var prof *agd.Profile
|
||||
prof, err = profileToInternal(pbProf, baseCustomLogger, cons, respSzEst)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("profile at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
profiles = append(profiles, prof)
|
||||
}
|
||||
|
||||
return profiles, nil
|
||||
}
|
||||
|
||||
// profileToInternal converts a protobuf profile structure to an internal one.
|
||||
// baseCustomLogger, cons and pbProfile must not be nil.
|
||||
func profileToInternal(
|
||||
pbProfile *fcpb.Profile,
|
||||
baseCustomLogger *slog.Logger,
|
||||
cons *access.ProfileConstructor,
|
||||
respSzEst datasize.ByteSize,
|
||||
) (prof *agd.Profile, err error) {
|
||||
bmAdult, bmSafeBrowsing, bm, err := blockingModesToInternal(pbProfile)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customDomains, err := customDomainsToInternal(pbProfile)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fltConf, err := configClientToInternal(pbProfile, baseCustomLogger)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &agd.Profile{
|
||||
CustomDomains: customDomains,
|
||||
FilterConfig: fltConf,
|
||||
|
||||
Access: accessToInternal(pbProfile.GetAccess(), cons),
|
||||
AdultBlockingMode: bmAdult,
|
||||
BlockingMode: bm,
|
||||
SafeBrowsingBlockingMode: bmSafeBrowsing,
|
||||
Ratelimiter: rateLimiterToInternal(pbProfile.GetRatelimiter(), respSzEst),
|
||||
|
||||
AccountID: agd.AccountID(pbProfile.GetAccountId()),
|
||||
ID: agd.ProfileID(pbProfile.GetProfileId()),
|
||||
|
||||
// Consider device IDs to have been prevalidated.
|
||||
DeviceIDs: container.NewMapSet(
|
||||
agdprotobuf.UnsafelyConvertStrSlice[string, agd.DeviceID](pbProfile.GetDeviceIds())...,
|
||||
),
|
||||
|
||||
// Consider rule-list IDs to have been prevalidated.
|
||||
FilteredResponseTTL: pbProfile.GetFilteredResponseTtl().AsDuration(),
|
||||
|
||||
AutoDevicesEnabled: pbProfile.GetAutoDevicesEnabled(),
|
||||
BlockChromePrefetch: pbProfile.GetBlockChromePrefetch(),
|
||||
BlockFirefoxCanary: pbProfile.GetBlockFirefoxCanary(),
|
||||
BlockPrivateRelay: pbProfile.GetBlockPrivateRelay(),
|
||||
Deleted: pbProfile.GetDeleted(),
|
||||
FilteringEnabled: pbProfile.GetFilteringEnabled(),
|
||||
IPLogEnabled: pbProfile.GetIpLogEnabled(),
|
||||
QueryLogEnabled: pbProfile.GetQueryLogEnabled(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// customDomainsToInternal converts profile's protobuf custom domains structures
|
||||
// into internal ones. pbProfile must not be nil.
|
||||
func customDomainsToInternal(
|
||||
pbProfile *fcpb.Profile,
|
||||
) (customDomains *agd.AccountCustomDomains, err error) {
|
||||
pbConfs := pbProfile.GetCustomDomains().GetDomains()
|
||||
customDomainConfs, err := customDomainConfsToInternal(pbConfs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("custom domain configs: %w", err)
|
||||
}
|
||||
|
||||
customDomains = &agd.AccountCustomDomains{
|
||||
Domains: customDomainConfs,
|
||||
Enabled: pbProfile.GetCustomDomains().GetEnabled(),
|
||||
}
|
||||
|
||||
return customDomains, nil
|
||||
}
|
||||
|
||||
// configClientToInternal converts profile's protobuf config client structures
|
||||
// into internal ones. pbProfile and baseCustomLogger must not be nil.
|
||||
func configClientToInternal(
|
||||
pbProfile *fcpb.Profile,
|
||||
baseCustomLogger *slog.Logger,
|
||||
) (fltConf *filter.ConfigClient, err error) {
|
||||
pbFltConf := pbProfile.GetFilterConfig()
|
||||
pbFltConfSched := pbFltConf.GetParental().GetPauseSchedule()
|
||||
schedule, err := filterConfigScheduleToInternal(pbFltConfSched)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pause schedule: %w", err)
|
||||
}
|
||||
|
||||
// Consider the rules to have been prevalidated.
|
||||
pbRules := pbFltConf.GetCustom().GetRules()
|
||||
rules := agdprotobuf.UnsafelyConvertStrSlice[string, filter.RuleText](pbRules)
|
||||
|
||||
var flt filter.Custom
|
||||
if len(rules) > 0 {
|
||||
flt = custom.New(&custom.Config{
|
||||
Logger: baseCustomLogger.With("client_id", pbProfile.GetProfileId()),
|
||||
Rules: rules,
|
||||
})
|
||||
}
|
||||
|
||||
ruleListIDs := pbFltConf.GetRuleList().GetIds()
|
||||
safeBrowsing := pbFltConf.GetSafeBrowsing()
|
||||
fltConf = &filter.ConfigClient{
|
||||
Custom: &filter.ConfigCustom{
|
||||
Filter: flt,
|
||||
Enabled: pbFltConf.GetCustom().GetEnabled(),
|
||||
},
|
||||
Parental: configParentalToInternal(pbFltConf, schedule),
|
||||
RuleList: &filter.ConfigRuleList{
|
||||
// Consider rule-list IDs to have been prevalidated.
|
||||
IDs: agdprotobuf.UnsafelyConvertStrSlice[string, filter.ID](ruleListIDs),
|
||||
Enabled: pbFltConf.GetRuleList().GetEnabled(),
|
||||
},
|
||||
SafeBrowsing: &filter.ConfigSafeBrowsing{
|
||||
Enabled: safeBrowsing.GetEnabled(),
|
||||
DangerousDomainsEnabled: safeBrowsing.GetDangerousDomainsEnabled(),
|
||||
NewlyRegisteredDomainsEnabled: safeBrowsing.GetNewlyRegisteredDomainsEnabled(),
|
||||
},
|
||||
}
|
||||
|
||||
return fltConf, nil
|
||||
}
|
||||
|
||||
// configParentalToInternal converts filter config's protobuf parental config
|
||||
// structures to internal ones. pbFltConf must not be nil.
|
||||
func configParentalToInternal(
|
||||
pbFltConf *fcpb.FilterConfig,
|
||||
schedule *filter.ConfigSchedule,
|
||||
) (c *filter.ConfigParental) {
|
||||
parental := pbFltConf.GetParental()
|
||||
|
||||
return &filter.ConfigParental{
|
||||
PauseSchedule: schedule,
|
||||
// Consider blocked-service IDs to have been prevalidated.
|
||||
BlockedServices: agdprotobuf.UnsafelyConvertStrSlice[string, filter.BlockedServiceID](
|
||||
parental.GetBlockedServices(),
|
||||
),
|
||||
Enabled: parental.GetEnabled(),
|
||||
AdultBlockingEnabled: parental.GetAdultBlockingEnabled(),
|
||||
SafeSearchGeneralEnabled: parental.GetSafeSearchGeneralEnabled(),
|
||||
SafeSearchYouTubeEnabled: parental.GetSafeSearchYoutubeEnabled(),
|
||||
}
|
||||
}
|
||||
|
||||
// blockingModesToInternal converts profile's protobuf blocking mode structures
|
||||
// into internal ones.
|
||||
func blockingModesToInternal(pbProfile *fcpb.Profile) (
|
||||
bmAdult dnsmsg.BlockingMode,
|
||||
bmSafeBrowsing dnsmsg.BlockingMode,
|
||||
bm dnsmsg.BlockingMode,
|
||||
err error,
|
||||
) {
|
||||
bmAdult, err = adultBlockingModeToInternal(pbProfile)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("adult blocking mode: %w", err)
|
||||
}
|
||||
|
||||
bmSafeBrowsing, err = safeBrowsingBlockingModeToInternal(pbProfile)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("safe browsing blocking mode: %w", err)
|
||||
}
|
||||
|
||||
bm, err = blockingModeToInternal(pbProfile)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("blocking mode: %w", err)
|
||||
}
|
||||
|
||||
return bmAdult, bmSafeBrowsing, bm, nil
|
||||
}
|
||||
|
||||
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one.
|
||||
// TODO(d.kolyshev): DRY with adultBlockingModeToInternal and
|
||||
// safeBrowsingBlockingModeToInternal.
|
||||
func blockingModeToInternal(pbProfile *fcpb.Profile) (m dnsmsg.BlockingMode, err error) {
|
||||
if !pbProfile.HasBlockingMode() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
customIP := pbProfile.GetBlockingModeCustomIp()
|
||||
|
||||
switch {
|
||||
case customIP != nil:
|
||||
var ipv4 []netip.Addr
|
||||
ipv4, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv4())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v4 custom ips: %w", err)
|
||||
}
|
||||
|
||||
var ipv6 []netip.Addr
|
||||
ipv6, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv6())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v6 custom ips: %w", err)
|
||||
}
|
||||
|
||||
return &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: ipv4,
|
||||
IPv6: ipv6,
|
||||
}, nil
|
||||
case pbProfile.GetBlockingModeNxdomain() != nil:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case pbProfile.GetBlockingModeNullIp() != nil:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case pbProfile.GetBlockingModeRefused() != nil:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bad pb blocking mode")
|
||||
}
|
||||
}
|
||||
|
||||
// adultBlockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one.
|
||||
func adultBlockingModeToInternal(pbProfile *fcpb.Profile) (m dnsmsg.BlockingMode, err error) {
|
||||
if !pbProfile.HasAdultBlockingMode() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
customIP := pbProfile.GetAdultBlockingModeCustomIp()
|
||||
|
||||
switch {
|
||||
case customIP != nil:
|
||||
var ipv4 []netip.Addr
|
||||
ipv4, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv4())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v4 custom ips: %w", err)
|
||||
}
|
||||
|
||||
var ipv6 []netip.Addr
|
||||
ipv6, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv6())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v6 custom ips: %w", err)
|
||||
}
|
||||
|
||||
return &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: ipv4,
|
||||
IPv6: ipv6,
|
||||
}, nil
|
||||
case pbProfile.GetAdultBlockingModeNxdomain() != nil:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case pbProfile.GetAdultBlockingModeNullIp() != nil:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case pbProfile.GetAdultBlockingModeRefused() != nil:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bad pb adult blocking mode")
|
||||
}
|
||||
}
|
||||
|
||||
// safeBrowsingBlockingModeToInternal converts a protobuf blocking-mode sum-type
|
||||
// to an internal one.
|
||||
func safeBrowsingBlockingModeToInternal(
|
||||
pbProfile *fcpb.Profile,
|
||||
) (m dnsmsg.BlockingMode, err error) {
|
||||
if !pbProfile.HasSafeBrowsingBlockingMode() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
customIP := pbProfile.GetSafeBrowsingBlockingModeCustomIp()
|
||||
|
||||
switch {
|
||||
case customIP != nil:
|
||||
var ipv4 []netip.Addr
|
||||
ipv4, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv4())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v4 custom ips: %w", err)
|
||||
}
|
||||
|
||||
var ipv6 []netip.Addr
|
||||
ipv6, err = agdprotobuf.ByteSlicesToIPs(customIP.GetIpv6())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v6 custom ips: %w", err)
|
||||
}
|
||||
|
||||
return &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: ipv4,
|
||||
IPv6: ipv6,
|
||||
}, nil
|
||||
case pbProfile.GetSafeBrowsingBlockingModeNxdomain() != nil:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case pbProfile.GetSafeBrowsingBlockingModeNullIp() != nil:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case pbProfile.GetSafeBrowsingBlockingModeRefused() != nil:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bad pb safe browsing blocking mode")
|
||||
}
|
||||
}
|
||||
|
||||
// filterConfigScheduleToInternal converts a protobuf protection-schedule
|
||||
// structure to an internal one.
|
||||
func filterConfigScheduleToInternal(
|
||||
pbSchedule *fcpb.FilterConfig_Schedule,
|
||||
) (c *filter.ConfigSchedule, err error) {
|
||||
if pbSchedule == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loc, err := agdtime.LoadLocation(pbSchedule.GetTimeZone())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("time zone: %w", err)
|
||||
}
|
||||
|
||||
week := pbSchedule.GetWeek()
|
||||
return &filter.ConfigSchedule{
|
||||
// Consider the lengths to be prevalidated.
|
||||
Week: &filter.WeeklySchedule{
|
||||
time.Monday: dayIntervalToInternal(week.GetMon()),
|
||||
time.Tuesday: dayIntervalToInternal(week.GetTue()),
|
||||
time.Wednesday: dayIntervalToInternal(week.GetWed()),
|
||||
time.Thursday: dayIntervalToInternal(week.GetThu()),
|
||||
time.Friday: dayIntervalToInternal(week.GetFri()),
|
||||
time.Saturday: dayIntervalToInternal(week.GetSat()),
|
||||
time.Sunday: dayIntervalToInternal(week.GetSun()),
|
||||
},
|
||||
TimeZone: loc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dayIntervalToInternal converts a protobuf day interval to an internal one.
|
||||
func dayIntervalToInternal(pbInterval *fcpb.DayInterval) (i *filter.DayInterval) {
|
||||
if pbInterval == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &filter.DayInterval{
|
||||
// #nosec G115 -- The values put in these are always from uint16s.
|
||||
Start: uint16(pbInterval.GetStart()),
|
||||
// #nosec G115 -- The values put in these are always from uint16s.
|
||||
End: uint16(pbInterval.GetEnd()),
|
||||
}
|
||||
}
|
||||
|
||||
// customDomainConfsToInternal converts protobuf custom-domain configurations to
|
||||
// internal ones.
|
||||
func customDomainConfsToInternal(
|
||||
pbConfs []*fcpb.CustomDomainConfig,
|
||||
) (confs []*agd.CustomDomainConfig, err error) {
|
||||
l := len(pbConfs)
|
||||
if l == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
confs = make([]*agd.CustomDomainConfig, 0, l)
|
||||
for i, pbConf := range pbConfs {
|
||||
var c *agd.CustomDomainConfig
|
||||
c, err = customDomainConfigToInternal(pbConf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
confs = append(confs, c)
|
||||
}
|
||||
|
||||
return confs, nil
|
||||
}
|
||||
|
||||
// customDomainConfigToInternal converts a protobuf custom-domain config to an
|
||||
// internal one. pbCustomDomainConf must not be nil.
|
||||
func customDomainConfigToInternal(
|
||||
pbCustomDomainConf *fcpb.CustomDomainConfig,
|
||||
) (c *agd.CustomDomainConfig, err error) {
|
||||
var state agd.CustomDomainState
|
||||
|
||||
if current := pbCustomDomainConf.GetStateCurrent(); current != nil {
|
||||
state = &agd.CustomDomainStateCurrent{
|
||||
NotBefore: current.GetNotBefore().AsTime(),
|
||||
NotAfter: current.GetNotAfter().AsTime(),
|
||||
// Consider certificate names to have been prevalidated.
|
||||
CertName: agd.CertificateName(current.GetCertName()),
|
||||
Enabled: current.GetEnabled(),
|
||||
}
|
||||
} else if pending := pbCustomDomainConf.GetStatePending(); pending != nil {
|
||||
state = &agd.CustomDomainStatePending{
|
||||
Expire: pending.GetExpire().AsTime(),
|
||||
WellKnownPath: pending.GetWellKnownPath(),
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("pb unknown domain state: %w", errors.ErrBadEnumValue)
|
||||
}
|
||||
|
||||
return &agd.CustomDomainConfig{
|
||||
State: state,
|
||||
Domains: slices.Clone(pbCustomDomainConf.GetDomains()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rateLimiterToInternal converts a protobuf rate-limiting settings structure to
|
||||
// an internal one.
|
||||
func rateLimiterToInternal(
|
||||
pbRateLimiter *fcpb.Ratelimiter,
|
||||
respSzEst datasize.ByteSize,
|
||||
) (r agd.Ratelimiter) {
|
||||
if !pbRateLimiter.GetEnabled() {
|
||||
return agd.GlobalRatelimiter{}
|
||||
}
|
||||
|
||||
return agd.NewDefaultRatelimiter(&agd.RatelimitConfig{
|
||||
ClientSubnets: fcpb.CIDRRangesToPrefixes(pbRateLimiter.GetClientCidr()),
|
||||
RPS: pbRateLimiter.GetRps(),
|
||||
Enabled: pbRateLimiter.GetEnabled(),
|
||||
}, respSzEst)
|
||||
}
|
||||
|
||||
// accessToInternal converts protobuf access settings to an internal structure.
|
||||
// If x is nil, toInternal returns [access.EmptyProfile]. If pbAccess is not
|
||||
// nil cons must not be nil.
|
||||
func accessToInternal(pbAccess *fcpb.Access, cons *access.ProfileConstructor) (a access.Profile) {
|
||||
if pbAccess == nil {
|
||||
return access.EmptyProfile{}
|
||||
}
|
||||
|
||||
allowedASN := pbAccess.GetAllowlistAsn()
|
||||
blockedASN := pbAccess.GetBlocklistAsn()
|
||||
|
||||
return cons.New(&access.ProfileConfig{
|
||||
AllowedNets: fcpb.CIDRRangesToPrefixes(pbAccess.GetAllowlistCidr()),
|
||||
BlockedNets: fcpb.CIDRRangesToPrefixes(pbAccess.GetBlocklistCidr()),
|
||||
AllowedASN: agdprotobuf.UnsafelyConvertUint32Slice[uint32, geoip.ASN](allowedASN),
|
||||
BlockedASN: agdprotobuf.UnsafelyConvertUint32Slice[uint32, geoip.ASN](blockedASN),
|
||||
BlocklistDomainRules: pbAccess.GetBlocklistDomainRules(),
|
||||
StandardEnabled: pbAccess.GetStandardEnabled(),
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.8
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.32.0
|
||||
// source: filecache.proto
|
||||
|
||||
@@ -116,6 +116,20 @@ type Profile struct {
|
||||
FilteringEnabled bool `protobuf:"varint,16,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
|
||||
IpLogEnabled bool `protobuf:"varint,17,opt,name=ip_log_enabled,json=ipLogEnabled,proto3" json:"ip_log_enabled,omitempty"`
|
||||
QueryLogEnabled bool `protobuf:"varint,18,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"`
|
||||
// Types that are valid to be assigned to AdultBlockingMode:
|
||||
//
|
||||
// *Profile_AdultBlockingModeCustomIp
|
||||
// *Profile_AdultBlockingModeNxdomain
|
||||
// *Profile_AdultBlockingModeNullIp
|
||||
// *Profile_AdultBlockingModeRefused
|
||||
AdultBlockingMode isProfile_AdultBlockingMode `protobuf_oneof:"adult_blocking_mode"`
|
||||
// Types that are valid to be assigned to SafeBrowsingBlockingMode:
|
||||
//
|
||||
// *Profile_SafeBrowsingBlockingModeCustomIp
|
||||
// *Profile_SafeBrowsingBlockingModeNxdomain
|
||||
// *Profile_SafeBrowsingBlockingModeNullIp
|
||||
// *Profile_SafeBrowsingBlockingModeRefused
|
||||
SafeBrowsingBlockingMode isProfile_SafeBrowsingBlockingMode `protobuf_oneof:"safe_browsing_blocking_mode"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -305,6 +319,92 @@ func (x *Profile) GetQueryLogEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Profile) GetAdultBlockingMode() isProfile_AdultBlockingMode {
|
||||
if x != nil {
|
||||
return x.AdultBlockingMode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetAdultBlockingModeCustomIp() *BlockingModeCustomIP {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*Profile_AdultBlockingModeCustomIp); ok {
|
||||
return x.AdultBlockingModeCustomIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetAdultBlockingModeNxdomain() *BlockingModeNXDOMAIN {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*Profile_AdultBlockingModeNxdomain); ok {
|
||||
return x.AdultBlockingModeNxdomain
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetAdultBlockingModeNullIp() *BlockingModeNullIP {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*Profile_AdultBlockingModeNullIp); ok {
|
||||
return x.AdultBlockingModeNullIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetAdultBlockingModeRefused() *BlockingModeREFUSED {
|
||||
if x != nil {
|
||||
if x, ok := x.AdultBlockingMode.(*Profile_AdultBlockingModeRefused); ok {
|
||||
return x.AdultBlockingModeRefused
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetSafeBrowsingBlockingMode() isProfile_SafeBrowsingBlockingMode {
|
||||
if x != nil {
|
||||
return x.SafeBrowsingBlockingMode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetSafeBrowsingBlockingModeCustomIp() *BlockingModeCustomIP {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*Profile_SafeBrowsingBlockingModeCustomIp); ok {
|
||||
return x.SafeBrowsingBlockingModeCustomIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetSafeBrowsingBlockingModeNxdomain() *BlockingModeNXDOMAIN {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*Profile_SafeBrowsingBlockingModeNxdomain); ok {
|
||||
return x.SafeBrowsingBlockingModeNxdomain
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetSafeBrowsingBlockingModeNullIp() *BlockingModeNullIP {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*Profile_SafeBrowsingBlockingModeNullIp); ok {
|
||||
return x.SafeBrowsingBlockingModeNullIp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Profile) GetSafeBrowsingBlockingModeRefused() *BlockingModeREFUSED {
|
||||
if x != nil {
|
||||
if x, ok := x.SafeBrowsingBlockingMode.(*Profile_SafeBrowsingBlockingModeRefused); ok {
|
||||
return x.SafeBrowsingBlockingModeRefused
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isProfile_BlockingMode interface {
|
||||
isProfile_BlockingMode()
|
||||
}
|
||||
@@ -333,6 +433,62 @@ func (*Profile_BlockingModeNullIp) isProfile_BlockingMode() {}
|
||||
|
||||
func (*Profile_BlockingModeRefused) isProfile_BlockingMode() {}
|
||||
|
||||
type isProfile_AdultBlockingMode interface {
|
||||
isProfile_AdultBlockingMode()
|
||||
}
|
||||
|
||||
type Profile_AdultBlockingModeCustomIp struct {
|
||||
AdultBlockingModeCustomIp *BlockingModeCustomIP `protobuf:"bytes,21,opt,name=adult_blocking_mode_custom_ip,json=adultBlockingModeCustomIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_AdultBlockingModeNxdomain struct {
|
||||
AdultBlockingModeNxdomain *BlockingModeNXDOMAIN `protobuf:"bytes,22,opt,name=adult_blocking_mode_nxdomain,json=adultBlockingModeNxdomain,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_AdultBlockingModeNullIp struct {
|
||||
AdultBlockingModeNullIp *BlockingModeNullIP `protobuf:"bytes,23,opt,name=adult_blocking_mode_null_ip,json=adultBlockingModeNullIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_AdultBlockingModeRefused struct {
|
||||
AdultBlockingModeRefused *BlockingModeREFUSED `protobuf:"bytes,24,opt,name=adult_blocking_mode_refused,json=adultBlockingModeRefused,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Profile_AdultBlockingModeCustomIp) isProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*Profile_AdultBlockingModeNxdomain) isProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*Profile_AdultBlockingModeNullIp) isProfile_AdultBlockingMode() {}
|
||||
|
||||
func (*Profile_AdultBlockingModeRefused) isProfile_AdultBlockingMode() {}
|
||||
|
||||
type isProfile_SafeBrowsingBlockingMode interface {
|
||||
isProfile_SafeBrowsingBlockingMode()
|
||||
}
|
||||
|
||||
type Profile_SafeBrowsingBlockingModeCustomIp struct {
|
||||
SafeBrowsingBlockingModeCustomIp *BlockingModeCustomIP `protobuf:"bytes,25,opt,name=safe_browsing_blocking_mode_custom_ip,json=safeBrowsingBlockingModeCustomIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_SafeBrowsingBlockingModeNxdomain struct {
|
||||
SafeBrowsingBlockingModeNxdomain *BlockingModeNXDOMAIN `protobuf:"bytes,26,opt,name=safe_browsing_blocking_mode_nxdomain,json=safeBrowsingBlockingModeNxdomain,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_SafeBrowsingBlockingModeNullIp struct {
|
||||
SafeBrowsingBlockingModeNullIp *BlockingModeNullIP `protobuf:"bytes,27,opt,name=safe_browsing_blocking_mode_null_ip,json=safeBrowsingBlockingModeNullIp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Profile_SafeBrowsingBlockingModeRefused struct {
|
||||
SafeBrowsingBlockingModeRefused *BlockingModeREFUSED `protobuf:"bytes,28,opt,name=safe_browsing_blocking_mode_refused,json=safeBrowsingBlockingModeRefused,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Profile_SafeBrowsingBlockingModeCustomIp) isProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*Profile_SafeBrowsingBlockingModeNxdomain) isProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*Profile_SafeBrowsingBlockingModeNullIp) isProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
func (*Profile_SafeBrowsingBlockingModeRefused) isProfile_SafeBrowsingBlockingMode() {}
|
||||
|
||||
type AccountCustomDomains struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Domains []*CustomDomainConfig `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"`
|
||||
@@ -1638,7 +1794,7 @@ const file_filecache_proto_rawDesc = "" +
|
||||
"\tsync_time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\bsyncTime\x12.\n" +
|
||||
"\bprofiles\x18\x02 \x03(\v2\x12.profiledb.ProfileR\bprofiles\x12+\n" +
|
||||
"\adevices\x18\x03 \x03(\v2\x11.profiledb.DeviceR\adevices\x12\x18\n" +
|
||||
"\aversion\x18\x04 \x01(\x05R\aversion\"\xef\b\n" +
|
||||
"\aversion\x18\x04 \x01(\x05R\aversion\"\xf3\x0f\n" +
|
||||
"\aProfile\x12F\n" +
|
||||
"\x0ecustom_domains\x18\x14 \x01(\v2\x1f.profiledb.AccountCustomDomainsR\rcustomDomains\x12<\n" +
|
||||
"\rfilter_config\x18\x01 \x01(\v2\x17.profiledb.FilterConfigR\ffilterConfig\x12)\n" +
|
||||
@@ -1663,8 +1819,18 @@ const file_filecache_proto_rawDesc = "" +
|
||||
"\adeleted\x18\x0f \x01(\bR\adeleted\x12+\n" +
|
||||
"\x11filtering_enabled\x18\x10 \x01(\bR\x10filteringEnabled\x12$\n" +
|
||||
"\x0eip_log_enabled\x18\x11 \x01(\bR\fipLogEnabled\x12*\n" +
|
||||
"\x11query_log_enabled\x18\x12 \x01(\bR\x0fqueryLogEnabledB\x0f\n" +
|
||||
"\rblocking_mode\"i\n" +
|
||||
"\x11query_log_enabled\x18\x12 \x01(\bR\x0fqueryLogEnabled\x12c\n" +
|
||||
"\x1dadult_blocking_mode_custom_ip\x18\x15 \x01(\v2\x1f.profiledb.BlockingModeCustomIPH\x01R\x19adultBlockingModeCustomIp\x12b\n" +
|
||||
"\x1cadult_blocking_mode_nxdomain\x18\x16 \x01(\v2\x1f.profiledb.BlockingModeNXDOMAINH\x01R\x19adultBlockingModeNxdomain\x12]\n" +
|
||||
"\x1badult_blocking_mode_null_ip\x18\x17 \x01(\v2\x1d.profiledb.BlockingModeNullIPH\x01R\x17adultBlockingModeNullIp\x12_\n" +
|
||||
"\x1badult_blocking_mode_refused\x18\x18 \x01(\v2\x1e.profiledb.BlockingModeREFUSEDH\x01R\x18adultBlockingModeRefused\x12r\n" +
|
||||
"%safe_browsing_blocking_mode_custom_ip\x18\x19 \x01(\v2\x1f.profiledb.BlockingModeCustomIPH\x02R safeBrowsingBlockingModeCustomIp\x12q\n" +
|
||||
"$safe_browsing_blocking_mode_nxdomain\x18\x1a \x01(\v2\x1f.profiledb.BlockingModeNXDOMAINH\x02R safeBrowsingBlockingModeNxdomain\x12l\n" +
|
||||
"#safe_browsing_blocking_mode_null_ip\x18\x1b \x01(\v2\x1d.profiledb.BlockingModeNullIPH\x02R\x1esafeBrowsingBlockingModeNullIp\x12n\n" +
|
||||
"#safe_browsing_blocking_mode_refused\x18\x1c \x01(\v2\x1e.profiledb.BlockingModeREFUSEDH\x02R\x1fsafeBrowsingBlockingModeRefusedB\x0f\n" +
|
||||
"\rblocking_modeB\x15\n" +
|
||||
"\x13adult_blocking_modeB\x1d\n" +
|
||||
"\x1bsafe_browsing_blocking_mode\"i\n" +
|
||||
"\x14AccountCustomDomains\x127\n" +
|
||||
"\adomains\x18\x01 \x03(\v2\x1d.profiledb.CustomDomainConfigR\adomains\x12\x18\n" +
|
||||
"\aenabled\x18\x02 \x01(\bR\aenabled\"\x85\x04\n" +
|
||||
@@ -1807,34 +1973,42 @@ var file_filecache_proto_depIdxs = []int32{
|
||||
9, // 9: profiledb.Profile.blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED
|
||||
14, // 10: profiledb.Profile.ratelimiter:type_name -> profiledb.Ratelimiter
|
||||
24, // 11: profiledb.Profile.filtered_response_ttl:type_name -> google.protobuf.Duration
|
||||
3, // 12: profiledb.AccountCustomDomains.domains:type_name -> profiledb.CustomDomainConfig
|
||||
15, // 13: profiledb.CustomDomainConfig.state_current:type_name -> profiledb.CustomDomainConfig.StateCurrent
|
||||
16, // 14: profiledb.CustomDomainConfig.state_pending:type_name -> profiledb.CustomDomainConfig.StatePending
|
||||
17, // 15: profiledb.FilterConfig.custom:type_name -> profiledb.FilterConfig.Custom
|
||||
18, // 16: profiledb.FilterConfig.parental:type_name -> profiledb.FilterConfig.Parental
|
||||
21, // 17: profiledb.FilterConfig.rule_list:type_name -> profiledb.FilterConfig.RuleList
|
||||
22, // 18: profiledb.FilterConfig.safe_browsing:type_name -> profiledb.FilterConfig.SafeBrowsing
|
||||
13, // 19: profiledb.Device.authentication:type_name -> profiledb.AuthenticationSettings
|
||||
12, // 20: profiledb.Access.allowlist_cidr:type_name -> profiledb.CidrRange
|
||||
12, // 21: profiledb.Access.blocklist_cidr:type_name -> profiledb.CidrRange
|
||||
12, // 22: profiledb.Ratelimiter.client_cidr:type_name -> profiledb.CidrRange
|
||||
23, // 23: profiledb.CustomDomainConfig.StateCurrent.not_before:type_name -> google.protobuf.Timestamp
|
||||
23, // 24: profiledb.CustomDomainConfig.StateCurrent.not_after:type_name -> google.protobuf.Timestamp
|
||||
23, // 25: profiledb.CustomDomainConfig.StatePending.expire:type_name -> google.protobuf.Timestamp
|
||||
19, // 26: profiledb.FilterConfig.Parental.pause_schedule:type_name -> profiledb.FilterConfig.Schedule
|
||||
20, // 27: profiledb.FilterConfig.Schedule.week:type_name -> profiledb.FilterConfig.WeeklySchedule
|
||||
5, // 28: profiledb.FilterConfig.WeeklySchedule.mon:type_name -> profiledb.DayInterval
|
||||
5, // 29: profiledb.FilterConfig.WeeklySchedule.tue:type_name -> profiledb.DayInterval
|
||||
5, // 30: profiledb.FilterConfig.WeeklySchedule.wed:type_name -> profiledb.DayInterval
|
||||
5, // 31: profiledb.FilterConfig.WeeklySchedule.thu:type_name -> profiledb.DayInterval
|
||||
5, // 32: profiledb.FilterConfig.WeeklySchedule.fri:type_name -> profiledb.DayInterval
|
||||
5, // 33: profiledb.FilterConfig.WeeklySchedule.sat:type_name -> profiledb.DayInterval
|
||||
5, // 34: profiledb.FilterConfig.WeeklySchedule.sun:type_name -> profiledb.DayInterval
|
||||
35, // [35:35] is the sub-list for method output_type
|
||||
35, // [35:35] is the sub-list for method input_type
|
||||
35, // [35:35] is the sub-list for extension type_name
|
||||
35, // [35:35] is the sub-list for extension extendee
|
||||
0, // [0:35] is the sub-list for field type_name
|
||||
6, // 12: profiledb.Profile.adult_blocking_mode_custom_ip:type_name -> profiledb.BlockingModeCustomIP
|
||||
7, // 13: profiledb.Profile.adult_blocking_mode_nxdomain:type_name -> profiledb.BlockingModeNXDOMAIN
|
||||
8, // 14: profiledb.Profile.adult_blocking_mode_null_ip:type_name -> profiledb.BlockingModeNullIP
|
||||
9, // 15: profiledb.Profile.adult_blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED
|
||||
6, // 16: profiledb.Profile.safe_browsing_blocking_mode_custom_ip:type_name -> profiledb.BlockingModeCustomIP
|
||||
7, // 17: profiledb.Profile.safe_browsing_blocking_mode_nxdomain:type_name -> profiledb.BlockingModeNXDOMAIN
|
||||
8, // 18: profiledb.Profile.safe_browsing_blocking_mode_null_ip:type_name -> profiledb.BlockingModeNullIP
|
||||
9, // 19: profiledb.Profile.safe_browsing_blocking_mode_refused:type_name -> profiledb.BlockingModeREFUSED
|
||||
3, // 20: profiledb.AccountCustomDomains.domains:type_name -> profiledb.CustomDomainConfig
|
||||
15, // 21: profiledb.CustomDomainConfig.state_current:type_name -> profiledb.CustomDomainConfig.StateCurrent
|
||||
16, // 22: profiledb.CustomDomainConfig.state_pending:type_name -> profiledb.CustomDomainConfig.StatePending
|
||||
17, // 23: profiledb.FilterConfig.custom:type_name -> profiledb.FilterConfig.Custom
|
||||
18, // 24: profiledb.FilterConfig.parental:type_name -> profiledb.FilterConfig.Parental
|
||||
21, // 25: profiledb.FilterConfig.rule_list:type_name -> profiledb.FilterConfig.RuleList
|
||||
22, // 26: profiledb.FilterConfig.safe_browsing:type_name -> profiledb.FilterConfig.SafeBrowsing
|
||||
13, // 27: profiledb.Device.authentication:type_name -> profiledb.AuthenticationSettings
|
||||
12, // 28: profiledb.Access.allowlist_cidr:type_name -> profiledb.CidrRange
|
||||
12, // 29: profiledb.Access.blocklist_cidr:type_name -> profiledb.CidrRange
|
||||
12, // 30: profiledb.Ratelimiter.client_cidr:type_name -> profiledb.CidrRange
|
||||
23, // 31: profiledb.CustomDomainConfig.StateCurrent.not_before:type_name -> google.protobuf.Timestamp
|
||||
23, // 32: profiledb.CustomDomainConfig.StateCurrent.not_after:type_name -> google.protobuf.Timestamp
|
||||
23, // 33: profiledb.CustomDomainConfig.StatePending.expire:type_name -> google.protobuf.Timestamp
|
||||
19, // 34: profiledb.FilterConfig.Parental.pause_schedule:type_name -> profiledb.FilterConfig.Schedule
|
||||
20, // 35: profiledb.FilterConfig.Schedule.week:type_name -> profiledb.FilterConfig.WeeklySchedule
|
||||
5, // 36: profiledb.FilterConfig.WeeklySchedule.mon:type_name -> profiledb.DayInterval
|
||||
5, // 37: profiledb.FilterConfig.WeeklySchedule.tue:type_name -> profiledb.DayInterval
|
||||
5, // 38: profiledb.FilterConfig.WeeklySchedule.wed:type_name -> profiledb.DayInterval
|
||||
5, // 39: profiledb.FilterConfig.WeeklySchedule.thu:type_name -> profiledb.DayInterval
|
||||
5, // 40: profiledb.FilterConfig.WeeklySchedule.fri:type_name -> profiledb.DayInterval
|
||||
5, // 41: profiledb.FilterConfig.WeeklySchedule.sat:type_name -> profiledb.DayInterval
|
||||
5, // 42: profiledb.FilterConfig.WeeklySchedule.sun:type_name -> profiledb.DayInterval
|
||||
43, // [43:43] is the sub-list for method output_type
|
||||
43, // [43:43] is the sub-list for method input_type
|
||||
43, // [43:43] is the sub-list for extension type_name
|
||||
43, // [43:43] is the sub-list for extension extendee
|
||||
0, // [0:43] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_filecache_proto_init() }
|
||||
@@ -1847,6 +2021,14 @@ func file_filecache_proto_init() {
|
||||
(*Profile_BlockingModeNxdomain)(nil),
|
||||
(*Profile_BlockingModeNullIp)(nil),
|
||||
(*Profile_BlockingModeRefused)(nil),
|
||||
(*Profile_AdultBlockingModeCustomIp)(nil),
|
||||
(*Profile_AdultBlockingModeNxdomain)(nil),
|
||||
(*Profile_AdultBlockingModeNullIp)(nil),
|
||||
(*Profile_AdultBlockingModeRefused)(nil),
|
||||
(*Profile_SafeBrowsingBlockingModeCustomIp)(nil),
|
||||
(*Profile_SafeBrowsingBlockingModeNxdomain)(nil),
|
||||
(*Profile_SafeBrowsingBlockingModeNullIp)(nil),
|
||||
(*Profile_SafeBrowsingBlockingModeRefused)(nil),
|
||||
}
|
||||
file_filecache_proto_msgTypes[3].OneofWrappers = []any{
|
||||
(*CustomDomainConfig_StateCurrent_)(nil),
|
||||
|
||||
@@ -2,8 +2,6 @@ syntax = "proto3";
|
||||
|
||||
package profiledb;
|
||||
|
||||
option go_package = "./filecachepb";
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
@@ -43,6 +41,20 @@ message Profile {
|
||||
bool filtering_enabled = 16;
|
||||
bool ip_log_enabled = 17;
|
||||
bool query_log_enabled = 18;
|
||||
|
||||
oneof adult_blocking_mode {
|
||||
BlockingModeCustomIP adult_blocking_mode_custom_ip = 21;
|
||||
BlockingModeNXDOMAIN adult_blocking_mode_nxdomain = 22;
|
||||
BlockingModeNullIP adult_blocking_mode_null_ip = 23;
|
||||
BlockingModeREFUSED adult_blocking_mode_refused = 24;
|
||||
}
|
||||
|
||||
oneof safe_browsing_blocking_mode {
|
||||
BlockingModeCustomIP safe_browsing_blocking_mode_custom_ip = 25;
|
||||
BlockingModeNXDOMAIN safe_browsing_blocking_mode_nxdomain = 26;
|
||||
BlockingModeNullIP safe_browsing_blocking_mode_null_ip = 27;
|
||||
BlockingModeREFUSED safe_browsing_blocking_mode_refused = 28;
|
||||
}
|
||||
}
|
||||
|
||||
message AccountCustomDomains {
|
||||
|
||||
@@ -90,6 +90,16 @@ func (x *Profile) toInternal(
|
||||
cons *access.ProfileConstructor,
|
||||
respSzEst datasize.ByteSize,
|
||||
) (prof *agd.Profile, err error) {
|
||||
adultBlockingMode, err := adultBlockingModeToInternal(x.AdultBlockingMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adult blocking mode: %w", err)
|
||||
}
|
||||
|
||||
safeBrowsingBlockingMode, err := safeBrowsingBlockingModeToInternal(x.SafeBrowsingBlockingMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("safe browsing blocking mode: %w", err)
|
||||
}
|
||||
|
||||
m, err := blockingModeToInternal(x.BlockingMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("blocking mode: %w", err)
|
||||
@@ -102,7 +112,7 @@ func (x *Profile) toInternal(
|
||||
}
|
||||
|
||||
// Consider the rules to have been prevalidated.
|
||||
rules := unsafelyConvertStrSlice[string, filter.RuleText](pbFltConf.Custom.Rules)
|
||||
rules := agdprotobuf.UnsafelyConvertStrSlice[string, filter.RuleText](pbFltConf.Custom.Rules)
|
||||
|
||||
var flt filter.Custom
|
||||
if len(rules) > 0 {
|
||||
@@ -130,7 +140,7 @@ func (x *Profile) toInternal(
|
||||
Parental: &filter.ConfigParental{
|
||||
PauseSchedule: schedule,
|
||||
// Consider blocked-service IDs to have been prevalidated.
|
||||
BlockedServices: unsafelyConvertStrSlice[string, filter.BlockedServiceID](
|
||||
BlockedServices: agdprotobuf.UnsafelyConvertStrSlice[string, filter.BlockedServiceID](
|
||||
pbFltConf.Parental.BlockedServices,
|
||||
),
|
||||
Enabled: pbFltConf.Parental.Enabled,
|
||||
@@ -140,7 +150,7 @@ func (x *Profile) toInternal(
|
||||
},
|
||||
RuleList: &filter.ConfigRuleList{
|
||||
// Consider rule-list IDs to have been prevalidated.
|
||||
IDs: unsafelyConvertStrSlice[string, filter.ID](pbFltConf.RuleList.Ids),
|
||||
IDs: agdprotobuf.UnsafelyConvertStrSlice[string, filter.ID](pbFltConf.RuleList.Ids),
|
||||
Enabled: pbFltConf.RuleList.Enabled,
|
||||
},
|
||||
SafeBrowsing: &filter.ConfigSafeBrowsing{
|
||||
@@ -155,7 +165,9 @@ func (x *Profile) toInternal(
|
||||
FilterConfig: fltConf,
|
||||
|
||||
Access: x.Access.toInternal(cons),
|
||||
AdultBlockingMode: adultBlockingMode,
|
||||
BlockingMode: m,
|
||||
SafeBrowsingBlockingMode: safeBrowsingBlockingMode,
|
||||
Ratelimiter: x.Ratelimiter.toInternal(respSzEst),
|
||||
|
||||
AccountID: agd.AccountID(x.AccountId),
|
||||
@@ -163,7 +175,7 @@ func (x *Profile) toInternal(
|
||||
|
||||
// Consider device IDs to have been prevalidated.
|
||||
DeviceIDs: container.NewMapSet(
|
||||
unsafelyConvertStrSlice[string, agd.DeviceID](x.DeviceIds)...,
|
||||
agdprotobuf.UnsafelyConvertStrSlice[string, agd.DeviceID](x.DeviceIds)...,
|
||||
),
|
||||
|
||||
// Consider rule-list IDs to have been prevalidated.
|
||||
@@ -224,6 +236,8 @@ func (x *DayInterval) toInternal() (i *filter.DayInterval) {
|
||||
|
||||
// blockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one.
|
||||
// TODO(d.kolyshev): DRY with adultBlockingModeToInternal and
|
||||
// safeBrowsingBlockingModeToInternal.
|
||||
func blockingModeToInternal(pbm isProfile_BlockingMode) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case *Profile_BlockingModeCustomIp:
|
||||
@@ -255,6 +269,80 @@ func blockingModeToInternal(pbm isProfile_BlockingMode) (m dnsmsg.BlockingMode,
|
||||
}
|
||||
}
|
||||
|
||||
// adultBlockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one.
|
||||
func adultBlockingModeToInternal(
|
||||
pbm isProfile_AdultBlockingMode,
|
||||
) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case *Profile_AdultBlockingModeCustomIp:
|
||||
var ipv4 []netip.Addr
|
||||
ipv4, err = agdprotobuf.ByteSlicesToIPs(pbm.AdultBlockingModeCustomIp.Ipv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v4 custom ips: %w", err)
|
||||
}
|
||||
|
||||
var ipv6 []netip.Addr
|
||||
ipv6, err = agdprotobuf.ByteSlicesToIPs(pbm.AdultBlockingModeCustomIp.Ipv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v6 custom ips: %w", err)
|
||||
}
|
||||
|
||||
return &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: ipv4,
|
||||
IPv6: ipv6,
|
||||
}, nil
|
||||
case *Profile_AdultBlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *Profile_AdultBlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *Profile_AdultBlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb adult blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// safeBrowsingBlockingModeToInternal converts a protobuf blocking-mode sum-type to an
|
||||
// internal one.
|
||||
func safeBrowsingBlockingModeToInternal(
|
||||
pbm isProfile_SafeBrowsingBlockingMode,
|
||||
) (m dnsmsg.BlockingMode, err error) {
|
||||
switch pbm := pbm.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case *Profile_SafeBrowsingBlockingModeCustomIp:
|
||||
var ipv4 []netip.Addr
|
||||
ipv4, err = agdprotobuf.ByteSlicesToIPs(pbm.SafeBrowsingBlockingModeCustomIp.Ipv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v4 custom ips: %w", err)
|
||||
}
|
||||
|
||||
var ipv6 []netip.Addr
|
||||
ipv6, err = agdprotobuf.ByteSlicesToIPs(pbm.SafeBrowsingBlockingModeCustomIp.Ipv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad v6 custom ips: %w", err)
|
||||
}
|
||||
|
||||
return &dnsmsg.BlockingModeCustomIP{
|
||||
IPv4: ipv4,
|
||||
IPv6: ipv6,
|
||||
}, nil
|
||||
case *Profile_SafeBrowsingBlockingModeNxdomain:
|
||||
return &dnsmsg.BlockingModeNXDOMAIN{}, nil
|
||||
case *Profile_SafeBrowsingBlockingModeNullIp:
|
||||
return &dnsmsg.BlockingModeNullIP{}, nil
|
||||
case *Profile_SafeBrowsingBlockingModeRefused:
|
||||
return &dnsmsg.BlockingModeREFUSED{}, nil
|
||||
default:
|
||||
// Consider unhandled type-switch cases programmer errors.
|
||||
return nil, fmt.Errorf("bad pb safe browsing blocking mode %T(%[1]v)", pbm)
|
||||
}
|
||||
}
|
||||
|
||||
// customDomainsToInternal converts protobuf custom-domain configurations to
|
||||
// internal ones.
|
||||
func customDomainsToInternal(
|
||||
@@ -479,11 +567,13 @@ func profileToProtobuf(p *agd.Profile) (pbProf *Profile) {
|
||||
CustomDomains: customDomainsToProtobuf(p.CustomDomains),
|
||||
FilterConfig: filterConfigToProtobuf(p.FilterConfig),
|
||||
Access: accessToProtobuf(p.Access.Config()),
|
||||
AdultBlockingMode: adultBlockingModeToProtobuf(p.AdultBlockingMode),
|
||||
BlockingMode: blockingModeToProtobuf(p.BlockingMode),
|
||||
SafeBrowsingBlockingMode: safeBrowsingBlockingModeToProtobuf(p.SafeBrowsingBlockingMode),
|
||||
Ratelimiter: ratelimiterToProtobuf(p.Ratelimiter.Config()),
|
||||
AccountId: string(p.AccountID),
|
||||
ProfileId: string(p.ID),
|
||||
DeviceIds: unsafelyConvertStrSlice[agd.DeviceID, string](
|
||||
DeviceIds: agdprotobuf.UnsafelyConvertStrSlice[agd.DeviceID, string](
|
||||
p.DeviceIDs.Values(),
|
||||
),
|
||||
FilteredResponseTtl: durationpb.New(p.FilteredResponseTTL),
|
||||
@@ -563,7 +653,7 @@ func customDomainConfigsToProtobuf(
|
||||
func filterConfigToProtobuf(c *filter.ConfigClient) (fc *FilterConfig) {
|
||||
var rules []string
|
||||
if c.Custom.Enabled {
|
||||
rules = unsafelyConvertStrSlice[filter.RuleText, string](c.Custom.Filter.Rules())
|
||||
rules = agdprotobuf.UnsafelyConvertStrSlice[filter.RuleText, string](c.Custom.Filter.Rules())
|
||||
}
|
||||
|
||||
return &FilterConfig{
|
||||
@@ -573,7 +663,7 @@ func filterConfigToProtobuf(c *filter.ConfigClient) (fc *FilterConfig) {
|
||||
},
|
||||
Parental: &FilterConfig_Parental{
|
||||
PauseSchedule: scheduleToProtobuf(c.Parental.PauseSchedule),
|
||||
BlockedServices: unsafelyConvertStrSlice[filter.BlockedServiceID, string](
|
||||
BlockedServices: agdprotobuf.UnsafelyConvertStrSlice[filter.BlockedServiceID, string](
|
||||
c.Parental.BlockedServices,
|
||||
),
|
||||
Enabled: c.Parental.Enabled,
|
||||
@@ -582,7 +672,7 @@ func filterConfigToProtobuf(c *filter.ConfigClient) (fc *FilterConfig) {
|
||||
SafeSearchYoutubeEnabled: c.Parental.SafeSearchYouTubeEnabled,
|
||||
},
|
||||
RuleList: &FilterConfig_RuleList{
|
||||
Ids: unsafelyConvertStrSlice[filter.ID, string](c.RuleList.IDs),
|
||||
Ids: agdprotobuf.UnsafelyConvertStrSlice[filter.ID, string](c.RuleList.IDs),
|
||||
Enabled: c.RuleList.Enabled,
|
||||
},
|
||||
SafeBrowsing: &FilterConfig_SafeBrowsing{
|
||||
@@ -669,6 +759,9 @@ func prefixesToProtobuf(nets []netip.Prefix) (cidrs []*CidrRange) {
|
||||
}
|
||||
|
||||
// blockingModeToProtobuf converts a blocking-mode sum-type to a protobuf one.
|
||||
//
|
||||
// TODO(d.kolyshev): DRY with adultBlockingModeToProtobuf and
|
||||
// safeBrowsingBlockingModeToProtobuf.
|
||||
func blockingModeToProtobuf(m dnsmsg.BlockingMode) (pbBlockingMode isProfile_BlockingMode) {
|
||||
switch m := m.(type) {
|
||||
case *dnsmsg.BlockingModeCustomIP:
|
||||
@@ -695,6 +788,70 @@ func blockingModeToProtobuf(m dnsmsg.BlockingMode) (pbBlockingMode isProfile_Blo
|
||||
}
|
||||
}
|
||||
|
||||
// adultBlockingModeToProtobuf converts a blocking-mode sum-type to a protobuf
|
||||
// one.
|
||||
func adultBlockingModeToProtobuf(
|
||||
m dnsmsg.BlockingMode,
|
||||
) (pbBlockingMode isProfile_AdultBlockingMode) {
|
||||
switch m := m.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *dnsmsg.BlockingModeCustomIP:
|
||||
return &Profile_AdultBlockingModeCustomIp{
|
||||
AdultBlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipsToByteSlices(m.IPv4),
|
||||
Ipv6: ipsToByteSlices(m.IPv6),
|
||||
},
|
||||
}
|
||||
case *dnsmsg.BlockingModeNXDOMAIN:
|
||||
return &Profile_AdultBlockingModeNxdomain{
|
||||
AdultBlockingModeNxdomain: &BlockingModeNXDOMAIN{},
|
||||
}
|
||||
case *dnsmsg.BlockingModeNullIP:
|
||||
return &Profile_AdultBlockingModeNullIp{
|
||||
AdultBlockingModeNullIp: &BlockingModeNullIP{},
|
||||
}
|
||||
case *dnsmsg.BlockingModeREFUSED:
|
||||
return &Profile_AdultBlockingModeRefused{
|
||||
AdultBlockingModeRefused: &BlockingModeREFUSED{},
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("bad adult blocking mode %T(%[1]v)", m))
|
||||
}
|
||||
}
|
||||
|
||||
// safeBrowsingBlockingModeToProtobuf converts a blocking-mode sum-type to a
|
||||
// protobuf one.
|
||||
func safeBrowsingBlockingModeToProtobuf(
|
||||
m dnsmsg.BlockingMode,
|
||||
) (pbBlockingMode isProfile_SafeBrowsingBlockingMode) {
|
||||
switch m := m.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *dnsmsg.BlockingModeCustomIP:
|
||||
return &Profile_SafeBrowsingBlockingModeCustomIp{
|
||||
SafeBrowsingBlockingModeCustomIp: &BlockingModeCustomIP{
|
||||
Ipv4: ipsToByteSlices(m.IPv4),
|
||||
Ipv6: ipsToByteSlices(m.IPv6),
|
||||
},
|
||||
}
|
||||
case *dnsmsg.BlockingModeNXDOMAIN:
|
||||
return &Profile_SafeBrowsingBlockingModeNxdomain{
|
||||
SafeBrowsingBlockingModeNxdomain: &BlockingModeNXDOMAIN{},
|
||||
}
|
||||
case *dnsmsg.BlockingModeNullIP:
|
||||
return &Profile_SafeBrowsingBlockingModeNullIp{
|
||||
SafeBrowsingBlockingModeNullIp: &BlockingModeNullIP{},
|
||||
}
|
||||
case *dnsmsg.BlockingModeREFUSED:
|
||||
return &Profile_SafeBrowsingBlockingModeRefused{
|
||||
SafeBrowsingBlockingModeRefused: &BlockingModeREFUSED{},
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("bad safe browsing blocking mode %T(%[1]v)", m))
|
||||
}
|
||||
}
|
||||
|
||||
// ipsToByteSlices is a wrapper around netip.Addr.MarshalBinary that ignores the
|
||||
// always-nil errors.
|
||||
func ipsToByteSlices(ips []netip.Addr) (data [][]byte) {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package filecachepb
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// unsafelyConvertStrSlice checks if []T1 can be converted to []T2 at compile
|
||||
// time and, if so, converts the slice using package unsafe.
|
||||
//
|
||||
// Slices resulting from this conversion must not be mutated.
|
||||
func unsafelyConvertStrSlice[T1, T2 ~string](s []T1) (res []T2) {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// #nosec G103 -- Conversion between two slices with the same underlying
|
||||
// element type is safe.
|
||||
return *(*[]T2)(unsafe.Pointer(&s))
|
||||
}
|
||||
@@ -13,7 +13,9 @@ import (
|
||||
// FileCacheVersion is the version of cached data structure. It must be
|
||||
// manually incremented on every change in [agd.Device], [agd.Profile], and any
|
||||
// file-cache structures.
|
||||
const FileCacheVersion = 18
|
||||
//
|
||||
// Please document the changes to this constant in the changelog.
|
||||
const FileCacheVersion = 19
|
||||
|
||||
// CacheVersionError is returned from [FileCacheStorage.Load] method if the
|
||||
// stored cache version doesn't match current [FileCacheVersion].
|
||||
|
||||
@@ -162,7 +162,9 @@ func NewProfile(tb testing.TB) (p *agd.Profile, d *agd.Device) {
|
||||
BlocklistDomainRules: []string{"block.test"},
|
||||
StandardEnabled: true,
|
||||
}),
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
Ratelimiter: agd.NewDefaultRatelimiter(&agd.RatelimitConfig{
|
||||
ClientSubnets: []netip.Prefix{netip.MustParsePrefix("5.5.5.0/24")},
|
||||
RPS: 100,
|
||||
|
||||
@@ -84,7 +84,9 @@ func newDefaultProfileDB(tb testing.TB, devices <-chan []*agd.Device) (db *profi
|
||||
return &profiledb.StorageProfilesResponse{
|
||||
Profiles: []*agd.Profile{{
|
||||
CustomDomains: &agd.AccountCustomDomains{},
|
||||
AdultBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
BlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
SafeBrowsingBlockingMode: &dnsmsg.BlockingModeNullIP{},
|
||||
ID: profiledbtest.ProfileID,
|
||||
DeviceIDs: devIDs,
|
||||
}},
|
||||
|
||||
@@ -70,11 +70,7 @@ func (db *testKV) get(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
// TODO(a.garipov): Consider making testutil.RequireTypeAssert accept
|
||||
// testutil.PanicT.
|
||||
require.IsType(pt, ([]byte)(nil), v)
|
||||
|
||||
val := v.([]byte)
|
||||
val := testutil.RequireTypeAssert[[]byte](pt, v)
|
||||
err := json.NewEncoder(rw).Encode([]*consulkv.KeyReadResponse{{
|
||||
Value: val,
|
||||
}})
|
||||
|
||||
@@ -2,52 +2,88 @@ package tlsconfig
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
// certPaths contains a certificate path and a key path.
|
||||
type certPaths struct {
|
||||
// certData is an internal representation of a certificate and its paths.
|
||||
type certData struct {
|
||||
cert *tls.Certificate
|
||||
|
||||
certPath string
|
||||
keyPath string
|
||||
// TODO(a.garipov, e.burkov): Think of a better approach to distinct
|
||||
|
||||
// TODO(a.garipov, e.burkov): Think of a better approach to distinguish
|
||||
// between default and custom certificates.
|
||||
isCustom bool
|
||||
}
|
||||
|
||||
// certIndex holds TLS certificates and their associated file paths. Each entry
|
||||
// in the slices corresponds to a certificate and its respective paths for the
|
||||
// certificate and key files. Using this struct allows us to reduce
|
||||
// allocations.
|
||||
type certIndex struct {
|
||||
// certs contains the list of TLS certificates. All elements must not be
|
||||
// nil.
|
||||
certs []*tls.Certificate
|
||||
|
||||
// paths contains corresponding file paths for certificate and key files.
|
||||
// All elements must not be nil.
|
||||
paths []*certPaths
|
||||
// bindData is a helper type to map IP prefixes to certificate names.
|
||||
//
|
||||
// TODO(e.burkov): Implement prefix comparison and use binary search.
|
||||
type bindData struct {
|
||||
pref netip.Prefix
|
||||
name agd.CertificateName
|
||||
}
|
||||
|
||||
// add saves the TLS certificate and its paths. Certificate paths must only be
|
||||
// added once, see [certStorage.contains]. cert and cp must not be nil.
|
||||
func (s *certIndex) add(cert *tls.Certificate, cp *certPaths) {
|
||||
s.certs = append(s.certs, cert)
|
||||
s.paths = append(s.paths, cp)
|
||||
// certIndex holds TLS certificates and their associated info.
|
||||
type certIndex struct {
|
||||
// certs maps certificate names to their information. Each entry
|
||||
// corresponds to a certificate name and its respective paths for the
|
||||
// certificate and key files.
|
||||
certs *sortedMap[agd.CertificateName, *certData]
|
||||
|
||||
// bound are the IP prefixes for the certificates. It must only contain
|
||||
// certificate names that are present in certs.
|
||||
bound []*bindData
|
||||
}
|
||||
|
||||
// newCertIndex returns a new properly initialized [certIndex].
|
||||
func newCertIndex() (s *certIndex) {
|
||||
return &certIndex{
|
||||
certs: newSortedMap[agd.CertificateName, *certData](),
|
||||
}
|
||||
}
|
||||
|
||||
// add saves the TLS certificate's data under its name. name must be unique,
|
||||
// see [certIndex.contains]. certData must not be nil.
|
||||
func (s *certIndex) add(name agd.CertificateName, certData *certData) {
|
||||
s.certs.set(name, certData)
|
||||
}
|
||||
|
||||
// bind binds the certificate to the given prefix. It returns false if the
|
||||
// binding already exists.
|
||||
func (s *certIndex) bind(name agd.CertificateName, pref netip.Prefix) (added bool) {
|
||||
if slices.ContainsFunc(s.bound, func(b *bindData) (found bool) {
|
||||
return b.name == name && b.pref == pref
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
|
||||
s.bound = append(s.bound, &bindData{
|
||||
pref: pref,
|
||||
name: name,
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// contains returns true if the TLS certificate has already been added using the
|
||||
// provided file paths. cp must not be nil.
|
||||
func (s *certIndex) contains(cp *certPaths) (ok bool) {
|
||||
return slices.ContainsFunc(s.paths, func(p *certPaths) (found bool) {
|
||||
return *cp == *p
|
||||
})
|
||||
// provided name.
|
||||
func (s *certIndex) contains(name agd.CertificateName) (ok bool) {
|
||||
_, ok = s.certs.get(name)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// count returns the number of saved TLS certificates.
|
||||
func (s *certIndex) count() (n int) {
|
||||
return len(s.certs)
|
||||
return s.certs.len()
|
||||
}
|
||||
|
||||
// certFor returns the TLS certificate for chi. chi must not be nil. cert must
|
||||
@@ -60,60 +96,75 @@ func (s *certIndex) count() (n int) {
|
||||
// TODO(a.garipov): Explore the above situation and consider fixes to allow
|
||||
// custom IP-only certs.
|
||||
func (s *certIndex) certFor(chi *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
|
||||
laddr := chi.Conn.LocalAddr()
|
||||
ip := netutil.NetAddrToAddrPort(laddr).Addr()
|
||||
if ip == (netip.Addr{}) {
|
||||
return nil, errors.Error("no local address")
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Reuse the slice to decrease allocations.
|
||||
var errs []error
|
||||
for _, c := range s.certs {
|
||||
err = chi.SupportsCertificate(c)
|
||||
for _, b := range s.bound {
|
||||
if !b.pref.Contains(ip) {
|
||||
continue
|
||||
}
|
||||
|
||||
certData, ok := s.certs.get(b.name)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("certificate %q: %w", b.name, errors.ErrNoValue))
|
||||
}
|
||||
|
||||
cert = certData.cert
|
||||
err = chi.SupportsCertificate(cert)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// rangeFn calls fn for each stored TLS certificate and its paths. fn must not
|
||||
// be nil. Neither cert nor cp must be modified.
|
||||
func (s *certIndex) rangeFn(fn func(cert *tls.Certificate, cp *certPaths) (cont bool)) {
|
||||
for i, p := range s.paths {
|
||||
if !fn(s.certs[i], p) {
|
||||
return nil, fmt.Errorf("no certificate found for %s", ip)
|
||||
}
|
||||
|
||||
// rangeFn calls fn for each stored TLS certificate and its data. fn must not
|
||||
// be nil and must not modify certData.
|
||||
func (s *certIndex) rangeFn(fn func(name agd.CertificateName, certData *certData) (cont bool)) {
|
||||
for name, cd := range s.certs.rangeFn {
|
||||
if !fn(name, cd) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove deletes the certificate from s. cp must not be nil.
|
||||
func (s *certIndex) remove(cp *certPaths) {
|
||||
i := slices.IndexFunc(s.paths, func(p *certPaths) (found bool) {
|
||||
return *cp == *p
|
||||
// remove deletes the certificate from s by name. name must be valid.
|
||||
func (s *certIndex) remove(name agd.CertificateName) {
|
||||
s.certs.del(name)
|
||||
s.bound = slices.DeleteFunc(s.bound, func(b *bindData) (found bool) {
|
||||
return b.name == name
|
||||
})
|
||||
if i == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
s.certs = slices.Delete(s.certs, i, i+1)
|
||||
s.paths = slices.Delete(s.paths, i, i+1)
|
||||
}
|
||||
|
||||
// stored returns the list of saved TLS certificates.
|
||||
// stored returns the saved TLS certificates. certs' values must not be
|
||||
// modified.
|
||||
func (s *certIndex) stored() (certs []*tls.Certificate) {
|
||||
return s.certs
|
||||
for _, cd := range s.certs.rangeFn {
|
||||
certs = append(certs, cd.cert)
|
||||
}
|
||||
|
||||
// update updates the certificate corresponding to the paths. cp and c must not
|
||||
// be nil.
|
||||
//
|
||||
// TODO(a.garipov): Think of a better way to do this that doesn't involve code
|
||||
// that looks like iterator invalidation.
|
||||
func (s *certIndex) update(cp *certPaths, c *tls.Certificate) (ok bool) {
|
||||
for i, p := range s.paths {
|
||||
if *cp == *p {
|
||||
s.certs[i] = c
|
||||
|
||||
return true
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
return false
|
||||
// update updates the certificate corresponding to name. It returns true if the
|
||||
// certificate was updated. name must be valid, c must not be nil.
|
||||
func (s *certIndex) update(name agd.CertificateName, c *tls.Certificate) (ok bool) {
|
||||
certData, ok := s.certs.get(name)
|
||||
if ok {
|
||||
certData.cert = c
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -3,90 +3,186 @@ package tlsconfig
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCertIndex(t *testing.T) {
|
||||
// Common domain names used for testing.
|
||||
const (
|
||||
domainA = "a.com"
|
||||
domainB = "b.org"
|
||||
testDomainName = "a.test"
|
||||
testDomainNameAlt = "b.test"
|
||||
testDomainNameUnknown = "unknown.test"
|
||||
)
|
||||
|
||||
// Common [agd.CertificateName]s used for testing.
|
||||
const (
|
||||
testCertName agd.CertificateName = "cert-a"
|
||||
testCertNameAlt agd.CertificateName = "cert-b"
|
||||
testCertNameUnknown agd.CertificateName = "cert-unknown"
|
||||
)
|
||||
|
||||
// Common [tls.Certificate]s used for testing.
|
||||
var (
|
||||
pathsDomainA = &certPaths{
|
||||
certPath: domainA + "_path",
|
||||
keyPath: domainA + "_path",
|
||||
testCert = &tls.Certificate{
|
||||
Leaf: &x509.Certificate{
|
||||
DNSNames: []string{testDomainName},
|
||||
Version: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
pathsDomainB = &certPaths{
|
||||
certPath: domainB + "_path",
|
||||
keyPath: domainB + "_path",
|
||||
}
|
||||
nonAddedPaths = &certPaths{
|
||||
certPath: "non_added_path",
|
||||
keyPath: "non_added_path",
|
||||
testCertAlt = &tls.Certificate{
|
||||
Leaf: &x509.Certificate{
|
||||
DNSNames: []string{testDomainNameAlt},
|
||||
Version: tls.VersionTLS13,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
certDomainA := &tls.Certificate{Leaf: &x509.Certificate{
|
||||
DNSNames: []string{domainA},
|
||||
Version: tls.VersionTLS13,
|
||||
}}
|
||||
|
||||
certDomainB := &tls.Certificate{Leaf: &x509.Certificate{
|
||||
DNSNames: []string{domainB},
|
||||
Version: tls.VersionTLS13,
|
||||
}}
|
||||
|
||||
certWithPaths := []struct {
|
||||
cert *tls.Certificate
|
||||
paths *certPaths
|
||||
}{{
|
||||
cert: certDomainA,
|
||||
paths: pathsDomainA,
|
||||
}, {
|
||||
cert: certDomainB,
|
||||
paths: pathsDomainB,
|
||||
}}
|
||||
|
||||
idx := &certIndex{}
|
||||
for _, cp := range certWithPaths {
|
||||
idx.add(cp.cert, cp.paths)
|
||||
func TestCertIndex(t *testing.T) {
|
||||
certs := map[agd.CertificateName]*certData{
|
||||
testCertName: {
|
||||
cert: testCert,
|
||||
certPath: testDomainName + "_path",
|
||||
keyPath: testDomainName + "_path",
|
||||
},
|
||||
testCertNameAlt: {
|
||||
cert: testCertAlt,
|
||||
certPath: testDomainNameAlt + "_path",
|
||||
keyPath: testDomainNameAlt + "_path",
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, idx.contains(pathsDomainA))
|
||||
idx := newCertIndex()
|
||||
for name, cd := range certs {
|
||||
idx.add(name, cd)
|
||||
}
|
||||
|
||||
copyPathsDomainsB := *pathsDomainB
|
||||
assert.True(t, idx.contains(©PathsDomainsB))
|
||||
assert.False(t, idx.contains(nonAddedPaths))
|
||||
assert.Equal(t, len(certWithPaths), idx.count())
|
||||
|
||||
got, err := idx.certFor(&tls.ClientHelloInfo{
|
||||
ServerName: domainA,
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
t.Run("contains", func(t *testing.T) {
|
||||
assert.True(t, idx.contains(testCertName))
|
||||
assert.True(t, idx.contains(testCertNameAlt))
|
||||
assert.False(t, idx.contains(testCertNameUnknown))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, certDomainA, got)
|
||||
|
||||
got, err = idx.certFor(&tls.ClientHelloInfo{
|
||||
ServerName: domainB,
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
t.Run("count", func(t *testing.T) {
|
||||
assert.Equal(t, len(certs), idx.count())
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, certDomainB, got)
|
||||
assert.Equal(t, []*tls.Certificate{certDomainA, certDomainB}, idx.stored())
|
||||
t.Run("stored", func(t *testing.T) {
|
||||
want := []*tls.Certificate{testCert, testCertAlt}
|
||||
|
||||
i := 0
|
||||
idx.rangeFn(func(c *tls.Certificate, cp *certPaths) (cont bool) {
|
||||
assert.Equal(t, certWithPaths[i].cert, c)
|
||||
assert.Equal(t, certWithPaths[i].paths, cp)
|
||||
assert.ElementsMatch(t, want, idx.stored())
|
||||
})
|
||||
|
||||
i++
|
||||
t.Run("rangeFn", func(t *testing.T) {
|
||||
n := 0
|
||||
idx.rangeFn(func(name agd.CertificateName, cd *certData) (cont bool) {
|
||||
require.Contains(t, certs, name)
|
||||
assert.Equal(t, cd, certs[name])
|
||||
|
||||
n++
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, len(certs), n)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCertIndex_CertFor(t *testing.T) {
|
||||
var (
|
||||
addr = netip.MustParseAddr("192.0.2.1")
|
||||
addrAlt = netip.MustParseAddr("192.0.2.2")
|
||||
addrUnknown = netip.MustParseAddr("192.0.2.3")
|
||||
)
|
||||
|
||||
certs := map[agd.CertificateName]struct {
|
||||
data *certData
|
||||
pref netip.Prefix
|
||||
}{
|
||||
testCertName: {
|
||||
data: &certData{
|
||||
cert: testCert,
|
||||
certPath: testDomainName + "_path",
|
||||
keyPath: testDomainName + "_path",
|
||||
},
|
||||
pref: netip.PrefixFrom(addr, 32),
|
||||
},
|
||||
testCertNameAlt: {
|
||||
data: &certData{
|
||||
cert: testCertAlt,
|
||||
certPath: testDomainNameAlt + "_path",
|
||||
keyPath: testDomainNameAlt + "_path",
|
||||
},
|
||||
pref: netip.PrefixFrom(addrAlt, 32),
|
||||
},
|
||||
}
|
||||
|
||||
idx := newCertIndex()
|
||||
for name, cd := range certs {
|
||||
idx.add(name, cd.data)
|
||||
|
||||
added := idx.bind(name, cd.pref)
|
||||
require.True(t, added)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
chi *tls.ClientHelloInfo
|
||||
wantCert *tls.Certificate
|
||||
wantErrMsg string
|
||||
name string
|
||||
}{{
|
||||
chi: &tls.ClientHelloInfo{
|
||||
ServerName: testDomainName,
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
Conn: NewLocalAddrConn(addr),
|
||||
},
|
||||
wantCert: testCert,
|
||||
wantErrMsg: "",
|
||||
name: "success",
|
||||
}, {
|
||||
chi: &tls.ClientHelloInfo{
|
||||
ServerName: testDomainNameAlt,
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
Conn: NewLocalAddrConn(addrAlt),
|
||||
},
|
||||
wantCert: testCertAlt,
|
||||
wantErrMsg: "",
|
||||
name: "success_alternative",
|
||||
}, {
|
||||
chi: &tls.ClientHelloInfo{
|
||||
ServerName: testDomainNameUnknown,
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
Conn: NewLocalAddrConn(addrUnknown),
|
||||
},
|
||||
wantCert: nil,
|
||||
wantErrMsg: "no certificate found for " + addrUnknown.String(),
|
||||
name: "fail_unknown",
|
||||
}, {
|
||||
chi: &tls.ClientHelloInfo{
|
||||
ServerName: testDomainNameUnknown,
|
||||
SupportedVersions: []uint16{tls.VersionTLS12},
|
||||
Conn: NewLocalAddrConn(addr),
|
||||
},
|
||||
wantCert: nil,
|
||||
wantErrMsg: "certificate is not valid for requested server name: " +
|
||||
"x509: certificate is valid for " + testDomainName +
|
||||
", not " + testDomainNameUnknown,
|
||||
name: "fail_server_name",
|
||||
}}
|
||||
|
||||
t.Run("certFor", func(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := idx.certFor(tc.chi)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
assert.Equal(t, tc.wantCert, got)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@@ -50,6 +51,8 @@ type CustomDomainDB struct {
|
||||
|
||||
cacheDir string
|
||||
|
||||
bindPrefixes []netip.Prefix
|
||||
|
||||
initRetryIvl time.Duration
|
||||
maxRetryIvl time.Duration
|
||||
}
|
||||
@@ -83,6 +86,10 @@ type CustomDomainDBConfig struct {
|
||||
// exist, it is created.
|
||||
CacheDirPath string
|
||||
|
||||
// BindPrefixes are the IP prefixes to bound to the custom-domain
|
||||
// certificates. All items must be valid.
|
||||
BindPrefixes []netip.Prefix
|
||||
|
||||
// InitialRetryIvl is the initial interval for retrying a failed cert after
|
||||
// a network or a ratelimiting error. It must be positive.
|
||||
InitialRetryIvl time.Duration
|
||||
@@ -115,6 +122,8 @@ func NewCustomDomainDB(c *CustomDomainDBConfig) (db *CustomDomainDB, err error)
|
||||
metrics: c.Metrics,
|
||||
strg: c.Storage,
|
||||
|
||||
bindPrefixes: c.BindPrefixes,
|
||||
|
||||
cacheDir: c.CacheDirPath,
|
||||
|
||||
initRetryIvl: c.InitialRetryIvl,
|
||||
@@ -190,7 +199,7 @@ func (db *CustomDomainDB) removeCertData(
|
||||
|
||||
var errs []error
|
||||
certPath, keyPath := db.cachePaths(certName)
|
||||
err := db.manager.Remove(ctx, certPath, keyPath, true)
|
||||
err := db.manager.Remove(ctx, certName)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("removing from manager: %w", err))
|
||||
}
|
||||
@@ -228,9 +237,9 @@ const (
|
||||
)
|
||||
|
||||
// cachePaths returns the cache paths for the given certificate name.
|
||||
func (db *CustomDomainDB) cachePaths(certName agd.CertificateName) (certPath, keyPath string) {
|
||||
certPath = filepath.Join(db.cacheDir, string(certName)+".crt.pem")
|
||||
keyPath = filepath.Join(db.cacheDir, string(certName)+".key.pem")
|
||||
func (db *CustomDomainDB) cachePaths(name agd.CertificateName) (certPath, keyPath string) {
|
||||
certPath = filepath.Join(db.cacheDir, string(name+".crt.pem"))
|
||||
keyPath = filepath.Join(db.cacheDir, string(name+".key.pem"))
|
||||
|
||||
return certPath, keyPath
|
||||
}
|
||||
@@ -414,11 +423,22 @@ func (db *CustomDomainDB) refreshCert(
|
||||
// Add to the manager in case this is an initial refresh.
|
||||
//
|
||||
// TODO(a.garipov): Consider splitting away the initial refresh logic.
|
||||
err = db.manager.Add(ctx, certPath, keyPath, true)
|
||||
err = db.manager.Add(ctx, &AddParams{
|
||||
Name: certName,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
IsCustom: true,
|
||||
})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("adding previous cert to manager: %w", err)
|
||||
}
|
||||
|
||||
err = db.bind(ctx, certName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -434,7 +454,7 @@ func (db *CustomDomainDB) refreshCert(
|
||||
|
||||
l.InfoContext(ctx, "got data", "cert_len", len(certData), "key_len", len(keyData))
|
||||
|
||||
err = db.saveCertData(ctx, l, certPath, certData, keyPath, keyData)
|
||||
err = db.saveCertData(ctx, l, certName, certPath, certData, keyPath, keyData)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("saving cert %q: %w", certName, err)
|
||||
}
|
||||
@@ -450,6 +470,7 @@ func (db *CustomDomainDB) refreshCert(
|
||||
func (db *CustomDomainDB) saveCertData(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
certName agd.CertificateName,
|
||||
certPath string,
|
||||
certData []byte,
|
||||
keyPath string,
|
||||
@@ -479,11 +500,22 @@ func (db *CustomDomainDB) saveCertData(
|
||||
|
||||
l.DebugContext(ctx, "saved key file", "path", keyPath)
|
||||
|
||||
err = db.manager.Add(ctx, certPath, keyPath, true)
|
||||
err = db.manager.Add(ctx, &AddParams{
|
||||
Name: certName,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
IsCustom: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding to manager: %w", err)
|
||||
}
|
||||
|
||||
err = db.bind(ctx, certName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -627,3 +659,21 @@ func (db *CustomDomainDB) performRetries(
|
||||
errcoll.Collect(ctx, db.errColl, db.logger, "retrying certs", err)
|
||||
}
|
||||
}
|
||||
|
||||
// bind binds the certificate with certName name to the configured prefixes.
|
||||
func (db *CustomDomainDB) bind(ctx context.Context, certName agd.CertificateName) (err error) {
|
||||
var errs []error
|
||||
for _, pref := range db.bindPrefixes {
|
||||
err = db.manager.Bind(ctx, certName, pref)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("binding cert %q: %w", certName, err))
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.Join(errs...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("binding cert %q: %w", certName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/pem"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -26,9 +27,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testCertName is the common certificate name for tests.
|
||||
const testCertName agd.CertificateName = "cert1234"
|
||||
|
||||
// testProfileID is the common profile ID for tests.
|
||||
const testProfileID agd.ProfileID = "prof1234"
|
||||
|
||||
@@ -53,6 +51,12 @@ var testDomains = []string{
|
||||
testWildcard,
|
||||
}
|
||||
|
||||
// testBindPrefixes are the common bind prefixes for tests.
|
||||
var testBindPrefixes = []netip.Prefix{
|
||||
netip.MustParsePrefix("127.0.0.1/32"),
|
||||
netip.MustParsePrefix("::1/128"),
|
||||
}
|
||||
|
||||
// Time values for tests.
|
||||
var (
|
||||
testTimeExpired = testTimeNow.Add(-1 * timeutil.Day)
|
||||
@@ -65,28 +69,28 @@ var (
|
||||
testStateDisabled = &agd.CustomDomainStateCurrent{
|
||||
NotBefore: testTimeNow.Add(-1 * timeutil.Day),
|
||||
NotAfter: testTimeNow.Add(1 * timeutil.Day),
|
||||
CertName: testCertName,
|
||||
CertName: testCustomCertName,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
testStateExpired = &agd.CustomDomainStateCurrent{
|
||||
NotBefore: testTimeExpired.Add(-1 * timeutil.Day),
|
||||
NotAfter: testTimeExpired,
|
||||
CertName: testCertName,
|
||||
CertName: testCustomCertName,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
testStateFuture = &agd.CustomDomainStateCurrent{
|
||||
NotBefore: testTimeFuture.Add(-1 * timeutil.Day),
|
||||
NotAfter: testTimeFuture,
|
||||
CertName: testCertName,
|
||||
CertName: testCustomCertName,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
testStateOK = &agd.CustomDomainStateCurrent{
|
||||
NotBefore: testTimeNow.Add(-1 * timeutil.Day),
|
||||
NotAfter: testTimeNow.Add(1 * timeutil.Day),
|
||||
CertName: testCertName,
|
||||
CertName: testCustomCertName,
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
@@ -296,6 +300,7 @@ func TestCustomDomainDB_AddCertificate(t *testing.T) {
|
||||
|
||||
db := newCustomDomainDB(t, &tlsconfig.CustomDomainDBConfig{
|
||||
Clock: clock,
|
||||
BindPrefixes: testBindPrefixes,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@@ -488,18 +493,32 @@ func (s *testCustomDomainStorage) CertificateData(
|
||||
|
||||
// testManager is the [tlsconfig.Manager] for tests.
|
||||
type testManager struct {
|
||||
onAdd func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error)
|
||||
onAdd func(ctx context.Context, params *tlsconfig.AddParams) (err error)
|
||||
onBind func(
|
||||
ctx context.Context,
|
||||
name agd.CertificateName,
|
||||
pref netip.Prefix,
|
||||
) (err error)
|
||||
onClone func() (c *tls.Config)
|
||||
onCloneWithMetrics func(proto, srvName string, deviceDomains []string) (c *tls.Config)
|
||||
onRemove func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error)
|
||||
onRemove func(ctx context.Context, name agd.CertificateName) (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ tlsconfig.Manager = (*testManager)(nil)
|
||||
|
||||
// Add implements the [tlsconfig.Manager] interface for *testManager.
|
||||
func (m *testManager) Add(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
return m.onAdd(ctx, certPath, keyPath, isCustom)
|
||||
func (m *testManager) Add(ctx context.Context, params *tlsconfig.AddParams) (err error) {
|
||||
return m.onAdd(ctx, params)
|
||||
}
|
||||
|
||||
// Bind implements the [tlsconfig.Manager] interface for *testManager.
|
||||
func (m *testManager) Bind(
|
||||
ctx context.Context,
|
||||
name agd.CertificateName,
|
||||
pref netip.Prefix,
|
||||
) (err error) {
|
||||
return m.onBind(ctx, name, pref)
|
||||
}
|
||||
|
||||
// Clone implements the [tlsconfig.Manager] interface for *testManager.
|
||||
@@ -518,15 +537,18 @@ func (m *testManager) CloneWithMetrics(
|
||||
}
|
||||
|
||||
// Remove implements the [tlsconfig.Manager] interface for *testManager.
|
||||
func (m *testManager) Remove(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
return m.onRemove(ctx, certPath, keyPath, isCustom)
|
||||
func (m *testManager) Remove(ctx context.Context, name agd.CertificateName) (err error) {
|
||||
return m.onRemove(ctx, name)
|
||||
}
|
||||
|
||||
// newTestManager returns a new *testManager all methods of which panic.
|
||||
func newTestManager() (m *testManager) {
|
||||
return &testManager{
|
||||
onAdd: func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
panic(testutil.UnexpectedCall(ctx, certPath, keyPath, isCustom))
|
||||
onAdd: func(ctx context.Context, params *tlsconfig.AddParams) (err error) {
|
||||
panic(testutil.UnexpectedCall(ctx, params))
|
||||
},
|
||||
onBind: func(ctx context.Context, name agd.CertificateName, pref netip.Prefix) (err error) {
|
||||
panic(testutil.UnexpectedCall(ctx, name, pref))
|
||||
},
|
||||
onClone: func() (c *tls.Config) {
|
||||
panic(testutil.UnexpectedCall())
|
||||
@@ -534,8 +556,8 @@ func newTestManager() (m *testManager) {
|
||||
onCloneWithMetrics: func(proto, srvName string, deviceDomains []string) (c *tls.Config) {
|
||||
panic(testutil.UnexpectedCall(proto, srvName, deviceDomains))
|
||||
},
|
||||
onRemove: func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
panic(testutil.UnexpectedCall(ctx, certPath, keyPath, isCustom))
|
||||
onRemove: func(ctx context.Context, name agd.CertificateName) (err error) {
|
||||
panic(testutil.UnexpectedCall(ctx, name))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -580,7 +602,7 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
ctx context.Context,
|
||||
certName agd.CertificateName,
|
||||
) (cert, key []byte, err error) {
|
||||
assert.Equal(t, testCertName, certName)
|
||||
assert.Equal(t, testCustomCertName, certName)
|
||||
|
||||
certDER, rsaKey := newCertAndKey(t, 1)
|
||||
|
||||
@@ -592,19 +614,23 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
wantCertPath, wantKeyPath := newCertAndKeyPaths(cacheDir)
|
||||
|
||||
mgrWithAdd := newTestManager()
|
||||
mgrWithAdd.onAdd = func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
assert.Equal(t, wantCertPath, certPath)
|
||||
assert.Equal(t, wantKeyPath, keyPath)
|
||||
assert.True(t, isCustom)
|
||||
mgrWithAdd.onAdd = func(ctx context.Context, params *tlsconfig.AddParams) (err error) {
|
||||
assert.Equal(t, wantCertPath, params.CertPath)
|
||||
assert.Equal(t, wantKeyPath, params.KeyPath)
|
||||
assert.True(t, params.IsCustom)
|
||||
|
||||
return nil
|
||||
}
|
||||
mgrWithAdd.onBind = func(ctx context.Context, n agd.CertificateName, p netip.Prefix) (err error) {
|
||||
assert.Equal(t, testCustomCertName, n)
|
||||
assert.Contains(t, testBindPrefixes, p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
mgrWithRemove := newTestManager()
|
||||
mgrWithRemove.onRemove = func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
assert.Equal(t, wantCertPath, certPath)
|
||||
assert.Equal(t, wantKeyPath, keyPath)
|
||||
assert.True(t, isCustom)
|
||||
mgrWithRemove.onRemove = func(ctx context.Context, name agd.CertificateName) (err error) {
|
||||
assert.Equal(t, testCustomCertName, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -614,6 +640,7 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
Manager: mgrWithAdd,
|
||||
Storage: strg,
|
||||
CacheDirPath: cacheDir,
|
||||
BindPrefixes: testBindPrefixes,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@@ -632,6 +659,7 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
Manager: mgrWithAdd,
|
||||
Storage: strg,
|
||||
CacheDirPath: cacheDir,
|
||||
BindPrefixes: testBindPrefixes,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@@ -650,6 +678,7 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
Manager: mgrWithRemove,
|
||||
Storage: strg,
|
||||
CacheDirPath: cacheDir,
|
||||
BindPrefixes: testBindPrefixes,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
@@ -668,8 +697,8 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
|
||||
// newCertAndKeyPaths is a helper that returns paths for the certificate and
|
||||
// the key using the test's temporary directory.
|
||||
func newCertAndKeyPaths(cacheDir string) (certPath, keyPath string) {
|
||||
return filepath.Join(cacheDir, string(testCertName)+tlsconfig.CustomDomainCertExt),
|
||||
filepath.Join(cacheDir, string(testCertName)+tlsconfig.CustomDomainKeyExt)
|
||||
return filepath.Join(cacheDir, string(testCustomCertName)+tlsconfig.CustomDomainCertExt),
|
||||
filepath.Join(cacheDir, string(testCustomCertName)+tlsconfig.CustomDomainKeyExt)
|
||||
}
|
||||
|
||||
func TestCustomDomainDB_Refresh_retry(t *testing.T) {
|
||||
@@ -679,17 +708,22 @@ func TestCustomDomainDB_Refresh_retry(t *testing.T) {
|
||||
wantCertPath, wantKeyPath := newCertAndKeyPaths(cacheDir)
|
||||
|
||||
mgr := newTestManager()
|
||||
mgr.onAdd = func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
assert.Equal(t, wantCertPath, certPath)
|
||||
assert.Equal(t, wantKeyPath, keyPath)
|
||||
assert.True(t, isCustom)
|
||||
mgr.onAdd = func(ctx context.Context, params *tlsconfig.AddParams) (err error) {
|
||||
assert.Equal(t, testCustomCertName, params.Name)
|
||||
assert.Equal(t, wantCertPath, params.CertPath)
|
||||
assert.Equal(t, wantKeyPath, params.KeyPath)
|
||||
assert.True(t, params.IsCustom)
|
||||
|
||||
return nil
|
||||
}
|
||||
mgr.onRemove = func(ctx context.Context, certPath, keyPath string, isCustom bool) (err error) {
|
||||
assert.Equal(t, wantCertPath, certPath)
|
||||
assert.Equal(t, wantKeyPath, keyPath)
|
||||
assert.True(t, isCustom)
|
||||
mgr.onRemove = func(ctx context.Context, name agd.CertificateName) (err error) {
|
||||
assert.Equal(t, testCustomCertName, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
mgr.onBind = func(ctx context.Context, name agd.CertificateName, p netip.Prefix) (err error) {
|
||||
assert.Equal(t, testCustomCertName, name)
|
||||
assert.Contains(t, testBindPrefixes, p)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -709,7 +743,7 @@ func TestCustomDomainDB_Refresh_retry(t *testing.T) {
|
||||
return nil, nil, strgErr
|
||||
}
|
||||
|
||||
assert.Equal(t, testCertName, certName)
|
||||
assert.Equal(t, testCustomCertName, certName)
|
||||
|
||||
certDER, rsaKey := newCertAndKey(t, 1)
|
||||
|
||||
@@ -728,6 +762,7 @@ func TestCustomDomainDB_Refresh_retry(t *testing.T) {
|
||||
ErrColl: &agdtest.ErrorCollector{
|
||||
OnCollect: func(_ context.Context, err error) {},
|
||||
},
|
||||
BindPrefixes: testBindPrefixes,
|
||||
})
|
||||
|
||||
require.True(t, t.Run("rate_limited", func(t *testing.T) {
|
||||
@@ -801,12 +836,13 @@ func TestCustomDomainDB_Refresh_present(t *testing.T) {
|
||||
ctx context.Context,
|
||||
certName agd.CertificateName,
|
||||
) (cert, key []byte, err error) {
|
||||
assert.Equal(t, testCertName, certName)
|
||||
assert.Equal(t, testCustomCertName, certName)
|
||||
|
||||
return certDER, x509.MarshalPKCS1PrivateKey(rsaKey), nil
|
||||
},
|
||||
},
|
||||
CacheDirPath: cacheDir,
|
||||
BindPrefixes: testBindPrefixes,
|
||||
}
|
||||
|
||||
require.True(t, t.Run("both_present", func(t *testing.T) {
|
||||
|
||||
369
internal/tlsconfig/defaultmanager.go
Normal file
369
internal/tlsconfig/defaultmanager.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
)
|
||||
|
||||
// DefaultManagerConfig is the configuration structure for [DefaultManager].
|
||||
type DefaultManagerConfig struct {
|
||||
// Logger is used for logging the operation of the TLS manager. It must not
|
||||
// be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// ErrColl is used to collect TLS-related errors. It must not be nil.
|
||||
ErrColl errcoll.Interface
|
||||
|
||||
// Metrics is used to collect TLS-related statistics. It must not be nil.
|
||||
//
|
||||
// TODO(a.garipov): See if the custom-domain certificates need any metrics.
|
||||
Metrics ManagerMetrics
|
||||
|
||||
// TicketDB stores paths to the TLS session tickets and updates them. It
|
||||
// must not be nil.
|
||||
TicketDB TicketDB
|
||||
|
||||
// KeyLogPath, if not empty, is the path to the TLS key log file. If not
|
||||
// empty, KeyLogPath should be a valid file path.
|
||||
KeyLogPath string
|
||||
}
|
||||
|
||||
// DefaultManager is the default implementation of [Manager].
|
||||
type DefaultManager struct {
|
||||
// mu protects fields certStorage, clones, clonesWithMetrics,
|
||||
// sessTicketPaths.
|
||||
mu *sync.Mutex
|
||||
logger *slog.Logger
|
||||
errColl errcoll.Interface
|
||||
metrics ManagerMetrics
|
||||
tickDB TicketDB
|
||||
certStorage *certIndex
|
||||
original *tls.Config
|
||||
clones []*tls.Config
|
||||
clonesWithMetrics []*tls.Config
|
||||
}
|
||||
|
||||
// NewDefaultManager returns a new initialized *DefaultManager. c must not be
|
||||
// nil and must be valid.
|
||||
func NewDefaultManager(c *DefaultManagerConfig) (m *DefaultManager, err error) {
|
||||
var keyLogWriter io.Writer
|
||||
|
||||
if keyLogFilePath := c.KeyLogPath; keyLogFilePath != "" {
|
||||
keyLogWriter, err = tlsKeyLogWriter(keyLogFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing tls key log writer: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
m = &DefaultManager{
|
||||
mu: &sync.Mutex{},
|
||||
logger: c.Logger,
|
||||
errColl: c.ErrColl,
|
||||
metrics: c.Metrics,
|
||||
tickDB: c.TicketDB,
|
||||
certStorage: newCertIndex(),
|
||||
}
|
||||
|
||||
m.original = &tls.Config{
|
||||
GetCertificate: m.getCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
KeyLogWriter: keyLogWriter,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Manager = (*DefaultManager)(nil)
|
||||
|
||||
// Add implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Add(ctx context.Context, params *AddParams) (err error) {
|
||||
l := m.logger.With(
|
||||
"cert", params.CertPath,
|
||||
"key", params.KeyPath,
|
||||
"is_custom", params.IsCustom,
|
||||
)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.certStorage.contains(params.Name) {
|
||||
l.InfoContext(ctx, "skipping already added certificate")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, err := m.load(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding certificate: %w", err)
|
||||
}
|
||||
|
||||
m.certStorage.add(params.Name, &certData{
|
||||
cert: cert,
|
||||
certPath: params.CertPath,
|
||||
keyPath: params.KeyPath,
|
||||
isCustom: params.IsCustom,
|
||||
})
|
||||
|
||||
l.InfoContext(ctx, "added certificate")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Bind(
|
||||
ctx context.Context,
|
||||
name agd.CertificateName,
|
||||
pref netip.Prefix,
|
||||
) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
added := m.certStorage.bind(name, pref)
|
||||
if !added {
|
||||
m.logger.InfoContext(ctx, "skipping existing binding", "cert", name, "pref", pref)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// load returns a new TLS configuration from the provided certificate and key
|
||||
// paths. m.mu must be locked. c must not be modified.
|
||||
func (m *DefaultManager) load(ctx context.Context, p *AddParams) (c *tls.Certificate, err error) {
|
||||
cert, err := tls.LoadX509KeyPair(p.CertPath, p.KeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading certificate: %w", err)
|
||||
}
|
||||
|
||||
if !p.IsCustom {
|
||||
authAlgo := cert.Leaf.PublicKeyAlgorithm.String()
|
||||
subj := cert.Leaf.Subject.String()
|
||||
m.metrics.SetCertificateInfo(ctx, authAlgo, subj, cert.Leaf.NotAfter)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// Clone implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Clone() (clone *tls.Config) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clone = m.original.Clone()
|
||||
m.clones = append(m.clones, clone)
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// getCertificate returns the TLS certificate for chi. See
|
||||
// [tls.Config.GetCertificate]. c must not be modified.
|
||||
func (m *DefaultManager) getCertificate(chi *tls.ClientHelloInfo) (c *tls.Certificate, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.certStorage.count() == 0 {
|
||||
return nil, errors.Error("no certificates")
|
||||
}
|
||||
|
||||
return m.certStorage.certFor(chi)
|
||||
}
|
||||
|
||||
// CloneWithMetrics implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) CloneWithMetrics(
|
||||
proto string,
|
||||
srvName string,
|
||||
deviceDomains []string,
|
||||
) (conf *tls.Config) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clone := m.original.Clone()
|
||||
|
||||
clone.GetConfigForClient = m.metrics.BeforeHandshake(proto)
|
||||
|
||||
clone.GetCertificate = m.getCertificate
|
||||
|
||||
clone.VerifyConnection = m.metrics.AfterHandshake(
|
||||
proto,
|
||||
srvName,
|
||||
deviceDomains,
|
||||
m.certStorage.stored(),
|
||||
)
|
||||
|
||||
m.clonesWithMetrics = append(m.clonesWithMetrics, clone)
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ service.Refresher = (*DefaultManager)(nil)
|
||||
|
||||
// Refresh implements the [service.Refresher] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Refresh(ctx context.Context) (err error) {
|
||||
m.logger.DebugContext(ctx, "refresh started")
|
||||
defer m.logger.DebugContext(ctx, "refresh finished")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "certificate refresh failed", err)
|
||||
}
|
||||
}()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
m.certStorage.rangeFn(func(name agd.CertificateName, cd *certData) (cont bool) {
|
||||
cert, loadErr := m.load(ctx, &AddParams{
|
||||
Name: name,
|
||||
CertPath: cd.certPath,
|
||||
KeyPath: cd.keyPath,
|
||||
IsCustom: cd.isCustom,
|
||||
})
|
||||
if loadErr != nil {
|
||||
errs = append(errs, loadErr)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
msg, lvl := "refreshed certificate", slog.LevelInfo
|
||||
if !m.certStorage.update(name, cert) {
|
||||
msg, lvl = "certificate did not refresh", slog.LevelWarn
|
||||
}
|
||||
|
||||
m.logger.Log(ctx, lvl, msg, "name", name, "cert", cd.certPath, "key", cd.keyPath)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
err = errors.Join(errs...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refreshing tls certificates: %w", err)
|
||||
}
|
||||
|
||||
m.logger.InfoContext(ctx, "refresh successful", "num_configs", m.certStorage.count())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a certificate from the manager. certPath and keyPath must not
|
||||
// be empty.
|
||||
func (m *DefaultManager) Remove(
|
||||
ctx context.Context,
|
||||
name agd.CertificateName,
|
||||
) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.certStorage.remove(name)
|
||||
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"removed certificate",
|
||||
"name", name,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RotateTickets refreshes and resets TLS session tickets. It may be used as a
|
||||
// [service.RefresherFunc].
|
||||
func (m *DefaultManager) RotateTickets(ctx context.Context) (err error) {
|
||||
m.logger.DebugContext(ctx, "ticket rotation started")
|
||||
defer m.logger.DebugContext(ctx, "ticket rotation finished")
|
||||
|
||||
paths, err := m.tickDB.Paths(ctx)
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "rotating tickets", err)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
m.metrics.SetSessionTicketRotationStatus(ctx, err)
|
||||
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "ticket rotation failed", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tickets := make([]SessionTicket, 0, len(paths))
|
||||
for _, filePath := range paths {
|
||||
var ticket SessionTicket
|
||||
ticket, err = readSessionTicketFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading session ticket: %w", err)
|
||||
}
|
||||
|
||||
tickets = append(tickets, ticket)
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, conf := range m.clones {
|
||||
conf.SetSessionTicketKeys(tickets)
|
||||
}
|
||||
|
||||
for _, conf := range m.clonesWithMetrics {
|
||||
conf.SetSessionTicketKeys(tickets)
|
||||
}
|
||||
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"ticket rotation successful",
|
||||
"num_configs", m.certStorage.count(),
|
||||
"num_tickets", len(tickets),
|
||||
"num_clones", len(m.clones),
|
||||
"num_clones_with_metrics", len(m.clonesWithMetrics),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readSessionTicketFile reads a single TLS session ticket from a file.
|
||||
func readSessionTicketFile(filePath string) (ticket SessionTicket, err error) {
|
||||
// #nosec G304 -- Trust the file paths that are given to us in the
|
||||
// configuration file.
|
||||
b, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return SessionTicket{}, fmt.Errorf("reading session ticket: %w", err)
|
||||
}
|
||||
|
||||
ticket, err = NewSessionTicket(b)
|
||||
if err != nil {
|
||||
return SessionTicket{}, fmt.Errorf("session ticket in %q: %w", filePath, err)
|
||||
}
|
||||
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
// tlsKeyLogWriter returns a writer for logging TLS secrets to file at
|
||||
// keyLogPath.
|
||||
func tlsKeyLogWriter(keyLogPath string) (kl io.Writer, err error) {
|
||||
path := filepath.Clean(keyLogPath)
|
||||
|
||||
// TODO(a.garipov): Consider closing the file when we add SIGHUP support.
|
||||
kl, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kl, nil
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"cmp"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
|
||||
@@ -16,6 +18,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testCertName is the name of the certificate used for tests.
|
||||
const testCertName agd.CertificateName = "test-cert"
|
||||
|
||||
// testCustomCertName is the name of the custom certificate used for tests.
|
||||
const testCustomCertName agd.CertificateName = "test-custom-cert"
|
||||
|
||||
// writeSesionKey is a helper function that writes generated session key to
|
||||
// specified path.
|
||||
func writeSessionKey(tb testing.TB, sessKeyPath string) {
|
||||
@@ -39,11 +47,12 @@ func writeSessionKey(tb testing.TB, sessKeyPath string) {
|
||||
|
||||
// assertCertSerialNumber is a helper function that checks serial number of the
|
||||
// TLS certificate.
|
||||
func assertCertSerialNumber(tb testing.TB, conf *tls.Config, wantSN int64) {
|
||||
func assertCertSerialNumber(tb testing.TB, conf *tls.Config, wantSN int64, laddr netip.Addr) {
|
||||
tb.Helper()
|
||||
|
||||
cert, err := conf.GetCertificate(&tls.ClientHelloInfo{
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
Conn: tlsconfig.NewLocalAddrConn(laddr),
|
||||
})
|
||||
require.NoError(tb, err)
|
||||
|
||||
@@ -88,14 +97,25 @@ func TestDefaultManager_Refresh(t *testing.T) {
|
||||
writeCertAndKey(t, certDER, certPath, key, keyPath)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := m.Add(ctx, certPath, keyPath, false)
|
||||
err := m.Add(ctx, &tlsconfig.AddParams{
|
||||
Name: testCertName,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
IsCustom: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ip := netip.MustParseAddr("192.0.2.1")
|
||||
subnet := netip.PrefixFrom(ip, 16)
|
||||
|
||||
err = m.Bind(ctx, testCertName, subnet)
|
||||
require.NoError(t, err)
|
||||
|
||||
conf := m.Clone()
|
||||
confWithMetrics := m.CloneWithMetrics("", "", nil)
|
||||
|
||||
assertCertSerialNumber(t, conf, snBefore)
|
||||
assertCertSerialNumber(t, confWithMetrics, snBefore)
|
||||
assertCertSerialNumber(t, conf, snBefore, ip)
|
||||
assertCertSerialNumber(t, confWithMetrics, snBefore, ip)
|
||||
|
||||
certDER, key = newCertAndKey(t, snAfter)
|
||||
writeCertAndKey(t, certDER, certPath, key, keyPath)
|
||||
@@ -103,8 +123,8 @@ func TestDefaultManager_Refresh(t *testing.T) {
|
||||
err = m.Refresh(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertCertSerialNumber(t, conf, snAfter)
|
||||
assertCertSerialNumber(t, confWithMetrics, snAfter)
|
||||
assertCertSerialNumber(t, conf, snAfter, ip)
|
||||
assertCertSerialNumber(t, confWithMetrics, snAfter, ip)
|
||||
}
|
||||
|
||||
func TestDefaultManager_Remove(t *testing.T) {
|
||||
@@ -121,11 +141,23 @@ func TestDefaultManager_Remove(t *testing.T) {
|
||||
m := newManager(t, nil)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := m.Add(ctx, certPath, keyPath, false)
|
||||
err := m.Add(ctx, &tlsconfig.AddParams{
|
||||
Name: testCertName,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
IsCustom: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
addr := netip.MustParseAddr("192.0.2.1")
|
||||
|
||||
subnet := netip.PrefixFrom(addr, 16)
|
||||
err = m.Bind(ctx, testCertName, subnet)
|
||||
require.NoError(t, err)
|
||||
|
||||
chi := &tls.ClientHelloInfo{
|
||||
SupportedVersions: []uint16{tls.VersionTLS13},
|
||||
Conn: tlsconfig.NewLocalAddrConn(addr),
|
||||
}
|
||||
|
||||
c := m.Clone()
|
||||
@@ -133,7 +165,7 @@ func TestDefaultManager_Remove(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||
err = m.Remove(ctx, certPath, keyPath, false)
|
||||
err = m.Remove(ctx, testCertName)
|
||||
require.NoError(t, err)
|
||||
|
||||
c = m.Clone()
|
||||
@@ -162,7 +194,12 @@ func TestDefaultManager_RotateTickets(t *testing.T) {
|
||||
writeCertAndKey(t, certDER, certPath, key, keyPath)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
err := m.Add(ctx, certPath, keyPath, false)
|
||||
err := m.Add(ctx, &tlsconfig.AddParams{
|
||||
Name: testCertName,
|
||||
CertPath: certPath,
|
||||
KeyPath: keyPath,
|
||||
IsCustom: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = m.RotateTickets(ctx)
|
||||
@@ -3,28 +3,40 @@ package tlsconfig
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/service"
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
|
||||
)
|
||||
|
||||
// AddParams are the parameters for [Manager.Add].
|
||||
type AddParams struct {
|
||||
// Name is the name of the certificate to add. It must be valid.
|
||||
Name agd.CertificateName
|
||||
|
||||
// CertPath is the path to the certificate. It must be a valid existing
|
||||
// filesystem path.
|
||||
CertPath string
|
||||
|
||||
// KeyPath is the path to the key. It must be a valid existing filesystem
|
||||
// path.
|
||||
KeyPath string
|
||||
|
||||
// IsCustom defines if the certificate belongs to a custom domain. If true,
|
||||
// the certificate's data should not be reported in metrics.
|
||||
IsCustom bool
|
||||
}
|
||||
|
||||
// Manager stores and updates TLS configurations.
|
||||
type Manager interface {
|
||||
// Add saves an initialized TLS certificate using the provided paths to a
|
||||
// certificate and a key. certPath and keyPath must not be empty.
|
||||
//
|
||||
// If isCustom is true, the certificate's data should not be reported in
|
||||
// metrics.
|
||||
// Add saves an initialized TLS certificate using the provided params.
|
||||
//
|
||||
// Add must ignore duplicates.
|
||||
Add(ctx context.Context, certPath, keyPath string, isCustom bool) (err error)
|
||||
Add(ctx context.Context, params *AddParams) (err error)
|
||||
|
||||
// Bind binds the certificate to the given prefix.
|
||||
//
|
||||
// Bind must ignore duplicating name-prefix combinations.
|
||||
Bind(ctx context.Context, name agd.CertificateName, prefix netip.Prefix) (err error)
|
||||
|
||||
// Clone returns the TLS configuration that contains saved TLS certificates.
|
||||
Clone() (c *tls.Config)
|
||||
@@ -32,12 +44,8 @@ type Manager interface {
|
||||
// CloneWithMetrics is like [Manager.Clone] but it also sets metrics.
|
||||
CloneWithMetrics(proto, srvName string, deviceDomains []string) (c *tls.Config)
|
||||
|
||||
// Remove deletes a certificate using the provided paths to its certificate
|
||||
// and key. certPath and keyPath must not be empty.
|
||||
//
|
||||
// If isCustom is true, the certificate's data should not be reported in
|
||||
// metrics.
|
||||
Remove(ctx context.Context, certPath, keyPath string, isCustom bool) (err error)
|
||||
// Remove deletes a custom certificate by its name. name must be valid.
|
||||
Remove(ctx context.Context, name agd.CertificateName) (err error)
|
||||
}
|
||||
|
||||
// EmptyManager is the implementation of the [Manager] interface that does
|
||||
@@ -48,7 +56,12 @@ type EmptyManager struct{}
|
||||
var _ Manager = EmptyManager{}
|
||||
|
||||
// Add implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Add(_ context.Context, _, _ string, _ bool) (err error) { return nil }
|
||||
func (EmptyManager) Add(_ context.Context, _ *AddParams) (err error) { return nil }
|
||||
|
||||
// Bind implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Bind(_ context.Context, _ agd.CertificateName, _ netip.Prefix) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Clone() (c *tls.Config) { return nil }
|
||||
@@ -57,357 +70,4 @@ func (EmptyManager) Clone() (c *tls.Config) { return nil }
|
||||
func (EmptyManager) CloneWithMetrics(_, _ string, _ []string) (c *tls.Config) { return nil }
|
||||
|
||||
// Remove implements the [Manager] interface for EmptyManager.
|
||||
func (EmptyManager) Remove(_ context.Context, _, _ string, _ bool) (err error) { return nil }
|
||||
|
||||
// DefaultManagerConfig is the configuration structure for [DefaultManager].
|
||||
type DefaultManagerConfig struct {
|
||||
// Logger is used for logging the operation of the TLS manager. It must not
|
||||
// be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// ErrColl is used to collect TLS-related errors. It must not be nil.
|
||||
ErrColl errcoll.Interface
|
||||
|
||||
// Metrics is used to collect TLS-related statistics. It must not be nil.
|
||||
//
|
||||
// TODO(a.garipov): See if the custom-domain certificates need any metrics.
|
||||
Metrics ManagerMetrics
|
||||
|
||||
// TicketDB stores paths to the TLS session tickets and updates them. It
|
||||
// must not be nil.
|
||||
TicketDB TicketDB
|
||||
|
||||
// KeyLogFilename, if not empty, is the name of the TLS key log file. If
|
||||
// not empty, KeyLogFilename must be a valid file path.
|
||||
KeyLogFilename string
|
||||
}
|
||||
|
||||
// DefaultManager is the default implementation of [Manager].
|
||||
type DefaultManager struct {
|
||||
// mu protects fields certStorage, clones, clonesWithMetrics,
|
||||
// sessTicketPaths.
|
||||
mu *sync.Mutex
|
||||
logger *slog.Logger
|
||||
errColl errcoll.Interface
|
||||
metrics ManagerMetrics
|
||||
tickDB TicketDB
|
||||
idx *certIndex
|
||||
original *tls.Config
|
||||
clones []*tls.Config
|
||||
clonesWithMetrics []*tls.Config
|
||||
}
|
||||
|
||||
// NewDefaultManager returns a new initialized *DefaultManager. c must not be
|
||||
// nil and must be valid.
|
||||
func NewDefaultManager(c *DefaultManagerConfig) (m *DefaultManager, err error) {
|
||||
var kl io.Writer
|
||||
fn := c.KeyLogFilename
|
||||
if fn != "" {
|
||||
kl, err = tlsKeyLogWriter(fn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing tls key log writer: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
m = &DefaultManager{
|
||||
mu: &sync.Mutex{},
|
||||
logger: c.Logger,
|
||||
errColl: c.ErrColl,
|
||||
metrics: c.Metrics,
|
||||
tickDB: c.TicketDB,
|
||||
idx: &certIndex{},
|
||||
}
|
||||
|
||||
m.original = &tls.Config{
|
||||
GetCertificate: m.getCertificate,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
KeyLogWriter: kl,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ Manager = (*DefaultManager)(nil)
|
||||
|
||||
// Add implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Add(
|
||||
ctx context.Context,
|
||||
certPath string,
|
||||
keyPath string,
|
||||
isCustom bool,
|
||||
) (err error) {
|
||||
cp := &certPaths{
|
||||
certPath: certPath,
|
||||
keyPath: keyPath,
|
||||
isCustom: isCustom,
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.idx.contains(cp) {
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"skipping already added certificate",
|
||||
"cert", cp.certPath,
|
||||
"key", cp.keyPath,
|
||||
"is_custom", cp.isCustom,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, err := m.load(ctx, cp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding certificate: %w", err)
|
||||
}
|
||||
|
||||
m.idx.add(cert, cp)
|
||||
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"added certificate",
|
||||
"cert", cp.certPath,
|
||||
"key", cp.keyPath,
|
||||
"is_custom", cp.isCustom,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// load returns a new TLS configuration from the provided certificate and key
|
||||
// paths. m.mu must be locked. c must not be modified.
|
||||
func (m *DefaultManager) load(
|
||||
ctx context.Context,
|
||||
cp *certPaths,
|
||||
) (c *tls.Certificate, err error) {
|
||||
cert, err := tls.LoadX509KeyPair(cp.certPath, cp.keyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading certificate: %w", err)
|
||||
}
|
||||
|
||||
if !cp.isCustom {
|
||||
authAlgo := cert.Leaf.PublicKeyAlgorithm.String()
|
||||
subj := cert.Leaf.Subject.String()
|
||||
m.metrics.SetCertificateInfo(ctx, authAlgo, subj, cert.Leaf.NotAfter)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// Clone implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Clone() (clone *tls.Config) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clone = m.original.Clone()
|
||||
m.clones = append(m.clones, clone)
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// getCertificate returns the TLS certificate for chi. See
|
||||
// [tls.Config.GetCertificate]. c must not be modified.
|
||||
func (m *DefaultManager) getCertificate(chi *tls.ClientHelloInfo) (c *tls.Certificate, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.idx.count() == 0 {
|
||||
return nil, errors.Error("no certificates")
|
||||
}
|
||||
|
||||
return m.idx.certFor(chi)
|
||||
}
|
||||
|
||||
// CloneWithMetrics implements the [Manager] interface for *DefaultManager.
|
||||
func (m *DefaultManager) CloneWithMetrics(
|
||||
proto string,
|
||||
srvName string,
|
||||
deviceDomains []string,
|
||||
) (conf *tls.Config) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clone := m.original.Clone()
|
||||
|
||||
clone.GetConfigForClient = m.metrics.BeforeHandshake(proto)
|
||||
|
||||
clone.GetCertificate = m.getCertificate
|
||||
|
||||
clone.VerifyConnection = m.metrics.AfterHandshake(
|
||||
proto,
|
||||
srvName,
|
||||
deviceDomains,
|
||||
m.idx.stored(),
|
||||
)
|
||||
|
||||
m.clonesWithMetrics = append(m.clonesWithMetrics, clone)
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ service.Refresher = (*DefaultManager)(nil)
|
||||
|
||||
// Refresh implements the [service.Refresher] interface for *DefaultManager.
|
||||
func (m *DefaultManager) Refresh(ctx context.Context) (err error) {
|
||||
m.logger.DebugContext(ctx, "refresh started")
|
||||
defer m.logger.DebugContext(ctx, "refresh finished")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "certificate refresh failed", err)
|
||||
}
|
||||
}()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
m.idx.rangeFn(func(_ *tls.Certificate, cp *certPaths) (cont bool) {
|
||||
cert, loadErr := m.load(ctx, cp)
|
||||
if err != nil {
|
||||
errs = append(errs, loadErr)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
msg, lvl := "refreshed certificate", slog.LevelInfo
|
||||
if !m.idx.update(cp, cert) {
|
||||
msg, lvl = "certificate did not refresh", slog.LevelWarn
|
||||
}
|
||||
|
||||
m.logger.Log(ctx, lvl, msg, "cert", cp.certPath, "key", cp.keyPath)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
err = errors.Join(errs...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refreshing tls certificates: %w", err)
|
||||
}
|
||||
|
||||
m.logger.InfoContext(ctx, "refresh successful", "num_configs", m.idx.count())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a certificate from the manager. certPath and keyPath must not
|
||||
// be empty.
|
||||
func (m *DefaultManager) Remove(
|
||||
ctx context.Context,
|
||||
certPath string,
|
||||
keyPath string,
|
||||
isCustom bool,
|
||||
) (err error) {
|
||||
cp := &certPaths{
|
||||
certPath: certPath,
|
||||
keyPath: keyPath,
|
||||
isCustom: isCustom,
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.idx.remove(cp)
|
||||
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"removed certificate",
|
||||
"cert", cp.certPath,
|
||||
"key", cp.keyPath,
|
||||
"is_custom", cp.isCustom,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RotateTickets refreshes and resets TLS session tickets. It may be used as a
|
||||
// [service.RefresherFunc].
|
||||
func (m *DefaultManager) RotateTickets(ctx context.Context) (err error) {
|
||||
m.logger.DebugContext(ctx, "ticket rotation started")
|
||||
defer m.logger.DebugContext(ctx, "ticket rotation finished")
|
||||
|
||||
paths, err := m.tickDB.Paths(ctx)
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "rotating tickets", err)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
m.metrics.SetSessionTicketRotationStatus(ctx, err)
|
||||
|
||||
if err != nil {
|
||||
errcoll.Collect(ctx, m.errColl, m.logger, "ticket rotation failed", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tickets := make([]SessionTicket, 0, len(paths))
|
||||
for _, filePath := range paths {
|
||||
var ticket SessionTicket
|
||||
ticket, err = readSessionTicketFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading session ticket: %w", err)
|
||||
}
|
||||
|
||||
tickets = append(tickets, ticket)
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, conf := range m.clones {
|
||||
conf.SetSessionTicketKeys(tickets)
|
||||
}
|
||||
|
||||
for _, conf := range m.clonesWithMetrics {
|
||||
conf.SetSessionTicketKeys(tickets)
|
||||
}
|
||||
|
||||
m.logger.InfoContext(
|
||||
ctx,
|
||||
"ticket rotation successful",
|
||||
"num_configs", m.idx.count(),
|
||||
"num_tickets", len(tickets),
|
||||
"num_clones", len(m.clones),
|
||||
"num_clones_with_metrics", len(m.clonesWithMetrics),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readSessionTicketFile reads a single TLS session ticket from a file.
|
||||
func readSessionTicketFile(fn string) (ticket SessionTicket, err error) {
|
||||
// #nosec G304 -- Trust the file paths that are given to us in the
|
||||
// configuration file.
|
||||
b, err := os.ReadFile(fn)
|
||||
if err != nil {
|
||||
return SessionTicket{}, fmt.Errorf("reading session ticket: %w", err)
|
||||
}
|
||||
|
||||
ticket, err = NewSessionTicket(b)
|
||||
if err != nil {
|
||||
return SessionTicket{}, fmt.Errorf("session ticket in %q: %w", fn, err)
|
||||
}
|
||||
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
// tlsKeyLogWriter returns a writer for logging TLS secrets to keyLogFilename.
|
||||
func tlsKeyLogWriter(keyLogFilename string) (kl io.Writer, err error) {
|
||||
path := filepath.Clean(keyLogFilename)
|
||||
|
||||
// TODO(a.garipov): Consider closing the file when we add SIGHUP support.
|
||||
kl, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kl, nil
|
||||
}
|
||||
func (EmptyManager) Remove(_ context.Context, _ agd.CertificateName) (err error) { return nil }
|
||||
|
||||
97
internal/tlsconfig/sortedmap.go
Normal file
97
internal/tlsconfig/sortedmap.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// sortedMap is a map that keeps elements in order with internal sorting
|
||||
// function. It must be initialized with [newSortedMap].
|
||||
//
|
||||
// TODO(e.burkov): Move to golibs.
|
||||
type sortedMap[K comparable, V any] struct {
|
||||
vals map[K]V
|
||||
cmp func(a, b K) (res int)
|
||||
keys []K
|
||||
}
|
||||
|
||||
// newSortedMap initializes a new instance of sorted map.
|
||||
func newSortedMap[K cmp.Ordered, V any]() (m *sortedMap[K, V]) {
|
||||
return &sortedMap[K, V]{
|
||||
vals: map[K]V{},
|
||||
cmp: cmp.Compare[K],
|
||||
}
|
||||
}
|
||||
|
||||
// set adds val with key to the sorted map. It panics if the m is nil.
|
||||
func (m *sortedMap[K, V]) set(key K, val V) {
|
||||
m.vals[key] = val
|
||||
|
||||
i, has := slices.BinarySearchFunc(m.keys, key, m.cmp)
|
||||
if has {
|
||||
m.keys[i] = key
|
||||
} else {
|
||||
m.keys = slices.Insert(m.keys, i, key)
|
||||
}
|
||||
}
|
||||
|
||||
// get returns val by key from the sorted map.
|
||||
func (m *sortedMap[K, V]) get(key K) (val V, ok bool) {
|
||||
if m == nil {
|
||||
var zero V
|
||||
|
||||
return zero, false
|
||||
}
|
||||
|
||||
val, ok = m.vals[key]
|
||||
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// del removes the value by key from the sorted map.
|
||||
func (m *sortedMap[K, V]) del(key K) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, has := m.vals[key]; !has {
|
||||
return
|
||||
}
|
||||
|
||||
delete(m.vals, key)
|
||||
i, _ := slices.BinarySearchFunc(m.keys, key, m.cmp)
|
||||
m.keys = slices.Delete(m.keys, i, i+1)
|
||||
}
|
||||
|
||||
// clear removes all elements from the sorted map.
|
||||
func (m *sortedMap[K, V]) clear() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.keys = m.keys[:0]
|
||||
clear(m.vals)
|
||||
}
|
||||
|
||||
// rangeFn calls f for each element of the map, sorted by m.cmp. If f returns
|
||||
// false it stops.
|
||||
func (m *sortedMap[K, V]) rangeFn(f func(K, V) (cont bool)) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, k := range m.keys {
|
||||
if !f(k, m.vals[k]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// len returns the number of elements in the sorted map.
|
||||
func (m *sortedMap[K, V]) len() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(m.vals)
|
||||
}
|
||||
94
internal/tlsconfig/sortedmap_test.go
Normal file
94
internal/tlsconfig/sortedmap_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSortedMap(t *testing.T) {
|
||||
var m *sortedMap[string, int]
|
||||
|
||||
letters := []string{}
|
||||
for i := range 10 {
|
||||
r := string('a' + rune(i))
|
||||
letters = append(letters, r)
|
||||
}
|
||||
|
||||
t.Run("create_and_fill", func(t *testing.T) {
|
||||
m = newSortedMap[string, int]()
|
||||
|
||||
nums := []int{}
|
||||
for i, r := range letters {
|
||||
m.set(r, i)
|
||||
nums = append(nums, i)
|
||||
}
|
||||
|
||||
gotLetters := []string{}
|
||||
gotNums := []int{}
|
||||
m.rangeFn(func(k string, v int) bool {
|
||||
gotLetters = append(gotLetters, k)
|
||||
gotNums = append(gotNums, v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, letters, gotLetters)
|
||||
assert.Equal(t, nums, gotNums)
|
||||
|
||||
n, ok := m.get(letters[0])
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, nums[0], n)
|
||||
})
|
||||
|
||||
t.Run("clear", func(t *testing.T) {
|
||||
lastLetter := letters[len(letters)-1]
|
||||
m.del(lastLetter)
|
||||
|
||||
_, ok := m.get(lastLetter)
|
||||
assert.False(t, ok)
|
||||
|
||||
m.clear()
|
||||
|
||||
gotLetters := []string{}
|
||||
m.rangeFn(func(k string, _ int) bool {
|
||||
gotLetters = append(gotLetters, k)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Len(t, gotLetters, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedMap_nil(t *testing.T) {
|
||||
const (
|
||||
key = "key"
|
||||
val = "val"
|
||||
)
|
||||
|
||||
var m sortedMap[string, string]
|
||||
|
||||
assert.Panics(t, func() {
|
||||
m.set(key, val)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
_, ok := m.get(key)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.rangeFn(func(_, _ string) (cont bool) {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.del(key)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.clear()
|
||||
})
|
||||
}
|
||||
@@ -74,7 +74,8 @@ func (db *LocalTicketDB) Paths(_ context.Context) (paths []string, err error) {
|
||||
|
||||
// RemoteTicketDBConfig is the configuration structure for [RemoteTicketDB].
|
||||
type RemoteTicketDBConfig struct {
|
||||
// Logger is used for logging the operation of the ticket database.
|
||||
// Logger is used for logging the operation of the ticket database. It must
|
||||
// not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Storage is used to retrieve the session tickets. It must not be nil.
|
||||
@@ -84,8 +85,8 @@ type RemoteTicketDBConfig struct {
|
||||
Clock timeutil.Clock
|
||||
|
||||
// CacheDirPath is the directory where the session tickets are cached. It
|
||||
// must be a valid non-empty path to a directory. If the directory doesn't
|
||||
// exist, it is created.
|
||||
// should be a valid non-empty path to a directory. If the directory
|
||||
// doesn't exist, it is created.
|
||||
CacheDirPath string
|
||||
|
||||
// IndexFileName is the base name of the index file. Stored session tickets
|
||||
|
||||
29
internal/tlsconfig/tlsconfig_internal_test.go
Normal file
29
internal/tlsconfig/tlsconfig_internal_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/golibs/testutil/fakenet"
|
||||
)
|
||||
|
||||
// NewLocalAddrConn returns a new [net.Conn] that has only the LocalAddr method
|
||||
// implemented, which returns addr.
|
||||
//
|
||||
// TODO(a.garipov): Add fakenet.NewConn.
|
||||
func NewLocalAddrConn(addr netip.Addr) (c *fakenet.Conn) {
|
||||
return &fakenet.Conn{
|
||||
OnLocalAddr: func() (a net.Addr) {
|
||||
return net.TCPAddrFromAddrPort(netip.AddrPortFrom(addr, 0))
|
||||
},
|
||||
OnRemoteAddr: func() (a net.Addr) { panic(testutil.UnexpectedCall()) },
|
||||
OnClose: func() (err error) { panic(testutil.UnexpectedCall()) },
|
||||
OnRead: func(b []byte) (n int, err error) { panic(testutil.UnexpectedCall()) },
|
||||
OnWrite: func(b []byte) (n int, err error) { panic(testutil.UnexpectedCall()) },
|
||||
OnSetDeadline: func(t time.Time) (err error) { panic(testutil.UnexpectedCall()) },
|
||||
OnSetReadDeadline: func(t time.Time) (err error) { panic(testutil.UnexpectedCall()) },
|
||||
OnSetWriteDeadline: func(t time.Time) (err error) { panic(testutil.UnexpectedCall()) },
|
||||
}
|
||||
}
|
||||
95
scripts/backend/customdomain.go
Normal file
95
scripts/backend/customdomain.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
|
||||
"github.com/AdguardTeam/golibs/httphdr"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// mockDNSServiceServer is the mock [backendpb.CustomDomainServiceServer].
|
||||
type mockCustomDomainServiceServer struct {
|
||||
backendpb.UnimplementedCustomDomainServiceServer
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// newCustomDomainServiceServer creates a new instance of
|
||||
// *mockCustomDomainServiceServer. logger must not be nil.
|
||||
func newCustomDomainServiceServer(logger *slog.Logger) (srv *mockCustomDomainServiceServer) {
|
||||
return &mockCustomDomainServiceServer{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ backendpb.CustomDomainServiceServer = (*mockCustomDomainServiceServer)(nil)
|
||||
|
||||
// GetCustomDomainCertificate implements the
|
||||
// [backendpb.CustomDomainServiceServer] interface
|
||||
// for *mockCustomDomainServiceServer.
|
||||
func (s *mockCustomDomainServiceServer) GetCustomDomainCertificate(
|
||||
ctx context.Context,
|
||||
req *backendpb.CustomDomainCertificateRequest,
|
||||
) (resp *backendpb.CustomDomainCertificateResponse, err error) {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
s.logger.InfoContext(
|
||||
ctx,
|
||||
"getting custom domain certificate",
|
||||
"auth", md.Get(httphdr.Authorization),
|
||||
"req", req,
|
||||
)
|
||||
|
||||
cert, key, err := generateCert(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp = &backendpb.CustomDomainCertificateResponse{
|
||||
Certificate: cert,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(key),
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// generateCert is a helper function that generates certificate and key.
|
||||
//
|
||||
// TODO(f.setrakov): DRY logic with tlsconfig_test.newCertAndKey.
|
||||
func generateCert(n int64) (certDER []byte, key *rsa.PrivateKey, err error) {
|
||||
serialNumber := big.NewInt(n)
|
||||
|
||||
notBefore := time.Now().Add(-time.Hour * 24)
|
||||
notAfter := time.Now().Add(time.Hour * 24)
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
DNSNames: []string{
|
||||
"current-1.domain.example",
|
||||
"current-2.domain.example",
|
||||
"pending-1.domain.example",
|
||||
"pending-2.domain.example",
|
||||
},
|
||||
}
|
||||
|
||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generating rsa key: %w", err)
|
||||
}
|
||||
|
||||
cer, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating certificate: %w", err)
|
||||
}
|
||||
|
||||
return cer, key, nil
|
||||
}
|
||||
@@ -258,11 +258,21 @@ func (s *mockDNSServiceServer) newDNSProfile(isFullSync bool) (dp *backendpb.DNS
|
||||
BlockChromePrefetch: true,
|
||||
BlockFirefoxCanary: true,
|
||||
BlockPrivateRelay: true,
|
||||
AdultBlockingMode: &backendpb.DNSProfile_AdultBlockingModeCustomIp{
|
||||
AdultBlockingModeCustomIp: &backendpb.BlockingModeCustomIP{
|
||||
Ipv4: []byte{1, 1, 1, 1},
|
||||
},
|
||||
},
|
||||
BlockingMode: &backendpb.DNSProfile_BlockingModeCustomIp{
|
||||
BlockingModeCustomIp: &backendpb.BlockingModeCustomIP{
|
||||
Ipv4: []byte{1, 2, 3, 4},
|
||||
},
|
||||
},
|
||||
SafeBrowsingBlockingMode: &backendpb.DNSProfile_SafeBrowsingBlockingModeCustomIp{
|
||||
SafeBrowsingBlockingModeCustomIp: &backendpb.BlockingModeCustomIP{
|
||||
Ipv4: []byte{2, 2, 2, 2},
|
||||
},
|
||||
},
|
||||
RateLimit: &backendpb.RateLimitSettings{
|
||||
ClientCidr: []*backendpb.CidrRange{{
|
||||
Address: netip.MustParseAddr("3.3.3.0").AsSlice(),
|
||||
|
||||
@@ -37,6 +37,9 @@ func main() {
|
||||
sessTickSrv := newMockSessionTicketServiceServer(l.With(slogutil.KeyPrefix, "session_ticket"))
|
||||
backendpb.RegisterSessionTicketServiceServer(grpcSrv, sessTickSrv)
|
||||
|
||||
customDomainSrv := newCustomDomainServiceServer(l.With(slogutil.KeyPrefix, "custom_domain"))
|
||||
backendpb.RegisterCustomDomainServiceServer(grpcSrv, customDomainSrv)
|
||||
|
||||
l.Info("starting serving", "laddr", listenAddr)
|
||||
err = grpcSrv.Serve(lsnr)
|
||||
if err != nil {
|
||||
|
||||
@@ -41,9 +41,25 @@ ecscache() (
|
||||
gofumpt -l -w ./ecsblocklist.go
|
||||
)
|
||||
|
||||
fcpb() (
|
||||
# TODO(f.setrakov): Change directory to ./internal/profiledb/internal/, so
|
||||
# we don't need to go up later.
|
||||
cd ./internal/profiledb/internal/filecachepb/
|
||||
protoc \
|
||||
--go_opt=paths=source_relative \
|
||||
--go_out=../fcpb/ \
|
||||
--go_opt=default_api_level=API_OPAQUE \
|
||||
--go_opt=Mfilecache.proto=github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/fcpb \
|
||||
./filecache.proto
|
||||
)
|
||||
|
||||
filecachepb() (
|
||||
cd ./internal/profiledb/internal/filecachepb/
|
||||
protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto
|
||||
protoc \
|
||||
--go_opt=paths=source_relative \
|
||||
--go_out=. \
|
||||
--go_opt=Mfilecache.proto=github.com/AdguardTeam/AdGuardDNS/internal/profiledb/internal/filecachepb \
|
||||
./filecache.proto
|
||||
)
|
||||
|
||||
geoip_asntops() (
|
||||
@@ -59,6 +75,7 @@ geoip_country() (
|
||||
if [ -z "${ONLY:-}" ]; then
|
||||
backendpb
|
||||
ecscache
|
||||
fcpb
|
||||
filecachepb
|
||||
geoip_asntops
|
||||
geoip_country
|
||||
@@ -73,6 +90,10 @@ else
|
||||
ecscache
|
||||
fi
|
||||
|
||||
if [ "${padded##* fcpb *}" = '' ]; then
|
||||
fcpb
|
||||
fi
|
||||
|
||||
if [ "${padded##* filecachepb *}" = '' ]; then
|
||||
filecachepb
|
||||
fi
|
||||
|
||||
@@ -66,7 +66,7 @@ set -f -u
|
||||
#
|
||||
# * internal/agdtest/profile.go: a test helper requiring the use of
|
||||
# reflect.Type.
|
||||
# * internal/profiledb/internal/filecachepb/unsafe.go: a “safe” unsafe helper
|
||||
# * internal/agdprotobuf/unsafe.go: a “safe” unsafe helper
|
||||
# to prevent excessive allocations.
|
||||
blocklist_imports() {
|
||||
import_or_tab="$(printf '^\\(import \\|\t\\)')"
|
||||
@@ -78,7 +78,7 @@ blocklist_imports() {
|
||||
-name '*.go' \
|
||||
'!' -name '*.pb.go' \
|
||||
'!' -path './internal/agdtest/profile.go' \
|
||||
'!' -path './internal/profiledb/internal/filecachepb/unsafe.go' \
|
||||
'!' -path './internal/agdprotobuf/unsafe.go' \
|
||||
')' \
|
||||
-exec \
|
||||
'grep' \
|
||||
@@ -225,7 +225,7 @@ if [ "$shadow_output" != '' ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_linter gosec --exclude-generated --quiet work
|
||||
run_linter gosec --exclude-generated --quiet ./...
|
||||
|
||||
run_linter errcheck work
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ initialisms = [
|
||||
# Do not add "PTR" since we use "Ptr" as a suffix.
|
||||
"inherit"
|
||||
, "ASN"
|
||||
, "CIDR"
|
||||
, "DHCP"
|
||||
, "DNSSEC"
|
||||
# E.g. SentryDSN.
|
||||
|
||||
Reference in New Issue
Block a user