Compare commits
29 Commits
ADG-1-util
...
AGDNS-2743
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53cb84efc0 | ||
|
|
c7c62ad3b6 | ||
|
|
003e7ce0d5 | ||
|
|
a8fdf1c553 | ||
|
|
7d479baba6 | ||
|
|
feb9c886d8 | ||
|
|
3521e8ed9f | ||
|
|
4d258972d1 | ||
|
|
9726171f0f | ||
|
|
6d282ae716 | ||
|
|
6a99c39d11 | ||
|
|
1cc6c00e4b | ||
|
|
1cb6634d67 | ||
|
|
106785aab0 | ||
|
|
295b2fefb1 | ||
|
|
8b4768aadd | ||
|
|
810ae94832 | ||
|
|
fd337967f4 | ||
|
|
4adf2bcf00 | ||
|
|
1eb1b1108c | ||
|
|
85e6bf54c9 | ||
|
|
cb5de5c653 | ||
|
|
403fa9b1fe | ||
|
|
2f32b97d2f | ||
|
|
19d2fd47e2 | ||
|
|
f82dee17f0 | ||
|
|
1a3853d52a | ||
|
|
c41af2763f | ||
|
|
ee91a6084f |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -1,8 +1,8 @@
|
|||||||
'name': 'build'
|
'name': 'build'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.24.1'
|
'GO_VERSION': '1.24.2'
|
||||||
'NODE_VERSION': '18'
|
'NODE_VERSION': '20'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
'push':
|
'push':
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
'name': 'lint'
|
'name': 'lint'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.24.1'
|
'GO_VERSION': '1.24.2'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
'push':
|
'push':
|
||||||
|
|||||||
101
CHANGELOG.md
101
CHANGELOG.md
@@ -9,21 +9,99 @@ The format is based on [*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/
|
|||||||
<!--
|
<!--
|
||||||
## [v0.108.0] – TBA
|
## [v0.108.0] – TBA
|
||||||
|
|
||||||
## [v0.107.58] - 2025-03-11 (APPROX.)
|
## [v0.107.61] - 2025-04-22 (APPROX.)
|
||||||
|
|
||||||
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
|
See also the [v0.107.61 GitHub milestone][ms-v0.107.61].
|
||||||
|
|
||||||
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
|
[ms-v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/milestone/96?closed=1
|
||||||
|
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
|
- Any simultaneous requests that are considered duplicates will now only result in a single request to upstreams, reducing the chance of a cache poisoning attack succeeding. This is controlled by the new configuration object `pending_requests`, which has a single `enabled` property, set to `true` by default.
|
||||||
|
|
||||||
|
**NOTE:** We thank [Xiang Li][mr-xiang-li] for reporting this security issue. It's strongly recommended to leave it enabled, otherwise AdGuard Home will be vulnerable to untrusted clients.
|
||||||
|
|
||||||
|
[mr-xiang-li]: https://lixiang521.com/
|
||||||
|
|
||||||
|
<!--
|
||||||
|
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## [v0.107.60] - 2025-04-14
|
||||||
|
|
||||||
|
See also the [v0.107.60 GitHub milestone][ms-v0.107.60].
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.2][go-1.24.2].
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Alpine Linux version in `Dockerfile` has been updated to 3.21 ([#7588]).
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- Node 20 support, Node 22 will be required in future releases.
|
||||||
|
|
||||||
|
**NOTE:** `npm` may be replaced with a different tool, such as `pnpm` or `yarn`, in a future release.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Filtering for DHCP clients ([#7734]).
|
||||||
|
|
||||||
|
- Incorrect label on login page ([#7729]).
|
||||||
|
|
||||||
|
- Validation process for the HTTPS port on the *Encryption Settings* page.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Node 18 support.
|
||||||
|
|
||||||
|
[#7588]: https://github.com/AdguardTeam/AdGuardHome/issues/7588
|
||||||
|
[#7729]: https://github.com/AdguardTeam/AdGuardHome/issues/7729
|
||||||
|
[#7734]: https://github.com/AdguardTeam/AdGuardHome/issues/7734
|
||||||
|
|
||||||
|
[go-1.24.2]: https://groups.google.com/g/golang-announce/c/Y2uBTVKjBQk
|
||||||
|
[ms-v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/milestone/95?closed=1
|
||||||
|
|
||||||
|
## [v0.107.59] - 2025-03-21
|
||||||
|
|
||||||
|
See also the [v0.107.59 GitHub milestone][ms-v0.107.59].
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
|
||||||
|
|
||||||
|
- Rules with the `client` modifier not working ([#7708]).
|
||||||
|
|
||||||
|
- The search form not working in the query log ([#7704]).
|
||||||
|
|
||||||
|
[#7704]: https://github.com/AdguardTeam/AdGuardHome/issues/7704
|
||||||
|
[#7708]: https://github.com/AdguardTeam/AdGuardHome/issues/7708
|
||||||
|
|
||||||
|
[ms-v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/milestone/94?closed=1
|
||||||
|
|
||||||
|
## [v0.107.58] - 2025-03-19
|
||||||
|
|
||||||
|
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The ability to check filtering rules for host names using an optional query type and optional ClientID or client IP address ([#4036]).
|
||||||
|
|
||||||
|
- Optional `client` and `qtype` URL query parameters to the `GET /control/check_host` HTTP API.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Clearing the DNS cache on the *DNS settings* page now includes both global cache and custom client cache.
|
||||||
|
|
||||||
- Invalid ICMPv6 Router Advertisement messages ([#7547]).
|
- Invalid ICMPv6 Router Advertisement messages ([#7547]).
|
||||||
|
|
||||||
- Disabled button for autofilled login form.
|
- Disabled button for autofilled login form.
|
||||||
@@ -34,14 +112,12 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
|||||||
|
|
||||||
- The formatting of large numbers in the clients tables on the *Client settings* page ([#7583]).
|
- The formatting of large numbers in the clients tables on the *Client settings* page ([#7583]).
|
||||||
|
|
||||||
|
[#4036]: https://github.com/AdguardTeam/AdGuardHome/issues/4036
|
||||||
[#7547]: https://github.com/AdguardTeam/AdGuardHome/issues/7547
|
[#7547]: https://github.com/AdguardTeam/AdGuardHome/issues/7547
|
||||||
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
|
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
|
||||||
|
|
||||||
[go-1.24.1]: https://groups.google.com/g/golang-announce/c/4t3lzH3I0eI
|
[go-1.24.1]: https://groups.google.com/g/golang-announce/c/4t3lzH3I0eI
|
||||||
|
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
|
||||||
<!--
|
|
||||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## [v0.107.57] - 2025-02-20
|
## [v0.107.57] - 2025-02-20
|
||||||
|
|
||||||
@@ -3039,11 +3115,14 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||||||
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.61...HEAD
|
||||||
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
[v0.107.61]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...v0.107.61
|
||||||
-->
|
-->
|
||||||
|
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.60...HEAD
|
||||||
|
[v0.107.60]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.59...v0.107.60
|
||||||
|
[v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...v0.107.59
|
||||||
|
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
||||||
[v0.107.57]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.56...v0.107.57
|
[v0.107.57]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.56...v0.107.57
|
||||||
[v0.107.56]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.55...v0.107.56
|
[v0.107.56]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.55...v0.107.56
|
||||||
[v0.107.55]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.54...v0.107.55
|
[v0.107.55]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.54...v0.107.55
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -27,7 +27,7 @@ DIST_DIR = dist
|
|||||||
GOAMD64 = v1
|
GOAMD64 = v1
|
||||||
GOPROXY = https://proxy.golang.org|direct
|
GOPROXY = https://proxy.golang.org|direct
|
||||||
GOTELEMETRY = off
|
GOTELEMETRY = off
|
||||||
GOTOOLCHAIN = go1.24.1
|
GOTOOLCHAIN = go1.24.2
|
||||||
GPG_KEY = devteam@adguard.com
|
GPG_KEY = devteam@adguard.com
|
||||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||||
NPM = npm
|
NPM = npm
|
||||||
@@ -38,7 +38,6 @@ REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
|
|||||||
SIGN = 1
|
SIGN = 1
|
||||||
SIGNER_API_KEY = not-a-real-key
|
SIGNER_API_KEY = not-a-real-key
|
||||||
VERSION = v0.0.0
|
VERSION = v0.0.0
|
||||||
YARN = yarn
|
|
||||||
|
|
||||||
NEXTAPI = 0
|
NEXTAPI = 0
|
||||||
|
|
||||||
@@ -139,5 +138,4 @@ txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
|
|||||||
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
|
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
|
||||||
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
|
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
|
||||||
|
|
||||||
openapi-lint: ; cd ./openapi/ && $(YARN) test
|
# TODO(a.garipov): Re-add openapi-lint.
|
||||||
openapi-show: ; cd ./openapi/ && $(YARN) start
|
|
||||||
|
|||||||
@@ -205,9 +205,9 @@ Run `make init` to prepare the development environment.
|
|||||||
|
|
||||||
You will need this to build AdGuard Home:
|
You will need this to build AdGuard Home:
|
||||||
|
|
||||||
- [Go](https://golang.org/dl/) v1.23 or later;
|
- [Go](https://golang.org/dl/) v1.24 or later;
|
||||||
- [Node.js](https://nodejs.org/en/download/) v18.18 or later;
|
- [Node.js](https://nodejs.org/en/download/) v20.19 or later;
|
||||||
- [npm](https://www.npmjs.com/) v8 or later;
|
- [npm](https://www.npmjs.com/) v10.8 or later;
|
||||||
|
|
||||||
### <a href="#building" id="building" name="building">Building</a>
|
### <a href="#building" id="building" name="building">Building</a>
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
# Make sure to sync any changes with the branch overrides below.
|
# Make sure to sync any changes with the branch overrides below.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'edge'
|
'channel': 'edge'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
|
'dockerFrontend': 'adguard/home-js-builder:3.1'
|
||||||
'dockerGo': 'adguard/go-builder:1.24.1--1'
|
'dockerGo': 'adguard/go-builder:1.24.2--1'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Build frontend':
|
- 'Build frontend':
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
'docker':
|
'docker':
|
||||||
'image': '${bamboo.dockerFrontend}'
|
'image': '${bamboo.dockerFrontend}'
|
||||||
'volumes':
|
'volumes':
|
||||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
|
||||||
'key': 'BF'
|
'key': 'BF'
|
||||||
'other':
|
'other':
|
||||||
'clean-working-dir': true
|
'clean-working-dir': true
|
||||||
@@ -157,6 +157,7 @@
|
|||||||
|
|
||||||
# Print Docker info.
|
# Print Docker info.
|
||||||
docker info
|
docker info
|
||||||
|
docker buildx version
|
||||||
|
|
||||||
# Prepare and push the build.
|
# Prepare and push the build.
|
||||||
env \
|
env \
|
||||||
@@ -277,8 +278,8 @@
|
|||||||
# need to build a few of these.
|
# need to build a few of these.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'beta'
|
'channel': 'beta'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
|
'dockerFrontend': 'adguard/home-js-builder:3.1'
|
||||||
'dockerGo': 'adguard/go-builder:1.24.1--1'
|
'dockerGo': 'adguard/go-builder:1.24.2--1'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final
|
# release-vX.Y.Z branches are the branches from which the actual final
|
||||||
# release is built.
|
# release is built.
|
||||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||||
@@ -293,5 +294,5 @@
|
|||||||
# are the ones that actually get released.
|
# are the ones that actually get released.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'release'
|
'channel': 'release'
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
|
'dockerFrontend': 'adguard/home-js-builder:3.1'
|
||||||
'dockerGo': 'adguard/go-builder:1.24.1--1'
|
'dockerGo': 'adguard/go-builder:1.24.2--1'
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
'key': 'AHBRTSPECS'
|
'key': 'AHBRTSPECS'
|
||||||
'name': 'AdGuard Home - Build and run tests'
|
'name': 'AdGuard Home - Build and run tests'
|
||||||
'variables':
|
'variables':
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
|
'dockerFrontend': 'adguard/home-js-builder:3.1'
|
||||||
'dockerGo': 'adguard/go-builder:1.24.1--1'
|
'dockerGo': 'adguard/go-builder:1.24.2--1'
|
||||||
'channel': 'development'
|
'channel': 'development'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
'docker':
|
'docker':
|
||||||
'image': '${bamboo.dockerFrontend}'
|
'image': '${bamboo.dockerFrontend}'
|
||||||
'volumes':
|
'volumes':
|
||||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
|
||||||
'key': 'JSTEST'
|
'key': 'JSTEST'
|
||||||
'other':
|
'other':
|
||||||
'clean-working-dir': true
|
'clean-working-dir': true
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
'docker':
|
'docker':
|
||||||
'image': '${bamboo.dockerFrontend}'
|
'image': '${bamboo.dockerFrontend}'
|
||||||
'volumes':
|
'volumes':
|
||||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
|
||||||
'key': 'BF'
|
'key': 'BF'
|
||||||
'other':
|
'other':
|
||||||
'clean-working-dir': true
|
'clean-working-dir': true
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
'docker':
|
'docker':
|
||||||
'image': '${bamboo.dockerFrontend}'
|
'image': '${bamboo.dockerFrontend}'
|
||||||
'volumes':
|
'volumes':
|
||||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
'${system.NPM_DIR}': '${bamboo.cacheNpm}'
|
||||||
'key': 'E2ETEST'
|
'key': 'E2ETEST'
|
||||||
'other':
|
'other':
|
||||||
'clean-working-dir': true
|
'clean-working-dir': true
|
||||||
@@ -233,6 +233,6 @@
|
|||||||
# Set the default release channel on the release branch to beta, as we
|
# Set the default release channel on the release branch to beta, as we
|
||||||
# may need to build a few of these.
|
# may need to build a few of these.
|
||||||
'variables':
|
'variables':
|
||||||
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
|
'dockerFrontend': 'adguard/home-js-builder:3.1'
|
||||||
'dockerGo': 'adguard/go-builder:1.24.1--1'
|
'dockerGo': 'adguard/go-builder:1.24.2--1'
|
||||||
'channel': 'candidate'
|
'channel': 'candidate'
|
||||||
|
|||||||
1207
client/package-lock.json
generated
vendored
1207
client/package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
4
client/package.json
vendored
4
client/package.json
vendored
@@ -66,7 +66,7 @@
|
|||||||
"@babel/preset-react": "^7.24.1",
|
"@babel/preset-react": "^7.24.1",
|
||||||
"@playwright/test": "1.50.1",
|
"@playwright/test": "1.50.1",
|
||||||
"@types/lodash": "^4.17.4",
|
"@types/lodash": "^4.17.4",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.13.10",
|
||||||
"@types/react": "^17.0.80",
|
"@types/react": "^17.0.80",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-redux": "^7.1.33",
|
"@types/react-redux": "^7.1.33",
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
"stylelint": "^16.5.0",
|
"stylelint": "^16.5.0",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"vitest": "^3.0.4",
|
"vitest": "^3.1.1",
|
||||||
"webpack": "^5.91.0",
|
"webpack": "^5.91.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^5.0.4",
|
"webpack-dev-server": "^5.0.4",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"filter": "Филтър",
|
"filter": "Филтър",
|
||||||
"query_log": "История на заявките",
|
"query_log": "История на заявките",
|
||||||
"compact": "Compact",
|
"compact": "Compact",
|
||||||
|
"nothing_found": "Нищо не е намерено",
|
||||||
"faq": "ЧЗВ",
|
"faq": "ЧЗВ",
|
||||||
"version": "версия",
|
"version": "версия",
|
||||||
"address": "Адрес",
|
"address": "Адрес",
|
||||||
@@ -65,14 +66,12 @@
|
|||||||
"stats_malware_phishing": "вируси/атаки",
|
"stats_malware_phishing": "вируси/атаки",
|
||||||
"stats_adult": "сайтове за възрастни",
|
"stats_adult": "сайтове за възрастни",
|
||||||
"stats_query_domain": "Най-отваряни страници",
|
"stats_query_domain": "Най-отваряни страници",
|
||||||
"for_last_24_hours": "за последните 24 часа",
|
|
||||||
"no_domains_found": "Няма намерени резултати",
|
"no_domains_found": "Няма намерени резултати",
|
||||||
"requests_count": "Сума на заявките",
|
"requests_count": "Сума на заявките",
|
||||||
"top_blocked_domains": "Най-блокирани страници",
|
"top_blocked_domains": "Най-блокирани страници",
|
||||||
"top_clients": "Най-активни IP адреси",
|
"top_clients": "Най-активни IP адреси",
|
||||||
"no_clients_found": "Нямa намерени адреси",
|
"no_clients_found": "Нямa намерени адреси",
|
||||||
"general_statistics": "Обща статисика",
|
"general_statistics": "Обща статисика",
|
||||||
"number_of_dns_query_24_hours": "Сума на DNS заявки за последните 24 часа",
|
|
||||||
"number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни",
|
"number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Сума на блокирани DNS заявки от AdGuard свързани със сигурността",
|
"number_of_dns_query_blocked_24_hours_by_sec": "Сума на блокирани DNS заявки от AdGuard свързани със сигурността",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Сума на блокирани сайтове за възрастни",
|
"number_of_dns_query_blocked_24_hours_adult": "Сума на блокирани сайтове за възрастни",
|
||||||
@@ -156,6 +155,7 @@
|
|||||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
||||||
"default": "По подразбиране",
|
"default": "По подразбиране",
|
||||||
"custom_ip": "Персонализиран IP",
|
"custom_ip": "Персонализиран IP",
|
||||||
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-пред-HTTPS",
|
"dns_over_https": "DNS-пред-HTTPS",
|
||||||
"dns_over_quic": "DNS-over-QUIC",
|
"dns_over_quic": "DNS-over-QUIC",
|
||||||
"plain_dns": "Обикновен DNS",
|
"plain_dns": "Обикновен DNS",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Důvod: {{reason}}",
|
"check_reason": "Důvod: {{reason}}",
|
||||||
"check_service": "Název služby: {{service}}",
|
"check_service": "Název služby: {{service}}",
|
||||||
|
"check_hostname": "Název hostitele nebo domény",
|
||||||
|
"check_client_id": "Identifikátor klienta (ClientID nebo IP adresa)",
|
||||||
|
"check_enter_client_id": "Zadejte identifikátor klienta",
|
||||||
|
"check_dns_record": "Vyberte typ DNS záznamu",
|
||||||
"service_name": "Název služby",
|
"service_name": "Název služby",
|
||||||
"check_not_found": "Nenalezeno ve Vašich seznamech filtrů",
|
"check_not_found": "Nenalezeno ve Vašich seznamech filtrů",
|
||||||
"client_confirm_block": "Opravdu chcete zablokovat klienta „{{ip}}“?",
|
"client_confirm_block": "Opravdu chcete zablokovat klienta „{{ip}}“?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Årsag: {{reason}}",
|
"check_reason": "Årsag: {{reason}}",
|
||||||
"check_service": "Tjenestenavn: {{service}}",
|
"check_service": "Tjenestenavn: {{service}}",
|
||||||
|
"check_hostname": "Værts- eller domænenavn",
|
||||||
|
"check_client_id": "Klientidentifikator (ClientID eller IP-adresse)",
|
||||||
|
"check_enter_client_id": "Angiv klientidentifikator",
|
||||||
|
"check_dns_record": "Vælg DNS-posttype",
|
||||||
"service_name": "Tjenestenavn",
|
"service_name": "Tjenestenavn",
|
||||||
"check_not_found": "Ikke fundet i dine filterlister",
|
"check_not_found": "Ikke fundet i dine filterlister",
|
||||||
"client_confirm_block": "Sikker på, at du vil blokere klienten \"{{ip}}\"?",
|
"client_confirm_block": "Sikker på, at du vil blokere klienten \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -97,9 +97,9 @@
|
|||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"filters": "Filter",
|
"filters": "Filter",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"query_log": "Anfragenprotokoll",
|
"query_log": "Abfrageprotokoll",
|
||||||
"compact": "Kompakt",
|
"compact": "Kompakt",
|
||||||
"nothing_found": "Nichts gefunden\n",
|
"nothing_found": "Nichts gefunden",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"address": "Adresse",
|
"address": "Adresse",
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
"cancel_btn": "Abbrechen",
|
"cancel_btn": "Abbrechen",
|
||||||
"enter_name_hint": "Name eingeben",
|
"enter_name_hint": "Name eingeben",
|
||||||
"enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben",
|
"enter_url_or_path_hint": "URL oder absoluten Pfad der Liste eingeben",
|
||||||
"check_updates_btn": "Nach Aktualisierungen suchen",
|
"check_updates_btn": "Nach Updates suchen",
|
||||||
"new_blocklist": "Neue Sperrliste",
|
"new_blocklist": "Neue Sperrliste",
|
||||||
"new_allowlist": "Neue Positivliste",
|
"new_allowlist": "Neue Positivliste",
|
||||||
"edit_blocklist": "Sperrliste bearbeiten",
|
"edit_blocklist": "Sperrliste bearbeiten",
|
||||||
@@ -566,7 +566,7 @@
|
|||||||
"ignore_domains": "Ignorierte Domains (durch Zeilenumbruch getrennt)",
|
"ignore_domains": "Ignorierte Domains (durch Zeilenumbruch getrennt)",
|
||||||
"ignore_domains_title": "Ignorierte Domains",
|
"ignore_domains_title": "Ignorierte Domains",
|
||||||
"ignore_domains_desc_stats": "Abfragen, die diesen Regeln entsprechen, werden nicht in die Statistik aufgenommen",
|
"ignore_domains_desc_stats": "Abfragen, die diesen Regeln entsprechen, werden nicht in die Statistik aufgenommen",
|
||||||
"ignore_domains_desc_query": "Abfragen, die diesen Regeln entsprechen, werden nicht in das Anfragenprotokoll aufgenommen",
|
"ignore_domains_desc_query": "Abfragen, die diesen Regeln entsprechen, werden nicht in das Abfrageprotokoll aufgenommen",
|
||||||
"interval_hours": "{{count}} Stunde",
|
"interval_hours": "{{count}} Stunde",
|
||||||
"interval_hours_plural": "{{count}} Stunden",
|
"interval_hours_plural": "{{count}} Stunden",
|
||||||
"filters_configuration": "Filterkonfiguration",
|
"filters_configuration": "Filterkonfiguration",
|
||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Grund: {{reason}}",
|
"check_reason": "Grund: {{reason}}",
|
||||||
"check_service": "Dienstname: {{service}}",
|
"check_service": "Dienstname: {{service}}",
|
||||||
|
"check_hostname": "Hostname oder Domainname",
|
||||||
|
"check_client_id": "Client-Kennung (ClientID oder IP-Adresse)",
|
||||||
|
"check_enter_client_id": "Client-Kennung eingeben",
|
||||||
|
"check_dns_record": "DNS-Datensatztyp auswählen",
|
||||||
"service_name": "Name des Dienstes",
|
"service_name": "Name des Dienstes",
|
||||||
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
"check_not_found": "Nicht in Ihren Filterlisten enthalten",
|
||||||
"client_confirm_block": "Möchten Sie den Client „{{ip}}“ wirklich sperren?",
|
"client_confirm_block": "Möchten Sie den Client „{{ip}}“ wirklich sperren?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Reason: {{reason}}",
|
"check_reason": "Reason: {{reason}}",
|
||||||
"check_service": "Service name: {{service}}",
|
"check_service": "Service name: {{service}}",
|
||||||
|
"check_hostname": "Hostname or domain name",
|
||||||
|
"check_client_id": "Client identifier (ClientID or IP address)",
|
||||||
|
"check_enter_client_id": "Enter client identifier",
|
||||||
|
"check_dns_record": "Select DNS record type",
|
||||||
"service_name": "Service name",
|
"service_name": "Service name",
|
||||||
"check_not_found": "Not found in your filter lists",
|
"check_not_found": "Not found in your filter lists",
|
||||||
"client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",
|
"client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "Configuración de clientes",
|
"client_settings": "Configuración de clientes",
|
||||||
"example_upstream_reserved": "un DNS de subida <0>para un dominio específico</0>.",
|
"example_upstream_reserved": "un proveedor DNS <0>para un dominio específico</0>.",
|
||||||
"example_multiple_upstreams_reserved": "múltiples upstreams <0>para dominios específicos</0>;",
|
"example_multiple_upstreams_reserved": "múltiples proveedores DNS <0>para dominios específicos</0>.",
|
||||||
"example_upstream_comment": "un comentario.",
|
"example_upstream_comment": "un comentario.",
|
||||||
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
|
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los proveedores DNS.",
|
||||||
"parallel_requests": "Consultas paralelas",
|
"parallel_requests": "Consultas paralelas",
|
||||||
"load_balancing": "Balanceo de carga",
|
"load_balancing": "Balanceo de carga",
|
||||||
"load_balancing_desc": "Consulta un servidor Dns upstream a la vez.<br/>AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo medio de búsqueda.",
|
"load_balancing_desc": "Consulta un proveedor DNS a la vez.<br/>AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo promedio de búsqueda.",
|
||||||
"bootstrap_dns": "Servidores DNS de arranque",
|
"bootstrap_dns": "Servidores DNS de arranque",
|
||||||
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
|
"bootstrap_dns_desc": "Direcciones IP de los servidores DNS utilizados para resolver las direcciones IP de los resolutores DoH/DoT que especifiques como proveedores DNS. No se permiten comentarios.",
|
||||||
"fallback_dns_title": "Servidores DNS alternativos",
|
"fallback_dns_title": "Servidores DNS alternativos",
|
||||||
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
|
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los proveedores DNS no responden. La sintaxis es la misma que en el campo de los principales proveedores DNS anterior.",
|
||||||
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
|
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
|
||||||
"local_ptr_title": "Servidores DNS inversos y privados",
|
"local_ptr_title": "Servidores DNS inversos y privados",
|
||||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para consultas PTR, SOA y NS privadas. La petición se considera privada si solicita un dominio ARPA que contiene una subred dentro de rangos IP privados, por ejemplo \"192.168.12.34\", y procede de un cliente con dirección privada. Si no se configura, AdGuard Home utiliza las direcciones de los resolvedores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para consultas PTR, SOA y NS privadas. La petición se considera privada si solicita un dominio ARPA que contiene una subred dentro de rangos IP privados, por ejemplo \"192.168.12.34\", y procede de un cliente con dirección privada. Si no se configura, AdGuard Home utiliza las direcciones de los resolvedores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
|
"local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
|
||||||
"local_ptr_placeholder": "Ingresa una dirección IP por línea",
|
"local_ptr_placeholder": "Ingresa una dirección IP por línea",
|
||||||
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
|
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
|
||||||
"resolve_clients_desc": "Resolve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).",
|
"resolve_clients_desc": "Resuelve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, proveedores DNS para clientes con direcciones IP públicas).",
|
||||||
"use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados",
|
"use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados",
|
||||||
"use_private_ptr_resolvers_desc": "Resolver las peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones privadas utilizando servidores upstream privados, DHCP, /etc/hosts, etc. Si se desactiva, AdGuard Home responde a todas estas consultas con NXDOMAIN.",
|
"use_private_ptr_resolvers_desc": "Resuelve peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones IP privadas a través de proveedores DNS privados, DHCP, /etc/hosts, etc. Si se deshabilita, AdGuard Home responderá a todas estas peticiones con NXDOMAIN.",
|
||||||
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
||||||
"save_config": "Guardar configuración",
|
"save_config": "Guardar configuración",
|
||||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||||
@@ -132,8 +132,8 @@
|
|||||||
"top_clients": "Clientes más frecuentes",
|
"top_clients": "Clientes más frecuentes",
|
||||||
"no_clients_found": "No se han encontrado clientes",
|
"no_clients_found": "No se han encontrado clientes",
|
||||||
"general_statistics": "Estadísticas generales",
|
"general_statistics": "Estadísticas generales",
|
||||||
"top_upstreams": "DNS de subida más frecuentes",
|
"top_upstreams": "Proveedores DNS más frecuentes",
|
||||||
"no_upstreams_data_found": "No se han encontrado datos de DNS de subida",
|
"no_upstreams_data_found": "No se han encontrado datos de proveedores DNS",
|
||||||
"number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día",
|
"number_of_dns_query_days": "Número de consultas DNS procesadas durante el último {{count}} día",
|
||||||
"number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días",
|
"number_of_dns_query_days_plural": "Número de consultas DNS procesadas durante los últimos {{count}} días",
|
||||||
"number_of_dns_query_hours": "Número de consultas DNS procesadas durante la última {{count}} hora",
|
"number_of_dns_query_hours": "Número de consultas DNS procesadas durante la última {{count}} hora",
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
"enforced_save_search": "Búsquedas seguras forzadas",
|
"enforced_save_search": "Búsquedas seguras forzadas",
|
||||||
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
|
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
|
||||||
"average_processing_time": "Tiempo promedio de procesamiento",
|
"average_processing_time": "Tiempo promedio de procesamiento",
|
||||||
"average_upstream_response_time": "Tiempo promedio de respuesta upstream",
|
"average_upstream_response_time": "Tiempo promedio de respuesta del proveedor DNS",
|
||||||
"response_time": "Tiempo de respuesta",
|
"response_time": "Tiempo de respuesta",
|
||||||
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS",
|
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición DNS",
|
||||||
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
|
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
"enforce_save_search_hint": "AdGuard Home reforzará la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex y Pixabay.",
|
"enforce_save_search_hint": "AdGuard Home reforzará la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex y Pixabay.",
|
||||||
"no_servers_specified": "No hay servidores especificados",
|
"no_servers_specified": "No hay servidores especificados",
|
||||||
"general_settings": "Configuración general",
|
"general_settings": "Configuración general",
|
||||||
"dns_settings": "Configuración del DNS",
|
"dns_settings": "Configuración DNS",
|
||||||
"dns_blocklists": "Listas de bloqueo DNS",
|
"dns_blocklists": "Listas de bloqueo DNS",
|
||||||
"dns_allowlists": "Listas de permitido DNS",
|
"dns_allowlists": "Listas de permitido DNS",
|
||||||
"dns_blocklists_desc": "AdGuard Home bloqueará los dominios que coincidan con las listas de bloqueo.",
|
"dns_blocklists_desc": "AdGuard Home bloqueará los dominios que coincidan con las listas de bloqueo.",
|
||||||
@@ -165,12 +165,12 @@
|
|||||||
"custom_filtering_rules": "Reglas de filtrado personalizado",
|
"custom_filtering_rules": "Reglas de filtrado personalizado",
|
||||||
"encryption_settings": "Configuración de cifrado",
|
"encryption_settings": "Configuración de cifrado",
|
||||||
"dhcp_settings": "Configuración DHCP",
|
"dhcp_settings": "Configuración DHCP",
|
||||||
"upstream_dns": "Servidores DNS de subida",
|
"upstream_dns": "Proveedores DNS",
|
||||||
"upstream_dns_help": "Ingresa una dirección de servidor por línea. <a>Más información</a> sobre la configuración de los servidores DNS de subida.",
|
"upstream_dns_help": "Ingresa una dirección de servidor por línea. <a>Más información</a> sobre la configuración de los proveedores DNS.",
|
||||||
"upstream_dns_configured_in_file": "Configurado en {{path}}",
|
"upstream_dns_configured_in_file": "Configurado en {{path}}",
|
||||||
"test_upstream_btn": "Probar DNS de subida",
|
"test_upstream_btn": "Probar proveedores DNS",
|
||||||
"upstreams": "DNS de subida",
|
"upstreams": "Proveedores DNS",
|
||||||
"upstream": "DNS de subida",
|
"upstream": "Proveedor DNS",
|
||||||
"apply_btn": "Aplicar",
|
"apply_btn": "Aplicar",
|
||||||
"disabled_filtering_toast": "Filtrado deshabilitado",
|
"disabled_filtering_toast": "Filtrado deshabilitado",
|
||||||
"enabled_filtering_toast": "Filtrado habilitado",
|
"enabled_filtering_toast": "Filtrado habilitado",
|
||||||
@@ -233,11 +233,11 @@
|
|||||||
"example_upstream_tcp_port": "DNS regular (mediante TCP, con puerto).",
|
"example_upstream_tcp_port": "DNS regular (mediante TCP, con puerto).",
|
||||||
"example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).",
|
"example_upstream_tcp_hostname": "DNS regular (mediante TCP, nombre del host).",
|
||||||
"all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
|
"all_lists_up_to_date_toast": "Todas las listas ya están actualizadas",
|
||||||
"updated_upstream_dns_toast": "Servidores DNS de subida guardados correctamente",
|
"updated_upstream_dns_toast": "Proveedores DNS guardados correctamente",
|
||||||
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
|
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
|
||||||
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
|
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revisa si lo has escrito correctamente",
|
||||||
"dns_test_parsing_error_toast": "No se pudo utilizar la sección {{section}}: línea {{line}}:, verifica si la escribiste correctamente",
|
"dns_test_parsing_error_toast": "No se pudo utilizar la sección {{section}}: línea {{line}}:, verifica si la escribiste correctamente",
|
||||||
"dns_test_warning_toast": "DNS de subida \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
|
"dns_test_warning_toast": "Proveedor DNS \"{{key}}\" no responde a las peticiones de prueba y es posible que no funcione correctamente",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
"block": "Bloquear",
|
"block": "Bloquear",
|
||||||
"disallow_this_client": "No permitir a este cliente",
|
"disallow_this_client": "No permitir a este cliente",
|
||||||
@@ -294,9 +294,9 @@
|
|||||||
"blocked_response_ttl": "Respuesta TTL bloqueada",
|
"blocked_response_ttl": "Respuesta TTL bloqueada",
|
||||||
"blocked_response_ttl_desc": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
|
"blocked_response_ttl_desc": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
|
||||||
"form_enter_blocked_response_ttl": "Ingresa el TTL de respuesta bloqueada (segundos)",
|
"form_enter_blocked_response_ttl": "Ingresa el TTL de respuesta bloqueada (segundos)",
|
||||||
"upstream_timeout": "Tiempo de espera del upstream",
|
"upstream_timeout": "Tiempo de espera del proveedor DNS",
|
||||||
"upstream_timeout_desc": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream",
|
"upstream_timeout_desc": "Especifica el número de segundos que se debe esperar para recibir una respuesta del proveedor DNS",
|
||||||
"form_enter_upstream_timeout": "Ingresa la duración del tiempo de espera del servidor DNS upstream en segundos",
|
"form_enter_upstream_timeout": "Ingresa la duración de tiempo de espera del proveedor DNS en segundos",
|
||||||
"dnscrypt": "DNSCrypt",
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS mediante HTTPS",
|
"dns_over_https": "DNS mediante HTTPS",
|
||||||
"dns_over_tls": "DNS mediante TLS",
|
"dns_over_tls": "DNS mediante TLS",
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
"form_enter_rate_limit": "Ingresa el límite de cantidad",
|
"form_enter_rate_limit": "Ingresa el límite de cantidad",
|
||||||
"rate_limit": "Límite de cantidad",
|
"rate_limit": "Límite de cantidad",
|
||||||
"edns_enable": "Habilitar subred de cliente EDNS",
|
"edns_enable": "Habilitar subred de cliente EDNS",
|
||||||
"edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del DNS de subida y registra los valores enviados por los clientes en el registro de consultas.",
|
"edns_cs_desc": "Añade la opción subred de cliente EDNS (ECS) a las peticiones del proveedor DNS y registra los valores enviados por los clientes en el registro de consultas.",
|
||||||
"edns_use_custom_ip": "Usar IP personalizada para EDNS",
|
"edns_use_custom_ip": "Usar IP personalizada para EDNS",
|
||||||
"edns_use_custom_ip_desc": "Permitir el uso de IP personalizadas para EDNS",
|
"edns_use_custom_ip_desc": "Permitir el uso de IP personalizadas para EDNS",
|
||||||
"rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.",
|
"rate_limit_desc": "Número de peticiones por segundo permitidas por cliente. Establecerlo en 0 significa que no hay límite.",
|
||||||
@@ -335,7 +335,7 @@
|
|||||||
"theme_auto": "Auto",
|
"theme_auto": "Auto",
|
||||||
"theme_light": "Claro",
|
"theme_light": "Claro",
|
||||||
"theme_dark": "Oscuro",
|
"theme_dark": "Oscuro",
|
||||||
"upstream_dns_client_desc": "Si se mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración del DNS</0>.",
|
"upstream_dns_client_desc": "Si se mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración DNS</0>.",
|
||||||
"tracker_source": "Fuente del rastreador",
|
"tracker_source": "Fuente del rastreador",
|
||||||
"source_label": "Fuente",
|
"source_label": "Fuente",
|
||||||
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
|
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
|
||||||
@@ -596,12 +596,12 @@
|
|||||||
"example_rewrite_wildcard": "reescribe las respuestas para todos los subdominios de <0>ejemplo.org</0>.",
|
"example_rewrite_wildcard": "reescribe las respuestas para todos los subdominios de <0>ejemplo.org</0>.",
|
||||||
"rewrite_ip_address": "Dirección IP: utiliza esta IP en una respuesta A o AAAA",
|
"rewrite_ip_address": "Dirección IP: utiliza esta IP en una respuesta A o AAAA",
|
||||||
"rewrite_domain_name": "Nombre de dominio: añade un registro CNAME",
|
"rewrite_domain_name": "Nombre de dominio: añade un registro CNAME",
|
||||||
"rewrite_A": "<0>A</0>: valor especial, mantiene registros <0>A</0> del DNS de subida",
|
"rewrite_A": "<0>A</0>: valor especial, mantiene registros <0>A</0> del proveedor DNS",
|
||||||
"rewrite_AAAA": "<0>AAAA</0>: valor especial, mantiene registros <0>AAAA</0> del DNS de subida",
|
"rewrite_AAAA": "<0>AAAA</0>: valor especial, mantiene registros <0>AAAA</0> del proveedor DNS",
|
||||||
"disable_ipv6": "Deshabilitar resolución de direcciones IPv6",
|
"disable_ipv6": "Deshabilitar resolución de direcciones IPv6",
|
||||||
"disable_ipv6_desc": "Descarta todas las consultas de DNS para direcciones IPv6 (tipo AAAA) y elimina las sugerencias de IPv6 de las respuestas HTTPS.",
|
"disable_ipv6_desc": "Descarta todas las consultas de DNS para direcciones IPv6 (tipo AAAA) y elimina las sugerencias de IPv6 de las respuestas HTTPS.",
|
||||||
"fastest_addr": "Dirección IP más rápida",
|
"fastest_addr": "Dirección IP más rápida",
|
||||||
"fastest_addr_desc": "Espera a que respondan <b>todos</b> los servidores DNS, mide la velocidad de conexión TCP de cada servidor y devuelve la Dirección IP del servidor con la velocidad de conexión más rápida.<br/>Este modo puede ralentizar significativamente las consultas DNS, si uno o más servidores DNS de upstream no están respondiendo. Asegúrate de que tus servidores DNS upstream sean estables y tu tiempo de espera de upstream sea bajo.",
|
"fastest_addr_desc": "Espera respuestas de <b>todos</b> los servidores DNS, mide la velocidad de conexión TCP de cada servidor y devuelve la dirección IP del servidor con la velocidad de conexión más rápida.<br/>Este modo puede ralentizar significativamente las consultas DNS, si uno o más proveedores DNS no responden. Asegúrate de que tus proveedores DNS sean estables y de que el tiempo de espera tu proveedor DNS sea bajo.",
|
||||||
"autofix_warning_text": "Si haces clic en \"Corregir\", AdGuard Home configurará tu sistema para utilizar el servidor DNS de AdGuard Home.",
|
"autofix_warning_text": "Si haces clic en \"Corregir\", AdGuard Home configurará tu sistema para utilizar el servidor DNS de AdGuard Home.",
|
||||||
"autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener</0> <0>Establecer la dirección del servidor DNS en 127.0.0.1</0> <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf</0> <0>Detener DNSStubListener (recargar el servicio systemd-resolved)</0>",
|
"autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener</0> <0>Establecer la dirección del servidor DNS en 127.0.0.1</0> <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf</0> <0>Detener DNSStubListener (recargar el servicio systemd-resolved)</0>",
|
||||||
"autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.",
|
"autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.",
|
||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Razón: {{reason}}",
|
"check_reason": "Razón: {{reason}}",
|
||||||
"check_service": "Nombre del servicio: {{service}}",
|
"check_service": "Nombre del servicio: {{service}}",
|
||||||
|
"check_hostname": "Nombre de host o nombre de dominio",
|
||||||
|
"check_client_id": "Identificador del cliente (ClientID o dirección IP)",
|
||||||
|
"check_enter_client_id": "Ingresa el identificador del cliente",
|
||||||
|
"check_dns_record": "Selecciona el tipo de registro DNS",
|
||||||
"service_name": "Nombre del servicio",
|
"service_name": "Nombre del servicio",
|
||||||
"check_not_found": "No se ha encontrado en tus listas de filtros",
|
"check_not_found": "No se ha encontrado en tus listas de filtros",
|
||||||
"client_confirm_block": "¿Estás seguro de que deseas bloquear al cliente \"{{ip}}\"?",
|
"client_confirm_block": "¿Estás seguro de que deseas bloquear al cliente \"{{ip}}\"?",
|
||||||
@@ -658,7 +662,7 @@
|
|||||||
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
|
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
|
||||||
"enter_cache_ttl_min_override": "Ingresa el TTL mínimo (en segundos)",
|
"enter_cache_ttl_min_override": "Ingresa el TTL mínimo (en segundos)",
|
||||||
"enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)",
|
"enter_cache_ttl_max_override": "Ingresa el TTL máximo (en segundos)",
|
||||||
"cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del servidor DNS de subida al almacenar en caché las respuestas DNS.",
|
"cache_ttl_min_override_desc": "Amplía el corto tiempo de vida (segundos) de los valores recibidos del proveedor DNS al almacenar en caché las respuestas DNS.",
|
||||||
"cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.",
|
"cache_ttl_max_override_desc": "Establece un valor de tiempo de vida (segundos) máximo para las entradas en la caché DNS.",
|
||||||
"ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo",
|
"ttl_cache_validation": "La anulación TTL mínimo de la caché debe ser menor o igual al máximo",
|
||||||
"cache_optimistic": "Caché optimista",
|
"cache_optimistic": "Caché optimista",
|
||||||
@@ -744,7 +748,7 @@
|
|||||||
"thursday_short": "Jue.",
|
"thursday_short": "Jue.",
|
||||||
"friday_short": "Vie.",
|
"friday_short": "Vie.",
|
||||||
"saturday_short": "Sáb.",
|
"saturday_short": "Sáb.",
|
||||||
"upstream_dns_cache_configuration": "Configuración de la caché DNS upstream",
|
"upstream_dns_cache_configuration": "Configuración de la caché del proveedor DNS",
|
||||||
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché de DNS para la configuración personalizada de este cliente",
|
"enable_upstream_dns_cache": "Habilitar el almacenamiento en caché del DNS para la configuración personalizada de este cliente",
|
||||||
"dns_cache_size": "Tamaño de la caché DNS, en bytes"
|
"dns_cache_size": "Tamaño de la caché DNS, en bytes"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME : {{cname}}",
|
"check_cname": "CNAME : {{cname}}",
|
||||||
"check_reason": "Raison : {{reason}}",
|
"check_reason": "Raison : {{reason}}",
|
||||||
"check_service": "Nom du service : {{service}}",
|
"check_service": "Nom du service : {{service}}",
|
||||||
|
"check_hostname": "Nom d'hôte ou nom de domaine",
|
||||||
|
"check_client_id": "Identifiant du client (ClientID ou adresse IP)",
|
||||||
|
"check_enter_client_id": "Saisissez l'identifiant du client",
|
||||||
|
"check_dns_record": "Sélectionnez le type d'enregistrement DNS",
|
||||||
"service_name": "Nom du service",
|
"service_name": "Nom du service",
|
||||||
"check_not_found": "Introuvable dans vos listes de filtres",
|
"check_not_found": "Introuvable dans vos listes de filtres",
|
||||||
"client_confirm_block": "Voulez-vous vraiment bloquer le client « {{ip}} » ?",
|
"client_confirm_block": "Voulez-vous vraiment bloquer le client « {{ip}} » ?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_service": "Nome servizio: {{service}}",
|
"check_service": "Nome servizio: {{service}}",
|
||||||
|
"check_hostname": "Nome host o nome di dominio",
|
||||||
|
"check_client_id": "Identificatore client (ClientID o indirizzo IP)",
|
||||||
|
"check_enter_client_id": "Inserisci identificatore client",
|
||||||
|
"check_dns_record": "Seleziona il tipo di registrazione DNS",
|
||||||
"service_name": "Nome servizio",
|
"service_name": "Nome servizio",
|
||||||
"check_not_found": "Non trovato negli elenchi dei filtri",
|
"check_not_found": "Non trovato negli elenchi dei filtri",
|
||||||
"client_confirm_block": "Sei sicuro di voler bloccare il client \"{{ip}}\"?",
|
"client_confirm_block": "Sei sicuro di voler bloccare il client \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "理由: {{reason}}",
|
"check_reason": "理由: {{reason}}",
|
||||||
"check_service": "サービス名: {{service}}",
|
"check_service": "サービス名: {{service}}",
|
||||||
|
"check_hostname": "ホスト名またはドメイン名",
|
||||||
|
"check_client_id": "クライアント識別子 (ClientID または IP アドレス)",
|
||||||
|
"check_enter_client_id": "クライアント識別子を入力してください",
|
||||||
|
"check_dns_record": "DNSレコードタイプ(DNS record type)を選択",
|
||||||
"service_name": "サービス名",
|
"service_name": "サービス名",
|
||||||
"check_not_found": "フィルタ一覧には見つかりません",
|
"check_not_found": "フィルタ一覧には見つかりません",
|
||||||
"client_confirm_block": "クライアント\"{{ip}}\"をブロックしてもよろしいですか?",
|
"client_confirm_block": "クライアント\"{{ip}}\"をブロックしてもよろしいですか?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "이유: {{reason}}",
|
"check_reason": "이유: {{reason}}",
|
||||||
"check_service": "서비스 이름: {{service}}",
|
"check_service": "서비스 이름: {{service}}",
|
||||||
|
"check_hostname": "호스트 이름 또는 도메인 이름",
|
||||||
|
"check_client_id": "클라이언트 식별자(클라이언트 ID 또는 IP 주소)",
|
||||||
|
"check_enter_client_id": "클라이언트 식별자 입력",
|
||||||
|
"check_dns_record": "DNS 레코드 유형 선택",
|
||||||
"service_name": "서비스 이름",
|
"service_name": "서비스 이름",
|
||||||
"check_not_found": "필터 목록에서 찾을 수 없음",
|
"check_not_found": "필터 목록에서 찾을 수 없음",
|
||||||
"client_confirm_block": "정말로 클라이언트 '{{ip}}'을(를) 차단하시겠습니까?",
|
"client_confirm_block": "정말로 클라이언트 '{{ip}}'을(를) 차단하시겠습니까?",
|
||||||
|
|||||||
@@ -110,9 +110,9 @@
|
|||||||
"homepage": "Startpagina",
|
"homepage": "Startpagina",
|
||||||
"report_an_issue": "Rapporteer een probleem",
|
"report_an_issue": "Rapporteer een probleem",
|
||||||
"privacy_policy": "Privacybeleid",
|
"privacy_policy": "Privacybeleid",
|
||||||
"enable_protection": "Schakel bescherming in",
|
"enable_protection": "Bescherming inschakelen",
|
||||||
"enabled_protection": "Bescherming ingeschakeld",
|
"enabled_protection": "Bescherming ingeschakeld",
|
||||||
"disable_protection": "Schakel bescherming uit",
|
"disable_protection": "Bescherming uitschakelen",
|
||||||
"disabled_protection": "Bescherming uitgeschakeld",
|
"disabled_protection": "Bescherming uitgeschakeld",
|
||||||
"refresh_statics": "Ververs statistieken",
|
"refresh_statics": "Ververs statistieken",
|
||||||
"dns_query": "DNS-queries",
|
"dns_query": "DNS-queries",
|
||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Reden: {{reason}}",
|
"check_reason": "Reden: {{reason}}",
|
||||||
"check_service": "Servicenaam: {{service}}",
|
"check_service": "Servicenaam: {{service}}",
|
||||||
|
"check_hostname": "Hostnaam of domeinnaam",
|
||||||
|
"check_client_id": "Client identificator (ClientID of IP-adres)",
|
||||||
|
"check_enter_client_id": "Voer Client identificator in",
|
||||||
|
"check_dns_record": "Selecteer type DNS-record",
|
||||||
"service_name": "Naam service",
|
"service_name": "Naam service",
|
||||||
"check_not_found": "Niet in je lijst met filters gevonden",
|
"check_not_found": "Niet in je lijst met filters gevonden",
|
||||||
"client_confirm_block": "Weet je zeker dat je client \"{{ip}}\" wil blokkeren?",
|
"client_confirm_block": "Weet je zeker dat je client \"{{ip}}\" wil blokkeren?",
|
||||||
@@ -698,13 +702,13 @@
|
|||||||
"disable_for_hours": "Voor {{count}} uur",
|
"disable_for_hours": "Voor {{count}} uur",
|
||||||
"disable_for_hours_plural": "Voor {{count}} uren",
|
"disable_for_hours_plural": "Voor {{count}} uren",
|
||||||
"disable_until_tomorrow": "Tot morgen",
|
"disable_until_tomorrow": "Tot morgen",
|
||||||
"disable_notify_for_seconds": "Beveiliging uitschakelen voor {{count}} seconde",
|
"disable_notify_for_seconds": "Bescherming uitschakelen voor {{count}} seconde",
|
||||||
"disable_notify_for_seconds_plural": "Beveiliging uitschakelen voor {{count}} seconden",
|
"disable_notify_for_seconds_plural": "Bescherming uitschakelen voor {{count}} seconden",
|
||||||
"disable_notify_for_minutes": "Beveiliging uitschakelen voor {{count}} minuut",
|
"disable_notify_for_minutes": "Bescherming uitschakelen voor {{count}} minuut",
|
||||||
"disable_notify_for_minutes_plural": "Beveiliging uitschakelen voor {{count}} minuten",
|
"disable_notify_for_minutes_plural": "Bescherming uitschakelen voor {{count}} minuten",
|
||||||
"disable_notify_for_hours": "Beveiliging uitschakelen voor {{count}} uur",
|
"disable_notify_for_hours": "Bescherming uitschakelen voor {{count}} uur",
|
||||||
"disable_notify_for_hours_plural": "Beveiliging uitschakelen voor {{count}} uren",
|
"disable_notify_for_hours_plural": "Bescherming uitschakelen voor {{count}} uren",
|
||||||
"disable_notify_until_tomorrow": "Beveiliging uitschakelen tot morgen",
|
"disable_notify_until_tomorrow": "Bescherming uitschakelen tot morgen",
|
||||||
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}",
|
"enable_protection_timer": "Bescherming wordt ingeschakeld over {{time}}",
|
||||||
"custom_retention_input": "Voer retentie in uren in",
|
"custom_retention_input": "Voer retentie in uren in",
|
||||||
"custom_rotation_input": "Voer rotatie in uren in",
|
"custom_rotation_input": "Voer rotatie in uren in",
|
||||||
|
|||||||
@@ -106,7 +106,6 @@
|
|||||||
"stats_malware_phishing": "Blokkert skadevare/phishing",
|
"stats_malware_phishing": "Blokkert skadevare/phishing",
|
||||||
"stats_adult": "Blokkerte voksennettsteder",
|
"stats_adult": "Blokkerte voksennettsteder",
|
||||||
"stats_query_domain": "Mest forespurte domener",
|
"stats_query_domain": "Mest forespurte domener",
|
||||||
"for_last_24_hours": "de siste 24 timene",
|
|
||||||
"for_last_days": "for den siste {{count}} dagen",
|
"for_last_days": "for den siste {{count}} dagen",
|
||||||
"for_last_days_plural": "de siste {{count}} dagene",
|
"for_last_days_plural": "de siste {{count}} dagene",
|
||||||
"stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
|
"stats_disabled": "Statistikkene har blitt skrudd av. Du kan skru den på fra <0>innstillingssiden</0>.",
|
||||||
@@ -121,7 +120,6 @@
|
|||||||
"no_upstreams_data_found": "Ingen oppstrøms servere data funnet",
|
"no_upstreams_data_found": "Ingen oppstrøms servere data funnet",
|
||||||
"number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene",
|
"number_of_dns_query_days": "Antall DNS-spørringer behandlet for de siste {{count}} dagene",
|
||||||
"number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
|
"number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
|
||||||
"number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
|
|
||||||
"number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
|
"number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
|
||||||
"number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul",
|
"number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul",
|
||||||
"number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert",
|
"number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert",
|
||||||
@@ -266,6 +264,7 @@
|
|||||||
"custom_ip": "Tilpasset IP",
|
"custom_ip": "Tilpasset IP",
|
||||||
"blocking_ipv4": "IPv4-blokkering",
|
"blocking_ipv4": "IPv4-blokkering",
|
||||||
"blocking_ipv6": "IPv6-blokkering",
|
"blocking_ipv6": "IPv6-blokkering",
|
||||||
|
"blocked_response_ttl": "Blokkerte svars TTL",
|
||||||
"dnscrypt": "DNSCrypt",
|
"dnscrypt": "DNSCrypt",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
@@ -627,7 +626,6 @@
|
|||||||
"use_saved_key": "Bruk den tidligere lagrede nøkkelen",
|
"use_saved_key": "Bruk den tidligere lagrede nøkkelen",
|
||||||
"parental_control": "Foreldrekontroll",
|
"parental_control": "Foreldrekontroll",
|
||||||
"safe_browsing": "Sikker surfing",
|
"safe_browsing": "Sikker surfing",
|
||||||
"served_from_cache": "{{value}} <i>(formidlet fra mellomlageret)</i>",
|
|
||||||
"theme_dark_desc": "Mørkt tema",
|
"theme_dark_desc": "Mørkt tema",
|
||||||
"theme_light_desc": "Lyst tema",
|
"theme_light_desc": "Lyst tema",
|
||||||
"disable_notify_until_tomorrow": "Deaktiver beskyttelsen til i morgen",
|
"disable_notify_until_tomorrow": "Deaktiver beskyttelsen til i morgen",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_service": "Nome do serviço: {{service}}",
|
"check_service": "Nome do serviço: {{service}}",
|
||||||
|
"check_hostname": "Nome do anfitrião ou nome de domínio",
|
||||||
|
"check_client_id": "Identificador do cliente (ClienteID ou endereço de IP)",
|
||||||
|
"check_enter_client_id": "Insira o identificador do cliente",
|
||||||
|
"check_dns_record": "Selecione o tipo de registro DNS",
|
||||||
"service_name": "Nome do serviço",
|
"service_name": "Nome do serviço",
|
||||||
"check_not_found": "Não encontrado em suas listas de filtros",
|
"check_not_found": "Não encontrado em suas listas de filtros",
|
||||||
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
|
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Motivo: {{reason}}",
|
"check_reason": "Motivo: {{reason}}",
|
||||||
"check_service": "Nome do serviço: {{service}}",
|
"check_service": "Nome do serviço: {{service}}",
|
||||||
|
"check_hostname": "Nome do hospedeiro ou nome de domínio",
|
||||||
|
"check_client_id": "Identificador do cliente (ClientID ou endereço IP)",
|
||||||
|
"check_enter_client_id": "Insira o identificador do cliente",
|
||||||
|
"check_dns_record": "Selecione o tipo de registro DNS",
|
||||||
"service_name": "Nome do serviço",
|
"service_name": "Nome do serviço",
|
||||||
"check_not_found": "Não encontrado nas tuas listas de filtros",
|
"check_not_found": "Não encontrado nas tuas listas de filtros",
|
||||||
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
|
"client_confirm_block": "Você tem certeza de que deseja bloquear o cliente \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Причина: {{reason}}",
|
"check_reason": "Причина: {{reason}}",
|
||||||
"check_service": "Название сервиса: {{service}}",
|
"check_service": "Название сервиса: {{service}}",
|
||||||
|
"check_hostname": "Имя хоста или домена",
|
||||||
|
"check_client_id": "Идентификатор клиента (ClientID или IP-адрес)",
|
||||||
|
"check_enter_client_id": "Введите идентификатор клиента",
|
||||||
|
"check_dns_record": "Выберите тип DNS-записи",
|
||||||
"service_name": "Имя сервиса",
|
"service_name": "Имя сервиса",
|
||||||
"check_not_found": "Не найдено в вашем списке фильтров",
|
"check_not_found": "Не найдено в вашем списке фильтров",
|
||||||
"client_confirm_block": "Вы уверены, что хотите заблокировать клиента «{{ip}}»?",
|
"client_confirm_block": "Вы уверены, что хотите заблокировать клиента «{{ip}}»?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Dôvod: {{reason}}",
|
"check_reason": "Dôvod: {{reason}}",
|
||||||
"check_service": "Meno služby: {{service}}",
|
"check_service": "Meno služby: {{service}}",
|
||||||
|
"check_hostname": "Názov hostiteľa alebo názov domény",
|
||||||
|
"check_client_id": "Identifikátor klienta (ClientID alebo IP adresa)",
|
||||||
|
"check_enter_client_id": "Zadajte identifikátor klienta",
|
||||||
|
"check_dns_record": "Vyberte typ DNS záznamu",
|
||||||
"service_name": "Názov služby",
|
"service_name": "Názov služby",
|
||||||
"check_not_found": "Nenašlo sa vo Vašom zozname filtrov",
|
"check_not_found": "Nenašlo sa vo Vašom zozname filtrov",
|
||||||
"client_confirm_block": "Naozaj chcete zablokovať klienta \"{{ip}}\"?",
|
"client_confirm_block": "Naozaj chcete zablokovať klienta \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Sebep: {{reason}}",
|
"check_reason": "Sebep: {{reason}}",
|
||||||
"check_service": "Hizmet adı: {{service}}",
|
"check_service": "Hizmet adı: {{service}}",
|
||||||
|
"check_hostname": "Ana makine adı veya alan adı",
|
||||||
|
"check_client_id": "İstemci tanımlayıcısı (ClientID veya IP adresi)",
|
||||||
|
"check_enter_client_id": "İstemci tanımlayıcısı girin",
|
||||||
|
"check_dns_record": "DNS kayıt türünü seçin",
|
||||||
"service_name": "Hizmet adı",
|
"service_name": "Hizmet adı",
|
||||||
"check_not_found": "Filtre listelerinizde bulunamadı",
|
"check_not_found": "Filtre listelerinizde bulunamadı",
|
||||||
"client_confirm_block": "\"{{ip}}\" istemcisini engellemek istediğinizden emin misiniz?",
|
"client_confirm_block": "\"{{ip}}\" istemcisini engellemek istediğinizden emin misiniz?",
|
||||||
|
|||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "原因:{{reason}}",
|
"check_reason": "原因:{{reason}}",
|
||||||
"check_service": "服务名称:{{service}}",
|
"check_service": "服务名称:{{service}}",
|
||||||
|
"check_hostname": "主机名或域名",
|
||||||
|
"check_client_id": "客户端标识符(ClientID 或 IP 地址)",
|
||||||
|
"check_enter_client_id": "输入客户端标识符",
|
||||||
|
"check_dns_record": "选择 DNS 记录类型",
|
||||||
"service_name": "服务名称",
|
"service_name": "服务名称",
|
||||||
"check_not_found": "未在您的筛选列表中找到",
|
"check_not_found": "未在您的筛选列表中找到",
|
||||||
"client_confirm_block": "确定要阻止客户端 \"{{ip}}\"?",
|
"client_confirm_block": "确定要阻止客户端 \"{{ip}}\"?",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"client_settings": "用戶端設定",
|
"client_settings": "用戶端設定",
|
||||||
"example_upstream_reserved": "<0>供特定的網域</0>之上游;",
|
"example_upstream_reserved": "<0>特定網域</0>的上游;",
|
||||||
"example_multiple_upstreams_reserved": "<0>特定網域</0>的多個上游伺服器;",
|
"example_multiple_upstreams_reserved": "<0>特定網域</0>的多個上游伺服器;",
|
||||||
"example_upstream_comment": "註解。",
|
"example_upstream_comment": "註解。",
|
||||||
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
|
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
||||||
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
||||||
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
||||||
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果停用,AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
|
"use_private_ptr_resolvers_desc": "透過私有上游伺服器、DHCP 或 /etc/hosts 等管道,解析含有私有 IP 位址的 ARPA 網域的 PTR、SOA 與 NS 請求。若停用此功能,AdGuard Home 將以 NXDOMAIN 回應所有相關請求。",
|
||||||
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
||||||
"save_config": "儲存配置",
|
"save_config": "儲存配置",
|
||||||
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
||||||
@@ -112,8 +112,8 @@
|
|||||||
"privacy_policy": "隱私政策",
|
"privacy_policy": "隱私政策",
|
||||||
"enable_protection": "啟用防護",
|
"enable_protection": "啟用防護",
|
||||||
"enabled_protection": "已啟用防護",
|
"enabled_protection": "已啟用防護",
|
||||||
"disable_protection": "禁用防護",
|
"disable_protection": "停用防護",
|
||||||
"disabled_protection": "已禁用防護",
|
"disabled_protection": "已停用防護",
|
||||||
"refresh_statics": "重新整理統計資料",
|
"refresh_statics": "重新整理統計資料",
|
||||||
"dns_query": "DNS 查詢",
|
"dns_query": "DNS 查詢",
|
||||||
"blocked_by": "<0>被過濾器封鎖</0>",
|
"blocked_by": "<0>被過濾器封鎖</0>",
|
||||||
@@ -124,8 +124,8 @@
|
|||||||
"for_last_hours_plural": "在過去的 {{count}} 小時內",
|
"for_last_hours_plural": "在過去的 {{count}} 小時內",
|
||||||
"for_last_days": "在最近的 {{count}} 日內",
|
"for_last_days": "在最近的 {{count}} 日內",
|
||||||
"for_last_days_plural": "在最近的 {{count}} 日內",
|
"for_last_days_plural": "在最近的 {{count}} 日內",
|
||||||
"stats_disabled": "該統計資料已被禁用。您可從<0>設定頁面</0>中打開它。",
|
"stats_disabled": "統計功能目前停用中,請至<0>設定頁面</0>重新開啟。",
|
||||||
"stats_disabled_short": "該統計資料已被禁用",
|
"stats_disabled_short": "該統計資料已停用",
|
||||||
"no_domains_found": "無已發現之網域",
|
"no_domains_found": "無已發現之網域",
|
||||||
"requests_count": "請求總數",
|
"requests_count": "請求總數",
|
||||||
"top_blocked_domains": "熱門已封鎖的網域",
|
"top_blocked_domains": "熱門已封鎖的網域",
|
||||||
@@ -172,13 +172,13 @@
|
|||||||
"upstreams": "上游",
|
"upstreams": "上游",
|
||||||
"upstream": "上游伺服器",
|
"upstream": "上游伺服器",
|
||||||
"apply_btn": "套用",
|
"apply_btn": "套用",
|
||||||
"disabled_filtering_toast": "已禁用過濾",
|
"disabled_filtering_toast": "已停用過濾",
|
||||||
"enabled_filtering_toast": "已啟用過濾",
|
"enabled_filtering_toast": "已啟用過濾",
|
||||||
"disabled_safe_browsing_toast": "已禁用安全瀏覽",
|
"disabled_safe_browsing_toast": "已停用安全瀏覽",
|
||||||
"enabled_safe_browsing_toast": "已啟用安全瀏覽",
|
"enabled_safe_browsing_toast": "已啟用安全瀏覽",
|
||||||
"disabled_parental_toast": "已禁用家長控制",
|
"disabled_parental_toast": "已停用家長控制",
|
||||||
"enabled_parental_toast": "已啟用家長控制",
|
"enabled_parental_toast": "已啟用家長控制",
|
||||||
"disabled_safe_search_toast": "已禁用安全搜尋",
|
"disabled_safe_search_toast": "已停用安全搜尋",
|
||||||
"enabled_save_search_toast": "已啟用安全搜尋",
|
"enabled_save_search_toast": "已啟用安全搜尋",
|
||||||
"updated_save_search_toast": "安全搜尋設定更新成功",
|
"updated_save_search_toast": "安全搜尋設定更新成功",
|
||||||
"enabled_table_header": "已啟用",
|
"enabled_table_header": "已啟用",
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
"query_log_retention": "查詢記錄保留時間",
|
"query_log_retention": "查詢記錄保留時間",
|
||||||
"query_log_enable": "啟用記錄",
|
"query_log_enable": "啟用記錄",
|
||||||
"query_log_configuration": "記錄配置",
|
"query_log_configuration": "記錄配置",
|
||||||
"query_log_disabled": "查詢記錄被禁用並可在<0>設定</0>中被配置",
|
"query_log_disabled": "查詢記錄功能已停用,請至「<0>設定</0>」調整",
|
||||||
"query_log_strict_search": "使用雙引號於嚴謹的搜尋",
|
"query_log_strict_search": "使用雙引號於嚴謹的搜尋",
|
||||||
"query_log_retention_confirm": "您確定要更改記錄檔保存期限嗎?如果您縮短期限部分資料可能將會遺失",
|
"query_log_retention_confirm": "您確定要更改記錄檔保存期限嗎?如果您縮短期限部分資料可能將會遺失",
|
||||||
"anonymize_client_ip": "將用戶端 IP 匿名",
|
"anonymize_client_ip": "將用戶端 IP 匿名",
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
"encryption_config_saved": "加密配置被儲存",
|
"encryption_config_saved": "加密配置被儲存",
|
||||||
"encryption_server": "伺服器名稱",
|
"encryption_server": "伺服器名稱",
|
||||||
"encryption_server_enter": "輸入您的域名",
|
"encryption_server_enter": "輸入您的域名",
|
||||||
"encryption_server_desc": "如果被設定,AdGuard Home 檢測用戶端 IDs,回覆 DDR 查詢,並執行額外的連線驗證。如果未被設定,這些功能被禁用。必須與在該憑證裡的 DNS 名稱其中之一相符。",
|
"encryption_server_desc": "如果設定,AdGuard Home 會偵測 ClientID、回應 DDR 查詢,並執行其他連線驗證。如果未設定,則會停用這些功能。必須符合憑證中的一個 DNS 名稱。",
|
||||||
"encryption_redirect": "自動地重新導向到 HTTPS",
|
"encryption_redirect": "自動地重新導向到 HTTPS",
|
||||||
"encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
|
"encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
|
||||||
"encryption_https": "HTTPS 連接埠",
|
"encryption_https": "HTTPS 連接埠",
|
||||||
@@ -429,8 +429,8 @@
|
|||||||
"encryption_reset": "您確定您想要重置加密設定嗎?",
|
"encryption_reset": "您確定您想要重置加密設定嗎?",
|
||||||
"encryption_warning": "警告",
|
"encryption_warning": "警告",
|
||||||
"encryption_plain_dns_enable": "啟用一般的 DNS",
|
"encryption_plain_dns_enable": "啟用一般的 DNS",
|
||||||
"encryption_plain_dns_desc": "預設情況下啟用一般的 DNS。使用者可以禁用它,強制所有裝置使用一般的 DNS。為此,必須至少啟用一個一般的 DNS 協定。",
|
"encryption_plain_dns_desc": "預設啟用一般 DNS。您可以停用它以強制所有裝置使用加密 DNS。若要這樣做,您必須啟用至少一個加密 DNS 通訊協定",
|
||||||
"encryption_plain_dns_error": "要禁用一般的 DNS,請至少啟用一個一般的 DNS 協定",
|
"encryption_plain_dns_error": "若要停用一般 DNS,請啟用至少一個加密 DNS 通訊協定",
|
||||||
"topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
|
"topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
|
||||||
"topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
|
"topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
|
||||||
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
|
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
"filters_configuration": "過濾器配置",
|
"filters_configuration": "過濾器配置",
|
||||||
"filters_enable": "啟用過濾器",
|
"filters_enable": "啟用過濾器",
|
||||||
"filters_interval": "過濾器更新間隔",
|
"filters_interval": "過濾器更新間隔",
|
||||||
"disabled": "已禁用",
|
"disabled": "已停用",
|
||||||
"username_label": "使用者名稱",
|
"username_label": "使用者名稱",
|
||||||
"username_placeholder": "輸入使用者名稱",
|
"username_placeholder": "輸入使用者名稱",
|
||||||
"password_label": "密碼",
|
"password_label": "密碼",
|
||||||
@@ -598,7 +598,7 @@
|
|||||||
"rewrite_domain_name": "域名:新增一筆正規名稱(CNAME)記錄",
|
"rewrite_domain_name": "域名:新增一筆正規名稱(CNAME)記錄",
|
||||||
"rewrite_A": "<0>A</0>:特殊的數值,阻止 <0>A</0> 記錄免於該上游",
|
"rewrite_A": "<0>A</0>:特殊的數值,阻止 <0>A</0> 記錄免於該上游",
|
||||||
"rewrite_AAAA": "<0>AAAA</0>:特殊的數值,阻止 <0>AAAA</0> 記錄免於該上游",
|
"rewrite_AAAA": "<0>AAAA</0>:特殊的數值,阻止 <0>AAAA</0> 記錄免於該上游",
|
||||||
"disable_ipv6": "禁用 IPv6 位址之解析",
|
"disable_ipv6": "停用 IPv6 位址解析",
|
||||||
"disable_ipv6_desc": "停止所有對於 IPv6 位址(類型 AAAA)的 DNS 查詢,並從 HTTPS 回應中移除 IPv6 的提示。",
|
"disable_ipv6_desc": "停止所有對於 IPv6 位址(類型 AAAA)的 DNS 查詢,並從 HTTPS 回應中移除 IPv6 的提示。",
|
||||||
"fastest_addr": "最快的 IP 位址",
|
"fastest_addr": "最快的 IP 位址",
|
||||||
"fastest_addr_desc": "等待<b>所有</b> DNS 伺服器的回應,測量每個伺服器的 TCP 連線速度,並返回連線速度最快的伺服器的 IP 位址。<br/>如果一個或多個上游伺服器沒有回應,此模式會顯著減慢 DNS 查詢速度。確保您的上游伺服器穩定且上游超時時間短。",
|
"fastest_addr_desc": "等待<b>所有</b> DNS 伺服器的回應,測量每個伺服器的 TCP 連線速度,並返回連線速度最快的伺服器的 IP 位址。<br/>如果一個或多個上游伺服器沒有回應,此模式會顯著減慢 DNS 查詢速度。確保您的上游伺服器穩定且上游超時時間短。",
|
||||||
@@ -620,6 +620,10 @@
|
|||||||
"check_cname": "正規名稱(CNAME):{{cname}}",
|
"check_cname": "正規名稱(CNAME):{{cname}}",
|
||||||
"check_reason": "原因:{{reason}}",
|
"check_reason": "原因:{{reason}}",
|
||||||
"check_service": "服務名稱:{{service}}",
|
"check_service": "服務名稱:{{service}}",
|
||||||
|
"check_hostname": "主機名稱或域名",
|
||||||
|
"check_client_id": "用戶端識別碼(ClientID 或 IP 位址)",
|
||||||
|
"check_enter_client_id": "輸入用戶識別碼",
|
||||||
|
"check_dns_record": "選擇 DNS 記錄類型",
|
||||||
"service_name": "服務名稱",
|
"service_name": "服務名稱",
|
||||||
"check_not_found": "未在您的過濾器清單中被找到",
|
"check_not_found": "未在您的過濾器清單中被找到",
|
||||||
"client_confirm_block": "您確定您想要封鎖該用戶端 \"{{ip}}\" 嗎?",
|
"client_confirm_block": "您確定您想要封鎖該用戶端 \"{{ip}}\" 嗎?",
|
||||||
@@ -652,7 +656,7 @@
|
|||||||
"blocklist": "封鎖清單",
|
"blocklist": "封鎖清單",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "快取大小",
|
"cache_size": "快取大小",
|
||||||
"cache_size_desc": "DNS 快取大小(以位元組)。要禁用快取,留空。",
|
"cache_size_desc": "DNS 快取大小 (位元組)。若要停用快取,請留空。",
|
||||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
||||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
||||||
"enter_cache_size": "輸入快取大小(位元組)",
|
"enter_cache_size": "輸入快取大小(位元組)",
|
||||||
@@ -677,13 +681,13 @@
|
|||||||
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
|
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
|
||||||
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
|
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
|
||||||
"filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在被允許的用戶端的清單之外。",
|
"filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在被允許的用戶端的清單之外。",
|
||||||
"last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"被允許的用戶端\"清單,無法不允許此用戶端。",
|
"last_rule_in_allowlist": "無法禁止此用戶端,因為排除規則 \"{{disallowed_rule}}\" 會停用「允許的用戶端」清單。",
|
||||||
"use_saved_key": "使用該先前已儲存的金鑰",
|
"use_saved_key": "使用該先前已儲存的金鑰",
|
||||||
"parental_control": "家長控制",
|
"parental_control": "家長控制",
|
||||||
"safe_browsing": "安全瀏覽",
|
"safe_browsing": "安全瀏覽",
|
||||||
"served_from_cache_label": "從快取中",
|
"served_from_cache_label": "從快取中",
|
||||||
"form_error_password_length": "密碼長度必須為 {{min}} 到 {{max}} 個字符",
|
"form_error_password_length": "密碼長度必須為 {{min}} 到 {{max}} 個字符",
|
||||||
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。",
|
"anonymizer_notification": "<0>注意:</0>IP 匿名功能已開啟。您可在<1>一般設定</1>中關閉。",
|
||||||
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
|
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
|
||||||
"cache_cleared": "DNS 快取被成功地清除",
|
"cache_cleared": "DNS 快取被成功地清除",
|
||||||
"clear_cache": "清除快取",
|
"clear_cache": "清除快取",
|
||||||
@@ -698,14 +702,14 @@
|
|||||||
"disable_for_hours": "{{count}} 小時",
|
"disable_for_hours": "{{count}} 小時",
|
||||||
"disable_for_hours_plural": "{{count}} 小時",
|
"disable_for_hours_plural": "{{count}} 小時",
|
||||||
"disable_until_tomorrow": "直到明天",
|
"disable_until_tomorrow": "直到明天",
|
||||||
"disable_notify_for_seconds": "計 {{count}} 秒禁用防護",
|
"disable_notify_for_seconds": "計 {{count}} 秒停用防護",
|
||||||
"disable_notify_for_seconds_plural": "計 {{count}} 秒禁用防護",
|
"disable_notify_for_seconds_plural": "計 {{count}} 秒停用防護",
|
||||||
"disable_notify_for_minutes": "計 {{count}} 分鐘禁用防護",
|
"disable_notify_for_minutes": "計 {{count}} 分鐘停用防護",
|
||||||
"disable_notify_for_minutes_plural": "計 {{count}} 分鐘禁用防護",
|
"disable_notify_for_minutes_plural": "計 {{count}} 分鐘停用防護",
|
||||||
"disable_notify_for_hours": "計 {{count}} 小時禁用防護",
|
"disable_notify_for_hours": "計 {{count}} 小時停用防護",
|
||||||
"disable_notify_for_hours_plural": "計 {{count}} 小時禁用防護",
|
"disable_notify_for_hours_plural": "計 {{count}} 小時停用防護",
|
||||||
"disable_notify_until_tomorrow": "禁用防護直到明天",
|
"disable_notify_until_tomorrow": "停用防護直到明天",
|
||||||
"enable_protection_timer": "防護將於 {{time}} 被啟用",
|
"enable_protection_timer": "防護將於 {{time}} 啟用",
|
||||||
"custom_retention_input": "輸入保留時間(小時)",
|
"custom_retention_input": "輸入保留時間(小時)",
|
||||||
"custom_rotation_input": "輸入旋轉時間(小時)",
|
"custom_rotation_input": "輸入旋轉時間(小時)",
|
||||||
"protection_section_label": "防護",
|
"protection_section_label": "防護",
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ import Info from './Info';
|
|||||||
import { RootState } from '../../../initialState';
|
import { RootState } from '../../../initialState';
|
||||||
import { validateRequiredValue } from '../../../helpers/validators';
|
import { validateRequiredValue } from '../../../helpers/validators';
|
||||||
import { Input } from '../../ui/Controls/Input';
|
import { Input } from '../../ui/Controls/Input';
|
||||||
|
import { DNS_RECORD_TYPES } from '../../../helpers/constants';
|
||||||
|
import { Select } from '../../ui/Controls/Select';
|
||||||
|
|
||||||
interface FormValues {
|
export type FilteringCheckFormValues = {
|
||||||
name: string;
|
name: string;
|
||||||
|
client?: string;
|
||||||
|
qtype?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSubmit?: (data: FormValues) => void;
|
onSubmit?: (data: FilteringCheckFormValues) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Check = ({ onSubmit }: Props) => {
|
const Check = ({ onSubmit }: Props) => {
|
||||||
@@ -27,11 +31,13 @@ const Check = ({ onSubmit }: Props) => {
|
|||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isDirty, isValid },
|
formState: { isValid },
|
||||||
} = useForm<FormValues>({
|
} = useForm<FilteringCheckFormValues>({
|
||||||
mode: 'onBlur',
|
mode: 'onBlur',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
|
client: '',
|
||||||
|
qtype: DNS_RECORD_TYPES[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,24 +54,56 @@ const Check = ({ onSubmit }: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
label={t('check_hostname')}
|
||||||
data-testid="check_domain_name"
|
data-testid="check_domain_name"
|
||||||
placeholder={t('form_enter_host')}
|
placeholder="example.com"
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
rightAddon={
|
|
||||||
<span className="input-group-append">
|
|
||||||
<button
|
|
||||||
className="btn btn-success btn-standard btn-large"
|
|
||||||
type="submit"
|
|
||||||
data-testid="check_domain_submit"
|
|
||||||
disabled={!isDirty || !isValid || processingCheck}>
|
|
||||||
{t('check')}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="client"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="text"
|
||||||
|
data-testid="check_client_id"
|
||||||
|
label={t('check_client_id')}
|
||||||
|
placeholder={t('check_enter_client_id')}
|
||||||
|
error={fieldState.error?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="qtype"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
label={t('check_dns_record')}
|
||||||
|
data-testid="check_dns_record_type"
|
||||||
|
>
|
||||||
|
{DNS_RECORD_TYPES.map((type) => (
|
||||||
|
<option key={type} value={type}>
|
||||||
|
{type}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-success btn-standard btn-large"
|
||||||
|
type="submit"
|
||||||
|
data-testid="check_domain_submit"
|
||||||
|
disabled={!isValid || processingCheck}
|
||||||
|
>
|
||||||
|
{t('check')}
|
||||||
|
</button>
|
||||||
|
|
||||||
{hostname && (
|
{hostname && (
|
||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import PageTitle from '../ui/PageTitle';
|
|||||||
|
|
||||||
import Examples from './Examples';
|
import Examples from './Examples';
|
||||||
|
|
||||||
import Check from './Check';
|
import Check, { FilteringCheckFormValues } from './Check';
|
||||||
|
|
||||||
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
|
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
|
||||||
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
|
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
|
||||||
@@ -48,8 +48,18 @@ class CustomRules extends Component<CustomRulesProps> {
|
|||||||
this.props.setRules(this.props.filtering.userRules);
|
this.props.setRules(this.props.filtering.userRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCheck = (values: any) => {
|
handleCheck = (values: FilteringCheckFormValues) => {
|
||||||
this.props.checkHost(values);
|
const params: FilteringCheckFormValues = { name: values.name };
|
||||||
|
|
||||||
|
if (values.client) {
|
||||||
|
params.client = values.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.qtype) {
|
||||||
|
params.qtype = values.qtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.checkHost(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = (e: any) => syncScroll(e, this.ref);
|
onScroll = (e: any) => syncScroll(e, this.ref);
|
||||||
@@ -68,6 +78,7 @@ class CustomRules extends Component<CustomRulesProps> {
|
|||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<div className="text-edit-container mb-4">
|
<div className="text-edit-container mb-4">
|
||||||
<textarea
|
<textarea
|
||||||
|
data-testid="custom_rule_textarea"
|
||||||
className="form-control font-monospace text-input"
|
className="form-control font-monospace text-input"
|
||||||
value={userRules}
|
value={userRules}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
@@ -81,6 +92,7 @@ class CustomRules extends Component<CustomRulesProps> {
|
|||||||
|
|
||||||
<div className="card-actions">
|
<div className="card-actions">
|
||||||
<button
|
<button
|
||||||
|
data-testid="apply_custom_rule"
|
||||||
className="btn btn-success btn-standard btn-large"
|
className="btn btn-success btn-standard btn-large"
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={this.handleSubmit}>
|
onClick={this.handleSubmit}>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const Header = () => {
|
|||||||
<div className="header__column">
|
<div className="header__column">
|
||||||
<div className="header__right">
|
<div className="header__right">
|
||||||
{!processingProfile && name && (
|
{!processingProfile && name && (
|
||||||
<a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
<a href="control/logout" className="btn btn-sm btn-outline-secondary" data-testid="sign_out">
|
||||||
{t('sign_out')}
|
{t('sign_out')}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ const Row = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className={className} onClick={onClick} role="row">
|
<div style={style} className={className} onClick={onClick} role="row" data-testid="querylog_cell">
|
||||||
<DateCell {...rowProps} />
|
<DateCell {...rowProps} />
|
||||||
|
|
||||||
<DomainCell {...rowProps} />
|
<DomainCell {...rowProps} />
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEBOUNCE_FILTER_TIMEOUT,
|
DEBOUNCE_FILTER_TIMEOUT,
|
||||||
DEFAULT_LOGS_FILTER,
|
DEFAULT_LOGS_FILTER,
|
||||||
@@ -54,9 +56,17 @@ export const Form = ({ className, setIsLoading }: Props) => {
|
|||||||
}
|
}
|
||||||
}, [responseStatusValue, setValue]);
|
}, [responseStatusValue, setValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { search: searchUrlParam } = queryString.parse(history.location.search);
|
||||||
|
|
||||||
|
if (searchUrlParam !== searchValue) {
|
||||||
|
setValue('search', searchUrlParam ? searchUrlParam.toString() : '');
|
||||||
|
}
|
||||||
|
}, [history.location.search]);
|
||||||
|
|
||||||
const onInputClear = async () => {
|
const onInputClear = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setValue('search', DEFAULT_LOGS_FILTER.search);
|
history.push(getLogsUrlParams(DEFAULT_LOGS_FILTER.search, responseStatusValue));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,6 +84,7 @@ export const Form = ({ className, setIsLoading }: Props) => {
|
|||||||
}}>
|
}}>
|
||||||
<div className="field__search">
|
<div className="field__search">
|
||||||
<SearchField
|
<SearchField
|
||||||
|
data-testid="querylog_search"
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
handleChange={(val) => setValue('search', val)}
|
handleChange={(val) => setValue('search', val)}
|
||||||
onKeyDown={onEnterPress}
|
onKeyDown={onEnterPress}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ const SETTINGS = {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
title: i18next.t('use_adguard_browsing_sec'),
|
title: i18next.t('use_adguard_browsing_sec'),
|
||||||
subtitle: i18next.t('use_adguard_browsing_sec_hint'),
|
subtitle: i18next.t('use_adguard_browsing_sec_hint'),
|
||||||
|
testId: 'safebrowsing',
|
||||||
[ORDER_KEY]: 0,
|
[ORDER_KEY]: 0,
|
||||||
},
|
},
|
||||||
parental: {
|
parental: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
title: i18next.t('use_adguard_parental'),
|
title: i18next.t('use_adguard_parental'),
|
||||||
subtitle: i18next.t('use_adguard_parental_hint'),
|
subtitle: i18next.t('use_adguard_parental_hint'),
|
||||||
|
testId: 'parental',
|
||||||
[ORDER_KEY]: 1,
|
[ORDER_KEY]: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -90,11 +92,12 @@ class Settings extends Component<SettingsProps> {
|
|||||||
renderSettings = (settings: any) =>
|
renderSettings = (settings: any) =>
|
||||||
getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => {
|
getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => {
|
||||||
const setting = settings[key];
|
const setting = settings[key];
|
||||||
const { enabled, title, subtitle } = setting;
|
const { enabled, title, subtitle, testId } = setting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key} className="form__group form__group--checkbox">
|
<div key={key} className="form__group form__group--checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
data-testid={testId}
|
||||||
value={enabled}
|
value={enabled}
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
@@ -118,6 +121,7 @@ class Settings extends Component<SettingsProps> {
|
|||||||
<>
|
<>
|
||||||
<div className="form__group form__group--checkbox">
|
<div className="form__group form__group--checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
data-testid="safesearch"
|
||||||
value={enabled}
|
value={enabled}
|
||||||
title={i18next.t('enforce_safe_search')}
|
title={i18next.t('enforce_safe_search')}
|
||||||
subtitle={i18next.t('enforce_save_search_hint')}
|
subtitle={i18next.t('enforce_save_search_hint')}
|
||||||
|
|||||||
@@ -94,14 +94,17 @@ const Footer = () => {
|
|||||||
auto: {
|
auto: {
|
||||||
desc: t('theme_auto_desc'),
|
desc: t('theme_auto_desc'),
|
||||||
icon: '#auto',
|
icon: '#auto',
|
||||||
|
testId: 'theme_auto',
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
desc: t('theme_dark_desc'),
|
desc: t('theme_dark_desc'),
|
||||||
icon: '#dark',
|
icon: '#dark',
|
||||||
|
testId: 'theme_dark',
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
desc: t('theme_light_desc'),
|
desc: t('theme_light_desc'),
|
||||||
icon: '#light',
|
icon: '#light',
|
||||||
|
testId: 'theme_light',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,7 +116,9 @@ const Footer = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm btn-secondary footer__theme-button"
|
className="btn btn-sm btn-secondary footer__theme-button"
|
||||||
onClick={() => onThemeChange(theme)}
|
onClick={() => onThemeChange(theme)}
|
||||||
title={content[theme].desc}>
|
title={content[theme].desc}
|
||||||
|
data-testid={content[theme].testId}
|
||||||
|
>
|
||||||
<svg className={cn('footer__theme-icon', { 'footer__theme-icon--active': currentValue === theme })}>
|
<svg className={cn('footer__theme-icon', { 'footer__theme-icon--active': currentValue === theme })}>
|
||||||
<use xlinkHref={content[theme].icon} />
|
<use xlinkHref={content[theme].icon} />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -523,3 +523,12 @@ export const TIME_UNITS = {
|
|||||||
HOURS: 'hours',
|
HOURS: 'hours',
|
||||||
DAYS: 'days',
|
DAYS: 'days',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DNS_RECORD_TYPES = [
|
||||||
|
"A", "AAAA", "AFSDB", "APL", "CAA", "CDNSKEY", "CDS", "CERT", "CNAME",
|
||||||
|
"CSYNC", "DHCID", "DLV", "DNAME", "DNSKEY", "DS", "EUI48", "EUI64",
|
||||||
|
"HINFO", "HIP", "HTTPS", "IPSECKEY", "KEY", "KX", "LOC", "MX", "NAPTR",
|
||||||
|
"NS", "NSEC", "NSEC3", "NSEC3PARAM", "OPENPGPKEY", "PTR", "RP", "RRSIG",
|
||||||
|
"SIG", "SMIMEA", "SOA", "SRV", "SSHFP", "SVCB", "TA", "TKEY",
|
||||||
|
"TLSA", "TSIG", "TXT", "URI", "ZONEMD"
|
||||||
|
];
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ export default {
|
|||||||
"homepage": "https://badmojr.github.io/1Hosts/",
|
"homepage": "https://badmojr.github.io/1Hosts/",
|
||||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_24.txt"
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_24.txt"
|
||||||
},
|
},
|
||||||
|
"1hosts_pro": {
|
||||||
|
"name": "1Hosts (Pro)",
|
||||||
|
"categoryId": "general",
|
||||||
|
"homepage": "https://badmojr.github.io/1Hosts/",
|
||||||
|
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_64.txt"
|
||||||
|
},
|
||||||
"CHN_adrules": {
|
"CHN_adrules": {
|
||||||
"name": "CHN: AdRules DNS List",
|
"name": "CHN: AdRules DNS List",
|
||||||
"categoryId": "regional",
|
"categoryId": "regional",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"timeUpdated": "2025-03-10T15:13:28.992Z",
|
"timeUpdated": "2025-03-17T10:05:02.622Z",
|
||||||
"categories": {
|
"categories": {
|
||||||
"0": "audio_video_player",
|
"0": "audio_video_player",
|
||||||
"1": "comments",
|
"1": "comments",
|
||||||
@@ -20958,6 +20958,7 @@
|
|||||||
"audiencesquare.com": "audiencesquare.com",
|
"audiencesquare.com": "audiencesquare.com",
|
||||||
"ad.gt": "audiencesquare.com",
|
"ad.gt": "audiencesquare.com",
|
||||||
"audigent.com": "audiencesquare.com",
|
"audigent.com": "audiencesquare.com",
|
||||||
|
"hadronid.net": "audiencesquare.com",
|
||||||
"auditude.com": "auditude",
|
"auditude.com": "auditude",
|
||||||
"audtd.com": "audtd.com",
|
"audtd.com": "audtd.com",
|
||||||
"cdn.augur.io": "augur",
|
"cdn.augur.io": "augur",
|
||||||
@@ -21413,6 +21414,7 @@
|
|||||||
"static.clmbtech.com": "columbia_online",
|
"static.clmbtech.com": "columbia_online",
|
||||||
"combotag.com": "combotag",
|
"combotag.com": "combotag",
|
||||||
"pdk.theplatform.com": "comcast_technology_solutions",
|
"pdk.theplatform.com": "comcast_technology_solutions",
|
||||||
|
"theplatform.com": "comcast_technology_solutions",
|
||||||
"comm100.cn": "comm100",
|
"comm100.cn": "comm100",
|
||||||
"comm100.com": "comm100",
|
"comm100.com": "comm100",
|
||||||
"cdn-cs.com": "commerce_sciences",
|
"cdn-cs.com": "commerce_sciences",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const Form = ({ onSubmit, processing }: LoginFormProps) => {
|
|||||||
{...field}
|
{...field}
|
||||||
data-testid="password"
|
data-testid="password"
|
||||||
type="password"
|
type="password"
|
||||||
label={t('username_label')}
|
label={t('password_label')}
|
||||||
placeholder={t('password_placeholder')}
|
placeholder={t('password_placeholder')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
|
|||||||
34
client/tests/e2e/control-panel.spec.ts
Normal file
34
client/tests/e2e/control-panel.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
test.describe('Control Panel', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/login.html');
|
||||||
|
await page.getByTestId('username').click();
|
||||||
|
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||||
|
await page.getByTestId('password').click();
|
||||||
|
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.getByTestId('sign_in').click();
|
||||||
|
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should sign out successfully', async ({ page }) => {
|
||||||
|
await page.getByTestId('sign_out').click();
|
||||||
|
|
||||||
|
await page.waitForURL((url) => url.href.endsWith('/login.html'));
|
||||||
|
|
||||||
|
await expect(page.getByTestId('sign_in')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should change theme to dark and then light', async ({ page }) => {
|
||||||
|
await page.getByTestId('theme_dark').click();
|
||||||
|
|
||||||
|
await expect(page.locator('body[data-theme="dark"]')).toBeVisible();
|
||||||
|
|
||||||
|
|
||||||
|
await page.getByTestId('theme_light').click();
|
||||||
|
|
||||||
|
await expect(page.locator('body:not([data-theme="dark"])')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
52
client/tests/e2e/dns-settings.spec.ts
Normal file
52
client/tests/e2e/dns-settings.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
test.describe('DNS Settings', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Login before each test
|
||||||
|
await page.goto('/login.html');
|
||||||
|
await page.getByTestId('username').click();
|
||||||
|
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||||
|
await page.getByTestId('password').click();
|
||||||
|
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.getByTestId('sign_in').click();
|
||||||
|
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const runDNSSettingsTest = async (page: Page, address: string) => {
|
||||||
|
await page.goto('/#dns');
|
||||||
|
|
||||||
|
const currentDns = await page.getByTestId('upstream_dns').inputValue();
|
||||||
|
|
||||||
|
await page.getByTestId('upstream_dns').fill(address);
|
||||||
|
await page.getByTestId('dns_upstream_test').click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
await expect(page.getByTestId('upstream_dns')).toHaveValue(address);
|
||||||
|
|
||||||
|
await page.getByTestId('upstream_dns').fill(currentDns);
|
||||||
|
await page.getByTestId('dns_upstream_save').click({ force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
test('test for Default DNS', async ({ page }) => {
|
||||||
|
await runDNSSettingsTest(page, 'https://dns10.quad9.net/dns-query');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test for Plain DNS', async ({ page }) => {
|
||||||
|
await runDNSSettingsTest(page, '94.140.14.140');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test for DNS-over-HTTPS', async ({ page }) => {
|
||||||
|
await runDNSSettingsTest(page, 'https://unfiltered.adguard-dns.com/dns-query');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test for DNS-over-TLS', async ({ page }) => {
|
||||||
|
await runDNSSettingsTest(page, 'tls://unfiltered.adguard-dns.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test for DNS-over-QUIC', async ({ page }) => {
|
||||||
|
await runDNSSettingsTest(page, 'quic://unfiltered.adguard-dns.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
73
client/tests/e2e/filtering.spec.ts
Normal file
73
client/tests/e2e/filtering.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { test, expect, type Page } from '@playwright/test';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
test.describe('Filtering', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Login before each test
|
||||||
|
await page.goto('/login.html');
|
||||||
|
await page.getByTestId('username').click();
|
||||||
|
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||||
|
await page.getByTestId('password').click();
|
||||||
|
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.getByTestId('sign_in').click();
|
||||||
|
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const runTerminalCommand = (command: string) => {
|
||||||
|
try {
|
||||||
|
console.info(`Executing command: ${command}`);
|
||||||
|
|
||||||
|
const output = execSync(command, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
||||||
|
|
||||||
|
console.info('Command executed successfully.');
|
||||||
|
console.debug(`Command output:\n${output}`);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Command execution failed with error:\n${error.message}`);
|
||||||
|
throw new Error(`Failed to execute command: ${command}\nError: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runCustomRuleTest = async (page: Page, domain_to_block: string) => {
|
||||||
|
await page.goto('/#custom_rules');
|
||||||
|
|
||||||
|
await page.getByTestId('custom_rule_textarea').fill(domain_to_block);
|
||||||
|
await page.getByTestId('apply_custom_rule').click();
|
||||||
|
|
||||||
|
const nslookupBlockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
|
||||||
|
|
||||||
|
console.info(`nslookup blocked CNAME result: '${nslookupBlockedResult}'`);
|
||||||
|
|
||||||
|
const currentRules = await page.getByTestId('custom_rule_textarea').inputValue();
|
||||||
|
console.debug(`Current rules before removal:\n${currentRules}`);
|
||||||
|
|
||||||
|
if (currentRules.includes(domain_to_block)) {
|
||||||
|
const updatedRules = currentRules
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line.trim() !== domain_to_block.trim())
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
await page.getByTestId('custom_rule_textarea').fill(updatedRules);
|
||||||
|
console.info(`Rule '${domain_to_block}' removed successfully.`);
|
||||||
|
|
||||||
|
console.info('Applying the updated filtering rules after removal.');
|
||||||
|
await page.getByTestId('apply_custom_rule').click();
|
||||||
|
|
||||||
|
await page.waitForLoadState('domcontentloaded');
|
||||||
|
|
||||||
|
console.info(`Filtering rules successfully updated after removing '${domain_to_block}'.`);
|
||||||
|
} else {
|
||||||
|
console.warn(`Rule '${domain_to_block}' not found. No changes were made.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nslookupUnblockedResult = await runTerminalCommand(`nslookup ${domain_to_block} 127.0.0.1`).toString();
|
||||||
|
console.info(`nslookup unblocked CNAME result: '${nslookupUnblockedResult}'`);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('Test blocking rule for apple.com', async ({ page }) => {
|
||||||
|
await runCustomRuleTest(page, 'apple.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
89
client/tests/e2e/general-settings.spec.ts
Normal file
89
client/tests/e2e/general-settings.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
test.describe('General Settings', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/login.html');
|
||||||
|
await page.getByTestId('username').click();
|
||||||
|
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||||
|
await page.getByTestId('password').click();
|
||||||
|
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.getByTestId('sign_in').click();
|
||||||
|
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should toggle browsing security feature and verify DNS changes', async ({ page }) => {
|
||||||
|
await page.goto('/#settings');
|
||||||
|
|
||||||
|
const browsingSecurity = await page.getByTestId('safebrowsing');
|
||||||
|
const browsingSecurityLabel = await browsingSecurity.locator('xpath=following-sibling::*[1]');
|
||||||
|
|
||||||
|
const initialState = await browsingSecurity.isChecked();
|
||||||
|
|
||||||
|
if (!initialState) {
|
||||||
|
await browsingSecurityLabel.click();
|
||||||
|
await expect(browsingSecurity).toBeChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultEnabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
|
||||||
|
|
||||||
|
await browsingSecurityLabel.click();
|
||||||
|
await expect(browsingSecurity).not.toBeChecked();
|
||||||
|
|
||||||
|
const resultDisabled = execSync('nslookup totalvirus.com 127.0.0.1').toString();
|
||||||
|
|
||||||
|
expect(resultEnabled).not.toEqual(resultDisabled);
|
||||||
|
|
||||||
|
if (initialState) {
|
||||||
|
await browsingSecurityLabel.click();
|
||||||
|
await expect(browsingSecurity).toBeChecked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should toggle parental control feature and verify DNS changes', async ({ page }) => {
|
||||||
|
await page.goto('/#settings');
|
||||||
|
|
||||||
|
const parentalControl = page.getByTestId('parental');
|
||||||
|
const parentalControlLabel = await parentalControl.locator('xpath=following-sibling::*[1]');
|
||||||
|
|
||||||
|
const initialState = await parentalControl.isChecked();
|
||||||
|
|
||||||
|
if (!initialState) {
|
||||||
|
await parentalControlLabel.click();
|
||||||
|
await expect(parentalControl).toBeChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultEnabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
|
||||||
|
|
||||||
|
await parentalControlLabel.click();
|
||||||
|
await expect(parentalControl).not.toBeChecked();
|
||||||
|
|
||||||
|
const resultDisabled = execSync('nslookup pornhub.com 127.0.0.1').toString();
|
||||||
|
|
||||||
|
expect(resultEnabled).not.toEqual(resultDisabled);
|
||||||
|
|
||||||
|
if (initialState) {
|
||||||
|
await parentalControlLabel.click();
|
||||||
|
await expect(parentalControl).toBeChecked();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should toggle safe search feature', async ({ page }) => {
|
||||||
|
await page.goto('/#settings');
|
||||||
|
|
||||||
|
const safeSearch = page.getByTestId('safesearch');
|
||||||
|
const safeSearchLabel = await safeSearch.locator('xpath=following-sibling::*[1]');
|
||||||
|
|
||||||
|
const initialState = await safeSearch.isChecked();
|
||||||
|
|
||||||
|
await safeSearchLabel.click();
|
||||||
|
|
||||||
|
await expect(safeSearch).not.toBeChecked({ checked: initialState });
|
||||||
|
|
||||||
|
await safeSearchLabel.click();
|
||||||
|
|
||||||
|
await expect(safeSearch).toBeChecked({ checked: initialState });
|
||||||
|
});
|
||||||
|
});
|
||||||
124
client/tests/e2e/querylog.spec.ts
Normal file
124
client/tests/e2e/querylog.spec.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { ADMIN_USERNAME, ADMIN_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
test.describe('QueryLog', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/login.html');
|
||||||
|
await page.getByTestId('username').click();
|
||||||
|
await page.getByTestId('username').fill(ADMIN_USERNAME);
|
||||||
|
await page.getByTestId('password').click();
|
||||||
|
await page.getByTestId('password').fill(ADMIN_PASSWORD);
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
await page.getByTestId('sign_in').click();
|
||||||
|
await page.waitForURL((url) => !url.href.endsWith('/login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Search of queryLog should work correctly', async ({ page }) => {
|
||||||
|
await page.route('/control/querylog', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"answer": [
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "77.88.44.242",
|
||||||
|
"ttl": 294
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "5.255.255.242",
|
||||||
|
"ttl": 294
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "77.88.55.242",
|
||||||
|
"ttl": 294
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answer_dnssec": false,
|
||||||
|
"cached": false,
|
||||||
|
"client": "127.0.0.1",
|
||||||
|
"client_info": {
|
||||||
|
"whois": {},
|
||||||
|
"name": "localhost",
|
||||||
|
"disallowed_rule": "127.0.0.1",
|
||||||
|
"disallowed": false
|
||||||
|
},
|
||||||
|
"client_proto": "",
|
||||||
|
"elapsedMs": "78.163167",
|
||||||
|
"question": {
|
||||||
|
"class": "IN",
|
||||||
|
"name": "ya.ru",
|
||||||
|
"type": "A"
|
||||||
|
},
|
||||||
|
"reason": "NotFilteredNotFound",
|
||||||
|
"rules": [],
|
||||||
|
"status": "NOERROR",
|
||||||
|
"time": "2024-07-17T16:02:37.500662+02:00",
|
||||||
|
"upstream": "https://dns10.quad9.net:443/dns-query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer": [
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "77.88.55.242",
|
||||||
|
"ttl": 351
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "77.88.44.242",
|
||||||
|
"ttl": 351
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "A",
|
||||||
|
"value": "5.255.255.242",
|
||||||
|
"ttl": 351
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answer_dnssec": false,
|
||||||
|
"cached": false,
|
||||||
|
"client": "127.0.0.1",
|
||||||
|
"client_info": {
|
||||||
|
"whois": {},
|
||||||
|
"name": "localhost",
|
||||||
|
"disallowed_rule": "127.0.0.1",
|
||||||
|
"disallowed": false
|
||||||
|
},
|
||||||
|
"client_proto": "",
|
||||||
|
"elapsedMs": "5051.070708",
|
||||||
|
"question": {
|
||||||
|
"class": "IN",
|
||||||
|
"name": "ya.ru",
|
||||||
|
"type": "A"
|
||||||
|
},
|
||||||
|
"reason": "NotFilteredNotFound",
|
||||||
|
"rules": [],
|
||||||
|
"status": "NOERROR",
|
||||||
|
"time": "2024-07-17T16:02:37.4983+02:00",
|
||||||
|
"upstream": "https://dns10.quad9.net:443/dns-query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"oldest": "2024-07-17T16:02:37.4983+02:00"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/#logs');
|
||||||
|
|
||||||
|
await page.getByTestId('querylog_search').fill('127.0.0.1');
|
||||||
|
|
||||||
|
const [request] = await Promise.all([
|
||||||
|
page.waitForRequest((req) => req.url().includes('/control/querylog')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (request) {
|
||||||
|
expect(request.url()).toContain('search=127.0.0.1');
|
||||||
|
expect(await page.getByTestId('querylog_cell').first().isVisible()).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# A docker file for scripts/make/build-docker.sh.
|
# A docker file for scripts/make/build-docker.sh.
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.21
|
||||||
|
|
||||||
ARG BUILD_DATE
|
ARG BUILD_DATE
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG VCS_REF
|
ARG VCS_REF
|
||||||
|
|
||||||
LABEL\
|
LABEL \
|
||||||
maintainer="AdGuard Team <devteam@adguard.com>" \
|
maintainer="AdGuard Team <devteam@adguard.com>" \
|
||||||
org.opencontainers.image.authors="AdGuard Team <devteam@adguard.com>" \
|
org.opencontainers.image.authors="AdGuard Team <devteam@adguard.com>" \
|
||||||
org.opencontainers.image.created=$BUILD_DATE \
|
org.opencontainers.image.created=$BUILD_DATE \
|
||||||
@@ -30,8 +30,8 @@ ARG TARGETARCH
|
|||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
|
|
||||||
COPY --chown=nobody:nogroup\
|
COPY --chown=nobody:nogroup \
|
||||||
./${DIST_DIR}/docker/AdGuardHome_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT}\
|
./${DIST_DIR}/docker/AdGuardHome_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT} \
|
||||||
/opt/adguardhome/AdGuardHome
|
/opt/adguardhome/AdGuardHome
|
||||||
|
|
||||||
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
||||||
@@ -45,8 +45,15 @@ RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
|||||||
# 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3)
|
# 3000 : TCP, UDP : HTTP(S) (alt, incl. HTTP/3)
|
||||||
# 5443 : TCP, UDP : DNSCrypt (alt)
|
# 5443 : TCP, UDP : DNSCrypt (alt)
|
||||||
# 6060 : TCP : HTTP (pprof)
|
# 6060 : TCP : HTTP (pprof)
|
||||||
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 443/udp 853/tcp\
|
EXPOSE 53/tcp 53/udp \
|
||||||
853/udp 3000/tcp 3000/udp 5443/tcp 5443/udp 6060/tcp
|
67/udp \
|
||||||
|
68/udp \
|
||||||
|
80/tcp \
|
||||||
|
443/tcp 443/udp \
|
||||||
|
853/tcp 853/udp \
|
||||||
|
3000/tcp 3000/udp \
|
||||||
|
5443/tcp 5443/udp \
|
||||||
|
6060/tcp
|
||||||
|
|
||||||
WORKDIR /opt/adguardhome/work
|
WORKDIR /opt/adguardhome/work
|
||||||
|
|
||||||
|
|||||||
77
go.mod
77
go.mod
@@ -1,17 +1,17 @@
|
|||||||
module github.com/AdguardTeam/AdGuardHome
|
module github.com/AdguardTeam/AdGuardHome
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.1
|
github.com/AdguardTeam/dnsproxy v0.75.3
|
||||||
github.com/AdguardTeam/golibs v0.32.5
|
github.com/AdguardTeam/golibs v0.32.8
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0
|
github.com/AdguardTeam/urlfilter v0.20.0
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||||
github.com/bluele/gcache v0.0.2
|
github.com/bluele/gcache v0.0.2
|
||||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||||
github.com/digineo/go-ipset/v2 v2.2.1
|
github.com/digineo/go-ipset/v2 v2.2.1
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
// TODO(e.burkov): This package is deprecated; find a new one or use our
|
// TODO(e.burkov): This package is deprecated; find a new one or use our
|
||||||
// own code for that. Perhaps, use gopacket.
|
// own code for that. Perhaps, use gopacket.
|
||||||
github.com/go-ping/ping v1.2.0
|
github.com/go-ping/ping v1.2.0
|
||||||
@@ -28,33 +28,31 @@ require (
|
|||||||
// TODO(a.garipov): This package is deprecated; find a new one or use our
|
// TODO(a.garipov): This package is deprecated; find a new one or use our
|
||||||
// own code for that. Perhaps, use gopacket.
|
// own code for that. Perhaps, use gopacket.
|
||||||
github.com/mdlayher/raw v0.1.0
|
github.com/mdlayher/raw v0.1.0
|
||||||
github.com/miekg/dns v1.1.63
|
github.com/miekg/dns v1.1.65
|
||||||
github.com/quic-go/quic-go v0.49.0
|
github.com/quic-go/quic-go v0.50.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/ti-mo/netfilter v0.5.2
|
github.com/ti-mo/netfilter v0.5.2
|
||||||
go.etcd.io/bbolt v1.4.0
|
go.etcd.io/bbolt v1.4.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.39.0
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.32.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.118.3 // indirect
|
cloud.google.com/go v0.120.1 // indirect
|
||||||
cloud.google.com/go/ai v0.10.0 // indirect
|
cloud.google.com/go/ai v0.10.2 // indirect
|
||||||
cloud.google.com/go/auth v0.15.0 // indirect
|
cloud.google.com/go/auth v0.16.0 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
cloud.google.com/go/longrunning v0.6.5 // indirect
|
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
|
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||||
@@ -63,16 +61,16 @@ require (
|
|||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golangci/misspell v0.6.0 // indirect
|
github.com/golangci/misspell v0.6.0 // indirect
|
||||||
github.com/google/generative-ai-go v0.19.0 // indirect
|
github.com/google/generative-ai-go v0.19.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||||
github.com/gookit/color v1.5.4 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||||
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
|
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
|
||||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
@@ -80,7 +78,7 @@ require (
|
|||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/securego/gosec/v2 v2.22.2 // indirect
|
github.com/securego/gosec/v2 v2.22.3 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
@@ -90,26 +88,27 @@ require (
|
|||||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 // indirect
|
go.uber.org/mock v0.5.1 // indirect
|
||||||
|
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/oauth2 v0.28.0 // indirect
|
golang.org/x/oauth2 v0.29.0 // indirect
|
||||||
golang.org/x/sync v0.12.0 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
golang.org/x/telemetry v0.0.0-20250305155315-2a181eac97a3 // indirect
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
|
||||||
golang.org/x/term v0.30.0 // indirect
|
golang.org/x/term v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.31.0 // indirect
|
golang.org/x/tools v0.32.0 // indirect
|
||||||
golang.org/x/vuln v1.1.4 // indirect
|
golang.org/x/vuln v1.1.4 // indirect
|
||||||
gonum.org/v1/gonum v0.15.1 // indirect
|
gonum.org/v1/gonum v0.16.0 // indirect
|
||||||
google.golang.org/api v0.224.0 // indirect
|
google.golang.org/api v0.229.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
|
||||||
google.golang.org/grpc v1.71.0 // indirect
|
google.golang.org/grpc v1.71.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
honnef.co/go/tools v0.6.1 // indirect
|
honnef.co/go/tools v0.6.1 // indirect
|
||||||
mvdan.cc/editorconfig v0.3.0 // indirect
|
mvdan.cc/editorconfig v0.3.0 // indirect
|
||||||
mvdan.cc/gofumpt v0.7.0 // indirect
|
mvdan.cc/gofumpt v0.8.0 // indirect
|
||||||
mvdan.cc/sh/v3 v3.11.0 // indirect
|
mvdan.cc/sh/v3 v3.11.0 // indirect
|
||||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
|
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
156
go.sum
156
go.sum
@@ -1,31 +1,27 @@
|
|||||||
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
|
cloud.google.com/go v0.120.1 h1:Z+5V7yd383+9617XDCyszmK5E4wJRJL+tquMfDj9hLM=
|
||||||
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
|
cloud.google.com/go v0.120.1/go.mod h1:56Vs7sf/i2jYM6ZL9NYlC82r04PThNcPS5YgFmb0rp8=
|
||||||
cloud.google.com/go/ai v0.10.0 h1:hwj6CI6sMKubXodoJJGTy/c2T1RbbLGM6TL3QoAvzU8=
|
cloud.google.com/go/ai v0.10.2 h1:5NHzmZlRs+3kvlsVdjT0cTnLrjQdROJ/8VOljVfs+8o=
|
||||||
cloud.google.com/go/ai v0.10.0/go.mod h1:kvnt2KeHqX8+41PVeMRBETDyQAp/RFvBWGdx/aGjNMo=
|
cloud.google.com/go/ai v0.10.2/go.mod h1:xZuZuE9d3RgsR132meCnPadiU9XV0qXjpLr+P4J46eE=
|
||||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU=
|
||||||
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/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
|
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||||
cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
|
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.1 h1:ux2sQfF/9+WRo6a32g9NtfaAPU19gJhqkEu2OZflxJg=
|
github.com/AdguardTeam/dnsproxy v0.75.3 h1:pxlMNO+cP1A3px40PY/old6SAE82pkdLPUA2P3KY8u0=
|
||||||
github.com/AdguardTeam/dnsproxy v0.75.1/go.mod h1:HKBI/IO2/ACOjfTV6qIzB5ZDDxfjgHHvQ3hIbGg9wvc=
|
github.com/AdguardTeam/dnsproxy v0.75.3/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
|
||||||
github.com/AdguardTeam/golibs v0.32.5 h1:4Rkv2xBnyJe6l/EM2MFgoY1S4pweYwDgLTYg2MDArEA=
|
github.com/AdguardTeam/golibs v0.32.8 h1:O3mc3kYcPkW3kbmd+gqzFNgUka13a+iBgFLThwOYSQE=
|
||||||
github.com/AdguardTeam/golibs v0.32.5/go.mod h1:agsvz8Iyv0uV9NU56hpCoFLAtSPkiBf9nPVhDvdUIb0=
|
github.com/AdguardTeam/golibs v0.32.8/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||||
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/ameshkov/dnscrypt/v2 v2.4.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
|
||||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs=
|
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
|
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
||||||
@@ -34,8 +30,8 @@ github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
|||||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
|
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
|
||||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
|
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
|
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -43,8 +39,8 @@ github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1M
|
|||||||
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -76,8 +72,8 @@ 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/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||||
@@ -87,8 +83,8 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0
|
|||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
@@ -128,12 +124,12 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
|
|||||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
@@ -145,16 +141,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
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/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
|
||||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
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/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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g=
|
github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc=
|
||||||
github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE=
|
github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
@@ -199,16 +197,18 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
|
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||||
|
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
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/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394 h1:VI4qDpTkfFaCXEPrbojidLgVQhj2x4nzTccG0hjaLlU=
|
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 h1:oMe07YcizemJ09rs2kRkFYAp0pt4e1lYLwPWiEGMpXE=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20250305212735-054e65f0b394/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
|
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -221,14 +221,14 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -241,43 +241,43 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
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/telemetry v0.0.0-20250305155315-2a181eac97a3 h1:k+pofz4/0MRETtVtItAwfDgPUvNlWrUrFw+8dtUVUa8=
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
|
||||||
golang.org/x/telemetry v0.0.0-20250305155315-2a181eac97a3/go.mod h1:16eI1RtbPZAEm3u7hpIh7JM/w5AbmlDtnrdKYaREic8=
|
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
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/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
|
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
|
||||||
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
|
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU=
|
google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8=
|
||||||
google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ=
|
google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
||||||
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/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
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=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -292,8 +292,8 @@ howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
|||||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
|
mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
|
||||||
mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
|
mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
|
||||||
mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU=
|
mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k=
|
||||||
mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo=
|
mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
|
||||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
|
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package aghalg
|
package aghalg_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewSortedMap(t *testing.T) {
|
func TestNewSortedMap(t *testing.T) {
|
||||||
var m SortedMap[string, int]
|
var m aghalg.SortedMap[string, int]
|
||||||
|
|
||||||
letters := []string{}
|
letters := []string{}
|
||||||
for i := range 10 {
|
for i := range 10 {
|
||||||
@@ -17,7 +18,7 @@ func TestNewSortedMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("create_and_fill", func(t *testing.T) {
|
t.Run("create_and_fill", func(t *testing.T) {
|
||||||
m = NewSortedMap[string, int](strings.Compare)
|
m = aghalg.NewSortedMap[string, int](strings.Compare)
|
||||||
|
|
||||||
nums := []int{}
|
nums := []int{}
|
||||||
for i, r := range letters {
|
for i, r := range letters {
|
||||||
@@ -68,7 +69,7 @@ func TestNewSortedMap_nil(t *testing.T) {
|
|||||||
val = "val"
|
val = "val"
|
||||||
)
|
)
|
||||||
|
|
||||||
var m SortedMap[string, string]
|
var m aghalg.SortedMap[string, string]
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
m.Set(key, val)
|
m.Set(key, val)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package aghnet
|
package aghnet_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,13 +30,13 @@ func TestGenerateHostName(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
hostname := GenerateHostname(tc.ip)
|
hostname := aghnet.GenerateHostname(tc.ip)
|
||||||
assert.Equal(t, tc.want, hostname)
|
assert.Equal(t, tc.want, hostname)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
assert.Panics(t, func() { GenerateHostname(netip.Addr{}) })
|
assert.Panics(t, func() { aghnet.GenerateHostname(netip.Addr{}) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package aghnet
|
package aghnet_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fakeIface is a stub implementation of aghnet.NetIface to simplify testing.
|
// fakeIface is a stub implementation of [aghnet.NetIface] interface to simplify
|
||||||
|
// testing.
|
||||||
type fakeIface struct {
|
type fakeIface struct {
|
||||||
err error
|
err error
|
||||||
addrs []net.Addr
|
addrs []net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addrs implements the NetIface interface for *fakeIface.
|
// Addrs implements the [aghnet.NetIface] interface for *fakeIface.
|
||||||
func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
|
func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||||
if iface.err != nil {
|
if iface.err != nil {
|
||||||
return nil, iface.err
|
return nil, iface.err
|
||||||
@@ -25,6 +27,9 @@ func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) {
|
|||||||
return iface.addrs, nil
|
return iface.addrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ aghnet.NetIface = (*fakeIface)(nil)
|
||||||
|
|
||||||
func TestIfaceIPAddrs(t *testing.T) {
|
func TestIfaceIPAddrs(t *testing.T) {
|
||||||
const errTest errors.Error = "test error"
|
const errTest errors.Error = "test error"
|
||||||
|
|
||||||
@@ -35,76 +40,76 @@ func TestIfaceIPAddrs(t *testing.T) {
|
|||||||
addr6 := &net.IPNet{IP: ip6}
|
addr6 := &net.IPNet{IP: ip6}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
iface NetIface
|
iface aghnet.NetIface
|
||||||
name string
|
name string
|
||||||
wantErrMsg string
|
wantErrMsg string
|
||||||
want []net.IP
|
want []net.IP
|
||||||
ipv IPVersion
|
ipv aghnet.IPVersion
|
||||||
}{{
|
}{{
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||||
name: "ipv4_success",
|
name: "ipv4_success",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip4},
|
want: []net.IP{ip4},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
name: "ipv4_success_with_ipv6",
|
name: "ipv4_success_with_ipv6",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip4},
|
want: []net.IP{ip4},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||||
name: "ipv4_error",
|
name: "ipv4_error",
|
||||||
wantErrMsg: errTest.Error(),
|
wantErrMsg: errTest.Error(),
|
||||||
want: nil,
|
want: nil,
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||||
name: "ipv6_success",
|
name: "ipv6_success",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip6},
|
want: []net.IP{ip6},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
name: "ipv6_success_with_ipv4",
|
name: "ipv6_success_with_ipv4",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip6},
|
want: []net.IP{ip6},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||||
name: "ipv6_error",
|
name: "ipv6_error",
|
||||||
wantErrMsg: errTest.Error(),
|
wantErrMsg: errTest.Error(),
|
||||||
want: nil,
|
want: nil,
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: nil, err: nil},
|
iface: &fakeIface{addrs: nil, err: nil},
|
||||||
name: "bad_proto",
|
name: "bad_proto",
|
||||||
wantErrMsg: "invalid ip version 10",
|
wantErrMsg: "invalid ip version 10",
|
||||||
want: nil,
|
want: nil,
|
||||||
ipv: IPVersion6 + IPVersion4,
|
ipv: aghnet.IPVersion6 + aghnet.IPVersion4,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip4}}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip4}}, err: nil},
|
||||||
name: "ipaddr_v4",
|
name: "ipaddr_v4",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip4},
|
want: []net.IP{ip4},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip6, Zone: ""}}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{&net.IPAddr{IP: ip6, Zone: ""}}, err: nil},
|
||||||
name: "ipaddr_v6",
|
name: "ipaddr_v6",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: []net.IP{ip6},
|
want: []net.IP{ip6},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
}, {
|
}, {
|
||||||
iface: &fakeIface{addrs: []net.Addr{&net.UnixAddr{}}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{&net.UnixAddr{}}, err: nil},
|
||||||
name: "non-ipv4",
|
name: "non-ipv4",
|
||||||
wantErrMsg: "",
|
wantErrMsg: "",
|
||||||
want: nil,
|
want: nil,
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
got, err := IfaceIPAddrs(tc.iface, tc.ipv)
|
got, err := aghnet.IfaceIPAddrs(tc.iface, tc.ipv)
|
||||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||||
|
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
@@ -118,7 +123,10 @@ type waitingFakeIface struct {
|
|||||||
n int
|
n int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addrs implements the NetIface interface for *waitingFakeIface.
|
// type check
|
||||||
|
var _ aghnet.NetIface = (*waitingFakeIface)(nil)
|
||||||
|
|
||||||
|
// Addrs implements the [aghnet.NetIface] interface for *waitingFakeIface.
|
||||||
func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) {
|
func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) {
|
||||||
if iface.err != nil {
|
if iface.err != nil {
|
||||||
return nil, iface.err
|
return nil, iface.err
|
||||||
@@ -143,76 +151,76 @@ func TestIfaceDNSIPAddrs(t *testing.T) {
|
|||||||
addr6 := &net.IPNet{IP: ip6}
|
addr6 := &net.IPNet{IP: ip6}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
iface NetIface
|
iface aghnet.NetIface
|
||||||
wantErr error
|
wantErr error
|
||||||
name string
|
name string
|
||||||
want []net.IP
|
want []net.IP
|
||||||
ipv IPVersion
|
ipv aghnet.IPVersion
|
||||||
}{{
|
}{{
|
||||||
name: "ipv4_success",
|
name: "ipv4_success",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: []net.IP{ip4, ip4},
|
want: []net.IP{ip4, ip4},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv4_success_with_ipv6",
|
name: "ipv4_success_with_ipv6",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: []net.IP{ip4, ip4},
|
want: []net.IP{ip4, ip4},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv4_error",
|
name: "ipv4_error",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: nil,
|
want: nil,
|
||||||
wantErr: errTest,
|
wantErr: errTest,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv4_wait",
|
name: "ipv4_wait",
|
||||||
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
|
iface: &waitingFakeIface{addrs: []net.Addr{addr4}, err: nil, n: 1},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: []net.IP{ip4, ip4},
|
want: []net.IP{ip4, ip4},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv6_success",
|
name: "ipv6_success",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
want: []net.IP{ip6, ip6},
|
want: []net.IP{ip6, ip6},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv6_success_with_ipv4",
|
name: "ipv6_success_with_ipv4",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
want: []net.IP{ip6, ip6},
|
want: []net.IP{ip6, ip6},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv6_error",
|
name: "ipv6_error",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
want: nil,
|
want: nil,
|
||||||
wantErr: errTest,
|
wantErr: errTest,
|
||||||
}, {
|
}, {
|
||||||
name: "ipv6_wait",
|
name: "ipv6_wait",
|
||||||
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
|
iface: &waitingFakeIface{addrs: []net.Addr{addr6}, err: nil, n: 1},
|
||||||
ipv: IPVersion6,
|
ipv: aghnet.IPVersion6,
|
||||||
want: []net.IP{ip6, ip6},
|
want: []net.IP{ip6, ip6},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "empty",
|
name: "empty",
|
||||||
iface: &fakeIface{addrs: nil, err: nil},
|
iface: &fakeIface{addrs: nil, err: nil},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: nil,
|
want: nil,
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "many",
|
name: "many",
|
||||||
iface: &fakeIface{addrs: []net.Addr{addr4, addr4}},
|
iface: &fakeIface{addrs: []net.Addr{addr4, addr4}},
|
||||||
ipv: IPVersion4,
|
ipv: aghnet.IPVersion4,
|
||||||
want: []net.IP{ip4, ip4},
|
want: []net.IP{ip4, ip4},
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
got, err := IfaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
got, err := aghnet.IfaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0)
|
||||||
require.ErrorIs(t, err, tc.wantErr)
|
require.ErrorIs(t, err, tc.wantErr)
|
||||||
|
|
||||||
assert.Equal(t, tc.want, got)
|
assert.Equal(t, tc.want, got)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package aghnet
|
package aghnet_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -18,7 +19,7 @@ func TestIPMut(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
t.Run("nil_no_mut", func(t *testing.T) {
|
t.Run("nil_no_mut", func(t *testing.T) {
|
||||||
ipmut := NewIPMut(nil)
|
ipmut := aghnet.NewIPMut(nil)
|
||||||
|
|
||||||
ips := netutil.CloneIPs(testIPs)
|
ips := netutil.CloneIPs(testIPs)
|
||||||
for i := range ips {
|
for i := range ips {
|
||||||
@@ -28,7 +29,7 @@ func TestIPMut(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("not_nil_mut", func(t *testing.T) {
|
t.Run("not_nil_mut", func(t *testing.T) {
|
||||||
ipmut := NewIPMut(func(ip net.IP) {
|
ipmut := aghnet.NewIPMut(func(ip net.IP) {
|
||||||
for i := range ip {
|
for i := range ip {
|
||||||
ip[i] = 0
|
ip[i] = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
package aghnet
|
package aghnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
59
internal/aghuser/aghuser.go
Normal file
59
internal/aghuser/aghuser.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package aghuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login is the type for web user logins.
|
||||||
|
type Login string
|
||||||
|
|
||||||
|
// NewLogin returns a web user login. The length of s must not be greater than
|
||||||
|
// [math.MaxUint16].
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Add more constraints as needed.
|
||||||
|
func NewLogin(s string) (l Login, err error) {
|
||||||
|
if s == "" {
|
||||||
|
return "", errors.ErrEmptyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return Login(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password is an interface that defines methods for handling web user
|
||||||
|
// passwords.
|
||||||
|
type Password interface {
|
||||||
|
// Authenticate returns true if the provided password is allowed.
|
||||||
|
Authenticate(ctx context.Context, password string) (ok bool)
|
||||||
|
|
||||||
|
// Hash returns a hashed representation of the web user password.
|
||||||
|
Hash() (b []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPassword is the default bcrypt implementation of the [Password]
|
||||||
|
// interface.
|
||||||
|
type DefaultPassword struct {
|
||||||
|
hash []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultPassword returns the new properly initialized *DefaultPassword.
|
||||||
|
func NewDefaultPassword(hash string) (p *DefaultPassword) {
|
||||||
|
return &DefaultPassword{
|
||||||
|
hash: []byte(hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ Password = (*DefaultPassword)(nil)
|
||||||
|
|
||||||
|
// Authenticate implements the [Password] interface for *DefaultPassword.
|
||||||
|
func (p *DefaultPassword) Authenticate(ctx context.Context, passwd string) (ok bool) {
|
||||||
|
return bcrypt.CompareHashAndPassword([]byte(p.hash), []byte(passwd)) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash implements the [Password] interface for *DefaultPassword.
|
||||||
|
func (p *DefaultPassword) Hash() (b []byte) {
|
||||||
|
return p.hash
|
||||||
|
}
|
||||||
6
internal/aghuser/aghuser_test.go
Normal file
6
internal/aghuser/aghuser_test.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package aghuser_test
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// testTimeout is the common timeout for tests.
|
||||||
|
const testTimeout = 1 * time.Second
|
||||||
149
internal/aghuser/db.go
Normal file
149
internal/aghuser/db.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package aghuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB is an interface that defines methods for interacting with user
|
||||||
|
// information. All methods must be safe for concurrent use.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Use this.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Consider updating methods to return a clone.
|
||||||
|
type DB interface {
|
||||||
|
// All retrieves all users from the database, sorted by login.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Consider function signature change to reflect the
|
||||||
|
// in-memory implementation, as it currently always returns nil for error.
|
||||||
|
All(ctx context.Context) (users []*User, err error)
|
||||||
|
|
||||||
|
// ByLogin retrieves a user by their login. u must not be modified.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Remove this once user sessions support [UserID].
|
||||||
|
ByLogin(ctx context.Context, login Login) (u *User, err error)
|
||||||
|
|
||||||
|
// ByUUID retrieves a user by their unique identifier. u must not be
|
||||||
|
// modified.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Use this.
|
||||||
|
ByUUID(ctx context.Context, id UserID) (u *User, err error)
|
||||||
|
|
||||||
|
// Create adds a new user to the database. If the credentials already
|
||||||
|
// exist, it returns the [errors.ErrDuplicated] error. It also can return
|
||||||
|
// an error from the cryptographic randomness reader. u must not be
|
||||||
|
// modified.
|
||||||
|
Create(ctx context.Context, u *User) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDB is the default in-memory implementation of the [DB] interface.
|
||||||
|
type DefaultDB struct {
|
||||||
|
// mu protects all properties below.
|
||||||
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
// loginToUserID maps a web user login to their UserID. The values must not
|
||||||
|
// be empty.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Remove this once user sessions support [UserID].
|
||||||
|
loginToUserID map[Login]UserID
|
||||||
|
|
||||||
|
// userIDToUser maps a UserID to a web user. The values must not be nil.
|
||||||
|
// It must be synchronized with loginToUserID, meaning all UserIDs stored in
|
||||||
|
// loginToUserID must also be stored in this map.
|
||||||
|
userIDToUser map[UserID]*User
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultDB returns the new properly initialized *DefaultDB.
|
||||||
|
func NewDefaultDB() (db *DefaultDB) {
|
||||||
|
return &DefaultDB{
|
||||||
|
mu: &sync.Mutex{},
|
||||||
|
loginToUserID: map[Login]UserID{},
|
||||||
|
userIDToUser: map[UserID]*User{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ DB = (*DefaultDB)(nil)
|
||||||
|
|
||||||
|
// All implements the [DB] interface for *DefaultDB.
|
||||||
|
func (db *DefaultDB) All(ctx context.Context) (users []*User, err error) {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
if len(db.userIDToUser) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
users = slices.SortedStableFunc(
|
||||||
|
maps.Values(db.userIDToUser),
|
||||||
|
func(a, b *User) (res int) {
|
||||||
|
// TODO(s.chzhen): Consider adding a custom comparer.
|
||||||
|
return cmp.Compare(a.Login, b.Login)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByLogin implements the [DB] interface for *DefaultDB.
|
||||||
|
func (db *DefaultDB) ByLogin(ctx context.Context, login Login) (u *User, err error) {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
id, ok := db.loginToUserID[login]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, ok = db.userIDToUser[id]
|
||||||
|
if !ok {
|
||||||
|
// Should not happen.
|
||||||
|
panic(fmt.Errorf("no web user present with login %q", login))
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByUUID implements the [DB] interface for *DefaultDB.
|
||||||
|
func (db *DefaultDB) ByUUID(ctx context.Context, id UserID) (u *User, err error) {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
u, ok := db.userIDToUser[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implements the [DB] interface for *DefaultDB.
|
||||||
|
func (db *DefaultDB) Create(ctx context.Context, u *User) (err error) {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
if u.ID == (UserID{}) {
|
||||||
|
return fmt.Errorf("userid: %w", errors.ErrEmptyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := db.userIDToUser[u.ID]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("userid: %w", errors.ErrDuplicated)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = db.loginToUserID[u.Login]
|
||||||
|
if ok {
|
||||||
|
return fmt.Errorf("login: %w", errors.ErrDuplicated)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.userIDToUser[u.ID] = u
|
||||||
|
db.loginToUserID[u.Login] = u.ID
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
83
internal/aghuser/db_test.go
Normal file
83
internal/aghuser/db_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package aghuser_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghuser"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDB(t *testing.T) {
|
||||||
|
db := aghuser.NewDefaultDB()
|
||||||
|
|
||||||
|
const (
|
||||||
|
userWithIDPassRaw = "user_with_id_password"
|
||||||
|
userSecondPassRaw = "user_second_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
userWithIDPassHash, err := bcrypt.GenerateFromPassword(
|
||||||
|
[]byte(userWithIDPassRaw),
|
||||||
|
bcrypt.DefaultCost,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
userSecondPassHash, err := bcrypt.GenerateFromPassword(
|
||||||
|
[]byte(userSecondPassRaw),
|
||||||
|
bcrypt.DefaultCost,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
userWithIDPass := aghuser.NewDefaultPassword(string(userWithIDPassHash))
|
||||||
|
userSecondPass := aghuser.NewDefaultPassword(string(userSecondPassHash))
|
||||||
|
|
||||||
|
var (
|
||||||
|
userWithID = &aghuser.User{
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
Login: "user_with_id",
|
||||||
|
Password: userWithIDPass,
|
||||||
|
}
|
||||||
|
userSecond = &aghuser.User{
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
Login: "user_second",
|
||||||
|
Password: userSecondPass,
|
||||||
|
}
|
||||||
|
userDuplicateLogin = &aghuser.User{
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
Login: userWithID.Login,
|
||||||
|
Password: userWithIDPass,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
|
||||||
|
err = db.Create(ctx, userWithID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = db.Create(ctx, userSecond)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = db.Create(ctx, userDuplicateLogin)
|
||||||
|
assert.ErrorIs(t, err, errors.ErrDuplicated)
|
||||||
|
|
||||||
|
got, err := db.ByUUID(ctx, userWithID.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, userWithID, got)
|
||||||
|
assert.True(t, got.Password.Authenticate(ctx, userWithIDPassRaw))
|
||||||
|
|
||||||
|
got, err = db.ByLogin(ctx, userSecond.Login)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, userSecond, got)
|
||||||
|
assert.True(t, got.Password.Authenticate(ctx, userSecondPassRaw))
|
||||||
|
|
||||||
|
users, err := db.All(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, users, 2)
|
||||||
|
assert.Equal(t, []*aghuser.User{userSecond, userWithID}, users)
|
||||||
|
}
|
||||||
35
internal/aghuser/session.go
Normal file
35
internal/aghuser/session.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package aghuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionToken is the type for the web user session token.
|
||||||
|
type SessionToken [16]byte
|
||||||
|
|
||||||
|
// NewSessionToken returns a cryptographically secure randomly generated web
|
||||||
|
// user session token. If an error occurs during random generation, it will
|
||||||
|
// cause the program to crash.
|
||||||
|
func NewSessionToken() (t SessionToken) {
|
||||||
|
_, _ = rand.Read(t[:])
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session represents a web user session.
|
||||||
|
type Session struct {
|
||||||
|
// Expire indicates when the session will expire.
|
||||||
|
Expire time.Time
|
||||||
|
|
||||||
|
// UserLogin is the login of the web user associated with the session.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Remove this field and associate the user by UserID.
|
||||||
|
UserLogin Login
|
||||||
|
|
||||||
|
// Token is the session token.
|
||||||
|
Token SessionToken
|
||||||
|
|
||||||
|
// UserID is the identifier of the web user associated with the session.
|
||||||
|
UserID UserID
|
||||||
|
}
|
||||||
453
internal/aghuser/sessionstorage.go
Normal file
453
internal/aghuser/sessionstorage.go
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
package aghuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
berrors "go.etcd.io/bbolt/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionStorage is an interface that defines methods for handling web user
|
||||||
|
// sessions. All methods must be safe for concurrent use.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Add DeleteAll method.
|
||||||
|
type SessionStorage interface {
|
||||||
|
// New creates a new session for the web user.
|
||||||
|
New(ctx context.Context, u *User) (s *Session, err error)
|
||||||
|
|
||||||
|
// FindByToken returns the stored session for the web user based on the session
|
||||||
|
// token.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Consider function signature change to reflect the
|
||||||
|
// in-memory implementation, as it currently always returns nil for error.
|
||||||
|
FindByToken(ctx context.Context, t SessionToken) (s *Session, err error)
|
||||||
|
|
||||||
|
// DeleteByToken removes a stored web user session by the provided token.
|
||||||
|
DeleteByToken(ctx context.Context, t SessionToken) (err error)
|
||||||
|
|
||||||
|
// Close releases the web user sessions database resources.
|
||||||
|
Close() (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSessionStorageConfig represents the web user session storage
|
||||||
|
// configuration structure.
|
||||||
|
type DefaultSessionStorageConfig struct {
|
||||||
|
// Logger is used for logging the operation of the session storage. It must
|
||||||
|
// not be nil.
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
// Clock is used to get the current time. It must not be nil.
|
||||||
|
Clock timeutil.Clock
|
||||||
|
|
||||||
|
// UserDB contains the web user information such as ID, login, and password.
|
||||||
|
// It must not be nil.
|
||||||
|
UserDB DB
|
||||||
|
|
||||||
|
// DBPath is the path to the database file where session data is stored. It
|
||||||
|
// must not be empty.
|
||||||
|
DBPath string
|
||||||
|
|
||||||
|
// SessionTTL is the default Time-To-Live duration for web user sessions.
|
||||||
|
// It specifies how long a session should last and is a required field.
|
||||||
|
SessionTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSessionStorage is the default bbolt database implementation of the
|
||||||
|
// [SessionStorage] interface.
|
||||||
|
type DefaultSessionStorage struct {
|
||||||
|
// db is an instance of the bbolt database where web user sessions are
|
||||||
|
// stored by [SessionToken] in the [bucketNameSessions] bucket.
|
||||||
|
db *bbolt.DB
|
||||||
|
|
||||||
|
// logger is used for logging the operation of the session storage.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
|
// mu protects sessions.
|
||||||
|
mu *sync.Mutex
|
||||||
|
|
||||||
|
// clock is used to get the current time.
|
||||||
|
clock timeutil.Clock
|
||||||
|
|
||||||
|
// userDB contains the web user information such as ID, login, and password.
|
||||||
|
userDB DB
|
||||||
|
|
||||||
|
// sessions maps a session token to a web user session.
|
||||||
|
sessions map[SessionToken]*Session
|
||||||
|
|
||||||
|
// sessionTTL is the default Time-To-Live value for web user sessions.
|
||||||
|
sessionTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultSessionStorage returns the new properly initialized
|
||||||
|
// *DefaultSessionStorage.
|
||||||
|
func NewDefaultSessionStorage(
|
||||||
|
ctx context.Context,
|
||||||
|
conf *DefaultSessionStorageConfig,
|
||||||
|
) (ds *DefaultSessionStorage, err error) {
|
||||||
|
ds = &DefaultSessionStorage{
|
||||||
|
clock: conf.Clock,
|
||||||
|
userDB: conf.UserDB,
|
||||||
|
logger: conf.Logger,
|
||||||
|
mu: &sync.Mutex{},
|
||||||
|
sessions: map[SessionToken]*Session{},
|
||||||
|
sessionTTL: conf.SessionTTL,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbFilename := conf.DBPath
|
||||||
|
// TODO(s.chzhen): Pass logger with options.
|
||||||
|
ds.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
|
||||||
|
if err != nil {
|
||||||
|
ds.logger.ErrorContext(ctx, "opening db %q: %w", dbFilename, err)
|
||||||
|
if errors.Is(err, berrors.ErrInvalid) {
|
||||||
|
const s = "AdGuard Home cannot be initialized due to an incompatible file system.\n" +
|
||||||
|
"Please read the explanation here: https://adguard-dns.io/kb/adguard-home/getting-started/#limitations"
|
||||||
|
slogutil.PrintLines(ctx, ds.logger, slog.LevelError, "", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.loadSessions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("loading sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSessions loads web user sessions from the bbolt database.
|
||||||
|
func (ds *DefaultSessionStorage) loadSessions(ctx context.Context) (err error) {
|
||||||
|
tx, err := ds.db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback := true
|
||||||
|
defer func() {
|
||||||
|
if needRollback {
|
||||||
|
err = errors.WithDeferred(err, tx.Rollback())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bkt := tx.Bucket([]byte(bboltBucketSessions))
|
||||||
|
if bkt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
removed, err := ds.processSessions(ctx, bkt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("processing sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if removed == 0 {
|
||||||
|
ds.logger.DebugContext(ctx, "loading sessions from db", "stored", len(ds.sessions))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback = false
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committing transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"loading sessions from db",
|
||||||
|
"stored", len(ds.sessions),
|
||||||
|
"removed", removed,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSessions iterates over the sessions bucket and loads or removes
|
||||||
|
// sessions as needed.
|
||||||
|
func (ds *DefaultSessionStorage) processSessions(
|
||||||
|
ctx context.Context,
|
||||||
|
bkt *bbolt.Bucket,
|
||||||
|
) (removed int, err error) {
|
||||||
|
invalidSessions := [][]byte{}
|
||||||
|
|
||||||
|
err = bkt.ForEach(ds.bboltSessionHandler(ctx, &invalidSessions))
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("iterating over sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, s := range invalidSessions {
|
||||||
|
if err = bkt.Delete(s); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = errors.Join(errs...); err != nil {
|
||||||
|
return 0, fmt.Errorf("deleting sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(invalidSessions), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bboltSessionHandler returns a function for [bbolt.Bucket.ForEach] that
|
||||||
|
// iterates over stored sessions, deserializes them, and logs any errors
|
||||||
|
// encountered. The returned error is always nil, as these errors are
|
||||||
|
// considered non-critical to stop the iteration process.
|
||||||
|
func (ds *DefaultSessionStorage) bboltSessionHandler(
|
||||||
|
ctx context.Context,
|
||||||
|
invalidSessions *[][]byte,
|
||||||
|
) (fn func(k, v []byte) (err error)) {
|
||||||
|
now := ds.clock.Now()
|
||||||
|
|
||||||
|
return func(k, v []byte) (err error) {
|
||||||
|
s, err := bboltDecode(v)
|
||||||
|
if err != nil {
|
||||||
|
*invalidSessions = append(*invalidSessions, k)
|
||||||
|
ds.logger.DebugContext(ctx, "deserializing session", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.After(s.Expire) {
|
||||||
|
*invalidSessions = append(*invalidSessions, k)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := ds.userDB.ByLogin(ctx, s.UserLogin)
|
||||||
|
if err != nil {
|
||||||
|
// Should not happen, as it currently always returns nil for error.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u == nil {
|
||||||
|
*invalidSessions = append(*invalidSessions, k)
|
||||||
|
ds.logger.DebugContext(ctx, "no saved user by name", "name", s.UserLogin)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t := SessionToken(k)
|
||||||
|
s.Token = t
|
||||||
|
s.UserID = u.ID
|
||||||
|
ds.sessions[t] = s
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bboltBucketSessions is the name of the bucket storing web user sessions in
|
||||||
|
// the bbolt database.
|
||||||
|
const bboltBucketSessions = "sessions-2"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// bboltSessionExpireLen is the length of the expire field in the binary
|
||||||
|
// entry stored in bbolt.
|
||||||
|
bboltSessionExpireLen = 4
|
||||||
|
|
||||||
|
// bboltSessionNameLen is the length of the name field in the binary entry
|
||||||
|
// stored in bbolt.
|
||||||
|
bboltSessionNameLen = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// bboltDecode deserializes decodes a binary data into a session.
|
||||||
|
func bboltDecode(data []byte) (s *Session, err error) {
|
||||||
|
if len(data) < bboltSessionExpireLen+bboltSessionNameLen {
|
||||||
|
return nil, fmt.Errorf("length of the data is less than expected: got %d", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
expireData := data[:bboltSessionExpireLen]
|
||||||
|
nameLenData := data[bboltSessionExpireLen : bboltSessionExpireLen+bboltSessionNameLen]
|
||||||
|
nameData := data[bboltSessionExpireLen+bboltSessionNameLen:]
|
||||||
|
|
||||||
|
nameLen := binary.BigEndian.Uint16(nameLenData)
|
||||||
|
if len(nameData) != int(nameLen) {
|
||||||
|
return nil, fmt.Errorf("login: expected length %d, got %d", nameLen, len(nameData))
|
||||||
|
}
|
||||||
|
|
||||||
|
expire := binary.BigEndian.Uint32(expireData)
|
||||||
|
|
||||||
|
return &Session{
|
||||||
|
Expire: time.Unix(int64(expire), 0),
|
||||||
|
UserLogin: Login(nameData),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bboltEncode serializes a session properties into a binary data.
|
||||||
|
func bboltEncode(s *Session) (data []byte) {
|
||||||
|
data = make([]byte, bboltSessionExpireLen+bboltSessionNameLen+len(s.UserLogin))
|
||||||
|
|
||||||
|
expireData := data[:bboltSessionExpireLen]
|
||||||
|
nameLenData := data[bboltSessionExpireLen : bboltSessionExpireLen+bboltSessionNameLen]
|
||||||
|
nameData := data[bboltSessionExpireLen+bboltSessionNameLen:]
|
||||||
|
|
||||||
|
expire := uint32(s.Expire.Unix())
|
||||||
|
binary.BigEndian.PutUint32(expireData, expire)
|
||||||
|
binary.BigEndian.PutUint16(nameLenData, uint16(len(s.UserLogin)))
|
||||||
|
copy(nameData, []byte(s.UserLogin))
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// type check
|
||||||
|
var _ SessionStorage = (*DefaultSessionStorage)(nil)
|
||||||
|
|
||||||
|
// New implements the [SessionStorage] interface for *DefaultSessionStorage.
|
||||||
|
func (ds *DefaultSessionStorage) New(ctx context.Context, u *User) (s *Session, err error) {
|
||||||
|
s = &Session{
|
||||||
|
Token: NewSessionToken(),
|
||||||
|
UserID: u.ID,
|
||||||
|
UserLogin: u.Login,
|
||||||
|
Expire: ds.clock.Now().Add(ds.sessionTTL),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.store(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("storing session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
ds.sessions[s.Token] = s
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// store saves a web user session in the bbolt database.
|
||||||
|
func (ds *DefaultSessionStorage) store(s *Session) (err error) {
|
||||||
|
tx, err := ds.db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback := true
|
||||||
|
defer func() {
|
||||||
|
if needRollback {
|
||||||
|
err = errors.WithDeferred(err, tx.Rollback())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bkt, err := tx.CreateBucketIfNotExists([]byte(bboltBucketSessions))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bkt.Put(s.Token[:], bboltEncode(s))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("putting data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback = false
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committing transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByToken implements the [SessionStorage] interface for
|
||||||
|
// *DefaultSessionStorage.
|
||||||
|
func (ds *DefaultSessionStorage) FindByToken(
|
||||||
|
ctx context.Context,
|
||||||
|
t SessionToken,
|
||||||
|
) (s *Session, err error) {
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
s, ok := ds.sessions[t]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := ds.clock.Now()
|
||||||
|
if now.After(s.Expire) {
|
||||||
|
err = ds.deleteByToken(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("expired session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteByToken implements the [SessionStorage] interface for
|
||||||
|
// *DefaultSessionStorage.
|
||||||
|
func (ds *DefaultSessionStorage) DeleteByToken(ctx context.Context, t SessionToken) (err error) {
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
// Don't wrap the error because it's informative enough as is.
|
||||||
|
return ds.deleteByToken(ctx, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteByToken removes stored session by token. ds.mu is expected to be
|
||||||
|
// locked.
|
||||||
|
func (ds *DefaultSessionStorage) deleteByToken(ctx context.Context, t SessionToken) (err error) {
|
||||||
|
err = ds.remove(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
ds.logger.ErrorContext(ctx, "deleting session", slogutil.KeyError, err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(ds.sessions, t)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove deletes a web user session from the bbolt database.
|
||||||
|
func (ds *DefaultSessionStorage) remove(ctx context.Context, t SessionToken) (err error) {
|
||||||
|
tx, err := ds.db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback := true
|
||||||
|
defer func() {
|
||||||
|
if needRollback {
|
||||||
|
err = errors.WithDeferred(err, tx.Rollback())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bkt := tx.Bucket([]byte(bboltBucketSessions))
|
||||||
|
if bkt == nil {
|
||||||
|
return errors.Error("no bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bkt.Delete(t[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
needRollback = false
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("committing transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.logger.DebugContext(ctx, "removed session from db")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the [SessionStorage] interface for *DefaultSessionStorage.
|
||||||
|
func (ds *DefaultSessionStorage) Close() (err error) {
|
||||||
|
err = ds.db.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("closing db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
162
internal/aghuser/sessionstorage_test.go
Normal file
162
internal/aghuser/sessionstorage_test.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package aghuser_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghuser"
|
||||||
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil/faketime"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// addSession is a helper function that saves and returns a session for a newly
|
||||||
|
// generated [aghuser.User] by login.
|
||||||
|
func addSession(
|
||||||
|
tb testing.TB,
|
||||||
|
ctx context.Context,
|
||||||
|
ds aghuser.SessionStorage,
|
||||||
|
login aghuser.Login,
|
||||||
|
) (s *aghuser.Session) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
s, err := ds.New(ctx, &aghuser.User{
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
Login: login,
|
||||||
|
})
|
||||||
|
require.NoError(tb, err)
|
||||||
|
require.NotNil(tb, s)
|
||||||
|
|
||||||
|
var got *aghuser.Session
|
||||||
|
got, err = ds.FindByToken(ctx, s.Token)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
require.NotNil(tb, got)
|
||||||
|
|
||||||
|
assert.Equal(tb, login, got.UserLogin)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultSessionStorage(t *testing.T) {
|
||||||
|
const (
|
||||||
|
userLoginFirst aghuser.Login = "user_one"
|
||||||
|
userLoginSecond aghuser.Login = "user_two"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||||
|
logger = slogutil.NewDiscardLogger()
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionTTL = time.Minute
|
||||||
|
timeStep = time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set up a mock clock to test expired sessions. Each call to [clock.Now]
|
||||||
|
// will return the [date] incremented by [timeStep].
|
||||||
|
date := time.Now()
|
||||||
|
clock := &faketime.Clock{
|
||||||
|
OnNow: func() (now time.Time) {
|
||||||
|
date = date.Add(timeStep)
|
||||||
|
|
||||||
|
return date
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dbFile, err := os.CreateTemp(t.TempDir(), "sessions.db")
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutil.CleanupAndRequireSuccess(t, dbFile.Close)
|
||||||
|
|
||||||
|
userDB := aghuser.NewDefaultDB()
|
||||||
|
|
||||||
|
err = userDB.Create(ctx, &aghuser.User{
|
||||||
|
Login: userLoginFirst,
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = userDB.Create(ctx, &aghuser.User{
|
||||||
|
Login: userLoginSecond,
|
||||||
|
ID: aghuser.MustNewUserID(),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ds *aghuser.DefaultSessionStorage
|
||||||
|
|
||||||
|
sessionFirst *aghuser.Session
|
||||||
|
sessionSecond *aghuser.Session
|
||||||
|
)
|
||||||
|
|
||||||
|
require.True(t, t.Run("prepare_session_storage", func(t *testing.T) {
|
||||||
|
ds, err = aghuser.NewDefaultSessionStorage(ctx, &aghuser.DefaultSessionStorageConfig{
|
||||||
|
Clock: clock,
|
||||||
|
UserDB: userDB,
|
||||||
|
Logger: logger,
|
||||||
|
DBPath: dbFile.Name(),
|
||||||
|
SessionTTL: sessionTTL,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sessionFirst = addSession(t, ctx, ds, userLoginFirst)
|
||||||
|
|
||||||
|
// Advance time to ensure the first session expires before creating the
|
||||||
|
// second session.
|
||||||
|
date = date.Add(time.Hour)
|
||||||
|
|
||||||
|
sessionSecond = addSession(t, ctx, ds, userLoginSecond)
|
||||||
|
|
||||||
|
err = ds.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.True(t, t.Run("load_sessions", func(t *testing.T) {
|
||||||
|
ds, err = aghuser.NewDefaultSessionStorage(ctx, &aghuser.DefaultSessionStorageConfig{
|
||||||
|
Clock: clock,
|
||||||
|
UserDB: userDB,
|
||||||
|
Logger: logger,
|
||||||
|
DBPath: dbFile.Name(),
|
||||||
|
SessionTTL: sessionTTL,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var got *aghuser.Session
|
||||||
|
got, err = ds.FindByToken(ctx, sessionFirst.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, got)
|
||||||
|
|
||||||
|
got, err = ds.FindByToken(ctx, sessionSecond.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
|
||||||
|
assert.Equal(t, userLoginSecond, got.UserLogin)
|
||||||
|
|
||||||
|
err = ds.DeleteByToken(ctx, sessionSecond.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err = ds.FindByToken(ctx, sessionSecond.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, got)
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.True(t, t.Run("expired_session", func(t *testing.T) {
|
||||||
|
testutil.CleanupAndRequireSuccess(t, ds.Close)
|
||||||
|
|
||||||
|
sessionFirst = addSession(t, ctx, ds, userLoginFirst)
|
||||||
|
|
||||||
|
date = date.Add(time.Hour)
|
||||||
|
|
||||||
|
var got *aghuser.Session
|
||||||
|
got, err = ds.FindByToken(ctx, sessionFirst.Token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Nil(t, got)
|
||||||
|
}))
|
||||||
|
}
|
||||||
44
internal/aghuser/user.go
Normal file
44
internal/aghuser/user.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Package aghuser contains types and logic for dealing with AdGuard Home's web
|
||||||
|
// users.
|
||||||
|
package aghuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserID is the type for the unique IDs of web users.
|
||||||
|
type UserID uuid.UUID
|
||||||
|
|
||||||
|
// NewUserID returns a new web user unique identifier. Any error returned is an
|
||||||
|
// error from the cryptographic randomness reader.
|
||||||
|
func NewUserID() (uid UserID, err error) {
|
||||||
|
uuidv7, err := uuid.NewV7()
|
||||||
|
|
||||||
|
return UserID(uuidv7), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewUserID is a wrapper around [NewUserID] that panics if there is an
|
||||||
|
// error. It is currently only used in tests.
|
||||||
|
func MustNewUserID() (uid UserID) {
|
||||||
|
uid, err := NewUserID()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("unexpected uuidv7 error: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a web user.
|
||||||
|
type User struct {
|
||||||
|
// Password stores the password information for the web user. It must not
|
||||||
|
// be nil.
|
||||||
|
Password Password
|
||||||
|
|
||||||
|
// Login is the login name of the web user. It must not be empty.
|
||||||
|
Login Login
|
||||||
|
|
||||||
|
// ID is the unique identifier for the web user. It must not be empty.
|
||||||
|
ID UserID
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/arpdb"
|
"github.com/AdguardTeam/AdGuardHome/internal/arpdb"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
@@ -495,6 +496,11 @@ func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
|
|||||||
return p.ShallowClone(), ok
|
return p.ShallowClone(), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foundMAC := s.dhcp.MACByIP(ip)
|
||||||
|
if foundMAC != nil {
|
||||||
|
return s.FindByMAC(foundMAC)
|
||||||
|
}
|
||||||
|
|
||||||
p = s.index.findByIPWithoutZone(ip)
|
p = s.index.findByIPWithoutZone(ip)
|
||||||
if p != nil {
|
if p != nil {
|
||||||
return p.ShallowClone(), true
|
return p.ShallowClone(), true
|
||||||
@@ -671,3 +677,44 @@ func (s *Storage) ClearUpstreamCache() {
|
|||||||
|
|
||||||
s.upstreamManager.clearUpstreamCache()
|
s.upstreamManager.clearUpstreamCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyClientFiltering retrieves persistent client information using the
|
||||||
|
// ClientID or client IP address, and applies it to the filtering settings.
|
||||||
|
// setts must not be nil.
|
||||||
|
func (s *Storage) ApplyClientFiltering(id string, addr netip.Addr, setts *filtering.Settings) {
|
||||||
|
c, ok := s.index.findByClientID(id)
|
||||||
|
if !ok {
|
||||||
|
c, ok = s.index.findByIP(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
foundMAC := s.dhcp.MACByIP(addr)
|
||||||
|
if foundMAC != nil {
|
||||||
|
c, ok = s.FindByMAC(foundMAC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
s.logger.Debug("no client filtering settings found", "clientid", id, "addr", addr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("applying custom client filtering settings", "client_name", c.Name)
|
||||||
|
|
||||||
|
if c.UseOwnBlockedServices {
|
||||||
|
setts.BlockedServices = c.BlockedServices.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
setts.ClientName = c.Name
|
||||||
|
setts.ClientTags = slices.Clone(c.Tags)
|
||||||
|
if !c.UseOwnSettings {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setts.FilteringEnabled = c.FilteringEnabled
|
||||||
|
setts.SafeSearchEnabled = c.SafeSearchConf.Enabled
|
||||||
|
setts.ClientSafeSearch = c.SafeSearch
|
||||||
|
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
||||||
|
setts.ParentalEnabled = c.ParentalEnabled
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ func (m *upstreamManager) isConfigChanged(cliConf *customUpstreamConfig) (ok boo
|
|||||||
// upstream configuration.
|
// upstream configuration.
|
||||||
func (m *upstreamManager) clearUpstreamCache() {
|
func (m *upstreamManager) clearUpstreamCache() {
|
||||||
for _, c := range m.uidToCustomConf {
|
for _, c := range m.uidToCustomConf {
|
||||||
c.proxyConf.ClearCache()
|
if c.proxyConf != nil {
|
||||||
|
c.proxyConf.ClearCache()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hostSrvName := s.conf.ServerName
|
hostSrvName := s.conf.TLSConf.ServerName
|
||||||
if hostSrvName == "" {
|
if hostSrvName == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
|
|||||||
clientID, err = clientIDFromClientServerName(
|
clientID, err = clientIDFromClientServerName(
|
||||||
hostSrvName,
|
hostSrvName,
|
||||||
cliSrvName,
|
cliSrvName,
|
||||||
s.conf.StrictSNICheck,
|
s.conf.TLSConf.StrictSNICheck,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("clientid check: %w", err)
|
return "", fmt.Errorf("clientid check: %w", err)
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func TestServer_HandleBefore_tls(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, _ := createTestTLS(t, TLSConfig{
|
s, _ := createTestTLS(t, &TLSConfig{
|
||||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||||
ServerName: tlsServerName,
|
ServerName: tlsServerName,
|
||||||
})
|
})
|
||||||
@@ -259,6 +259,7 @@ func TestServer_HandleBefore_udp(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
AllowedClients: tc.allowedClients,
|
AllowedClients: tc.allowedClients,
|
||||||
DisallowedClients: tc.disallowedClients,
|
DisallowedClients: tc.disallowedClients,
|
||||||
|
|||||||
@@ -212,13 +212,13 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tlsConf := TLSConfig{
|
tlsConf := &TLSConfig{
|
||||||
ServerName: tc.confSrvName,
|
ServerName: tc.confSrvName,
|
||||||
StrictSNICheck: tc.strictSNI,
|
StrictSNICheck: tc.strictSNI,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
conf: ServerConfig{TLSConfig: tlsConf},
|
conf: ServerConfig{TLSConf: tlsConf},
|
||||||
baseLogger: slogutil.NewDiscardLogger(),
|
baseLogger: slogutil.NewDiscardLogger(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,12 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/container"
|
"github.com/AdguardTeam/golibs/container"
|
||||||
@@ -34,9 +32,6 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Callbacks for other modules
|
// Callbacks for other modules
|
||||||
|
|
||||||
// FilterHandler is an optional additional filtering callback.
|
|
||||||
FilterHandler func(cliAddr netip.Addr, clientID string, settings *filtering.Settings) `yaml:"-"`
|
|
||||||
|
|
||||||
// ClientsContainer stores the information about special handling of some
|
// ClientsContainer stores the information about special handling of some
|
||||||
// DNS clients.
|
// DNS clients.
|
||||||
ClientsContainer ClientsContainer `yaml:"-"`
|
ClientsContainer ClientsContainer `yaml:"-"`
|
||||||
@@ -172,43 +167,34 @@ type EDNSClientSubnet struct {
|
|||||||
UseCustom bool `yaml:"use_custom"`
|
UseCustom bool `yaml:"use_custom"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
// TLSConfig contains the TLS configuration settings for DNS-over-HTTPS (DoH),
|
||||||
|
// DNS-over-TLS (DoT), DNS-over-QUIC (DoQ), and Discovery of Designated
|
||||||
|
// Resolvers (DDR).
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
cert tls.Certificate
|
// Cert is the TLS certificate used for TLS connections. It is nil if
|
||||||
|
// encryption is disabled.
|
||||||
|
Cert *tls.Certificate
|
||||||
|
|
||||||
TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
// TLSListenAddrs are the addresses to listen on for DoT connections. Each
|
||||||
QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"`
|
// item in the list must be non-nil if Cert is not nil.
|
||||||
HTTPSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"`
|
TLSListenAddrs []*net.TCPAddr
|
||||||
|
|
||||||
// PEM-encoded certificates chain
|
// QUICListenAddrs are the addresses to listen on for DoQ connections. Each
|
||||||
CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"`
|
// item in the list must be non-nil if Cert is not nil.
|
||||||
// PEM-encoded private key
|
QUICListenAddrs []*net.UDPAddr
|
||||||
PrivateKey string `yaml:"private_key" json:"private_key"`
|
|
||||||
|
|
||||||
CertificatePath string `yaml:"certificate_path" json:"certificate_path"`
|
// HTTPSListenAddrs should be the addresses AdGuard Home is listening on for
|
||||||
PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"`
|
// DoH connections. These addresses are announced with DDR. Each item in
|
||||||
|
// the list must be non-nil.
|
||||||
CertificateChainData []byte `yaml:"-" json:"-"`
|
HTTPSListenAddrs []*net.TCPAddr
|
||||||
PrivateKeyData []byte `yaml:"-" json:"-"`
|
|
||||||
|
|
||||||
// ServerName is the hostname of the server. Currently, it is only being
|
// ServerName is the hostname of the server. Currently, it is only being
|
||||||
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
|
// used for ClientID checking and Discovery of Designated Resolvers (DDR).
|
||||||
ServerName string `yaml:"-" json:"-"`
|
ServerName string
|
||||||
|
|
||||||
// DNS names from certificate (SAN) or CN value from Subject
|
|
||||||
dnsNames []string
|
|
||||||
|
|
||||||
// OverrideTLSCiphers, when set, contains the names of the cipher suites to
|
|
||||||
// use. If the slice is empty, the default safe suites are used.
|
|
||||||
OverrideTLSCiphers []string `yaml:"override_tls_ciphers,omitempty" json:"-"`
|
|
||||||
|
|
||||||
// StrictSNICheck controls if the connections with SNI mismatching the
|
// StrictSNICheck controls if the connections with SNI mismatching the
|
||||||
// certificate's ones should be rejected.
|
// certificate's ones should be rejected.
|
||||||
StrictSNICheck bool `yaml:"strict_sni_check" json:"-"`
|
StrictSNICheck bool
|
||||||
|
|
||||||
// hasIPAddrs is set during the certificate parsing and is true if the
|
|
||||||
// configured certificate contains at least a single IP address.
|
|
||||||
hasIPAddrs bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
// DNSCryptConfig is the DNSCrypt server configuration struct.
|
||||||
@@ -243,8 +229,11 @@ type ServerConfig struct {
|
|||||||
// Remove that.
|
// Remove that.
|
||||||
AddrProcConf *client.DefaultAddrProcConfig
|
AddrProcConf *client.DefaultAddrProcConfig
|
||||||
|
|
||||||
|
// TLSConf is the TLS configuration for DNS-over-TLS, DNS-over-QUIC, and
|
||||||
|
// HTTPS. It must not be nil.
|
||||||
|
TLSConf *TLSConfig
|
||||||
|
|
||||||
Config
|
Config
|
||||||
TLSConfig
|
|
||||||
DNSCryptConfig
|
DNSCryptConfig
|
||||||
TLSAllowUnencryptedDoH bool
|
TLSAllowUnencryptedDoH bool
|
||||||
|
|
||||||
@@ -285,6 +274,10 @@ type ServerConfig struct {
|
|||||||
|
|
||||||
// ServePlainDNS defines if plain DNS is allowed for incoming requests.
|
// ServePlainDNS defines if plain DNS is allowed for incoming requests.
|
||||||
ServePlainDNS bool
|
ServePlainDNS bool
|
||||||
|
|
||||||
|
// PendingRequestsEnabled defines if duplicate requests should be forwarded
|
||||||
|
// to upstreams along with the original one.
|
||||||
|
PendingRequestsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamMode is a enumeration of upstream mode representations. See
|
// UpstreamMode is a enumeration of upstream mode representations. See
|
||||||
@@ -328,6 +321,9 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
|||||||
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
||||||
PrivateSubnets: s.privateNets,
|
PrivateSubnets: s.privateNets,
|
||||||
MessageConstructor: s,
|
MessageConstructor: s,
|
||||||
|
PendingRequests: &proxy.PendingRequestsConfig{
|
||||||
|
Enabled: srvConf.PendingRequestsEnabled,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if srvConf.EDNSClientSubnet.UseCustom {
|
if srvConf.EDNSClientSubnet.UseCustom {
|
||||||
@@ -612,45 +608,33 @@ func (conf *ServerConfig) ourAddrsSet() (m addrPortSet, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareTLS - prepares TLS configuration for the DNS proxy
|
// prepareTLS sets up the TLS configuration for the DNS proxy.
|
||||||
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
func (s *Server) prepareTLS(proxyConfig *proxy.Config) (err error) {
|
||||||
if len(s.conf.CertificateChainData) == 0 || len(s.conf.PrivateKeyData) == 0 {
|
if s.conf.TLSConf.Cert == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.conf.TLSConf.TLSListenAddrs == nil && s.conf.TLSConf.QUICListenAddrs == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.TLSListenAddrs == nil && s.conf.QUICListenAddrs == nil {
|
proxyConfig.TLSListenAddr = s.conf.TLSConf.TLSListenAddrs
|
||||||
return nil
|
proxyConfig.QUICListenAddr = s.conf.TLSConf.QUICListenAddrs
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfig.TLSListenAddr = aghalg.CoalesceSlice(
|
cert, err := x509.ParseCertificate(s.conf.TLSConf.Cert.Certificate[0])
|
||||||
s.conf.TLSListenAddrs,
|
|
||||||
proxyConfig.TLSListenAddr,
|
|
||||||
)
|
|
||||||
|
|
||||||
proxyConfig.QUICListenAddr = aghalg.CoalesceSlice(
|
|
||||||
s.conf.QUICListenAddrs,
|
|
||||||
proxyConfig.QUICListenAddr,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse TLS keypair: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(s.conf.cert.Certificate[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
return fmt.Errorf("x509.ParseCertificate(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conf.hasIPAddrs = aghtls.CertificateHasIP(cert)
|
s.hasIPAddrs = aghtls.CertificateHasIP(cert)
|
||||||
|
|
||||||
if s.conf.StrictSNICheck {
|
if s.conf.TLSConf.StrictSNICheck {
|
||||||
if len(cert.DNSNames) != 0 {
|
if len(cert.DNSNames) != 0 {
|
||||||
s.conf.dnsNames = cert.DNSNames
|
s.dnsNames = cert.DNSNames
|
||||||
log.Debug("dns: using certificate's SAN as DNS names: %v", cert.DNSNames)
|
log.Debug("dns: using certificate's SAN as DNS names: %v", cert.DNSNames)
|
||||||
slices.Sort(s.conf.dnsNames)
|
slices.Sort(s.dnsNames)
|
||||||
} else {
|
} else {
|
||||||
s.conf.dnsNames = append(s.conf.dnsNames, cert.Subject.CommonName)
|
s.dnsNames = []string{cert.Subject.CommonName}
|
||||||
log.Debug("dns: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
|
log.Debug("dns: using certificate's CN as DNS name: %s", cert.Subject.CommonName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,11 +683,11 @@ func anyNameMatches(dnsNames []string, sni string) (ok bool) {
|
|||||||
// Called by 'tls' package when Client Hello is received
|
// Called by 'tls' package when Client Hello is received
|
||||||
// If the server name (from SNI) supplied by client is incorrect - we terminate the ongoing TLS handshake.
|
// If the server name (from SNI) supplied by client is incorrect - we terminate the ongoing TLS handshake.
|
||||||
func (s *Server) onGetCertificate(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (s *Server) onGetCertificate(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if s.conf.StrictSNICheck && !anyNameMatches(s.conf.dnsNames, ch.ServerName) {
|
if s.conf.TLSConf.StrictSNICheck && !anyNameMatches(s.dnsNames, ch.ServerName) {
|
||||||
log.Info("dns: tls: unknown SNI in Client Hello: %s", ch.ServerName)
|
log.Info("dns: tls: unknown SNI in Client Hello: %s", ch.ServerName)
|
||||||
return nil, fmt.Errorf("invalid SNI")
|
return nil, fmt.Errorf("invalid SNI")
|
||||||
}
|
}
|
||||||
return &s.conf.cert, nil
|
return s.conf.TLSConf.Cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// preparePlain prepares the plain-DNS configuration for the DNS proxy.
|
// preparePlain prepares the plain-DNS configuration for the DNS proxy.
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
|||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
UseDNS64: true,
|
UseDNS64: true,
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -335,6 +336,7 @@ func TestServer_dns64WithDisabledRDNS(t *testing.T) {
|
|||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
UseDNS64: true,
|
UseDNS64: true,
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -103,16 +103,26 @@ type SystemResolvers interface {
|
|||||||
//
|
//
|
||||||
// The zero Server is empty and ready for use.
|
// The zero Server is empty and ready for use.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
// dnsProxy is the DNS proxy for forwarding client's DNS requests.
|
// addrProc, if not nil, is used to process clients' IP addresses with rDNS,
|
||||||
dnsProxy *proxy.Proxy
|
// WHOIS, etc.
|
||||||
|
addrProc client.AddressProcessor
|
||||||
|
|
||||||
// dnsFilter is the DNS filter for filtering client's DNS requests and
|
// bootstrap is the resolver for upstreams' hostnames.
|
||||||
// responses.
|
bootstrap upstream.Resolver
|
||||||
dnsFilter *filtering.DNSFilter
|
|
||||||
|
// clientIDCache is a temporary storage for ClientIDs that were extracted
|
||||||
|
// during the BeforeRequestHandler stage.
|
||||||
|
clientIDCache cache.Cache
|
||||||
|
|
||||||
// dhcpServer is the DHCP server for accessing lease data.
|
// dhcpServer is the DHCP server for accessing lease data.
|
||||||
dhcpServer DHCP
|
dhcpServer DHCP
|
||||||
|
|
||||||
|
// etcHosts contains the current data from the system's hosts files.
|
||||||
|
etcHosts upstream.Resolver
|
||||||
|
|
||||||
|
// privateNets is the configured set of IP networks considered private.
|
||||||
|
privateNets netutil.SubnetSet
|
||||||
|
|
||||||
// queryLog is the query log for client's DNS requests, responses and
|
// queryLog is the query log for client's DNS requests, responses and
|
||||||
// filtering results.
|
// filtering results.
|
||||||
queryLog querylog.QueryLog
|
queryLog querylog.QueryLog
|
||||||
@@ -120,37 +130,43 @@ type Server struct {
|
|||||||
// stats is the statistics collector for client's DNS usage data.
|
// stats is the statistics collector for client's DNS usage data.
|
||||||
stats stats.Interface
|
stats stats.Interface
|
||||||
|
|
||||||
|
// sysResolvers used to fetch system resolvers to use by default for private
|
||||||
|
// PTR resolving.
|
||||||
|
sysResolvers SystemResolvers
|
||||||
|
|
||||||
// access drops disallowed clients.
|
// access drops disallowed clients.
|
||||||
access *accessManager
|
access *accessManager
|
||||||
|
|
||||||
|
// anonymizer masks the client's IP addresses if needed.
|
||||||
|
anonymizer *aghnet.IPMut
|
||||||
|
|
||||||
// baseLogger is used to create loggers for other entities. It should not
|
// baseLogger is used to create loggers for other entities. It should not
|
||||||
// have a prefix and must not be nil.
|
// have a prefix and must not be nil.
|
||||||
baseLogger *slog.Logger
|
baseLogger *slog.Logger
|
||||||
|
|
||||||
// localDomainSuffix is the suffix used to detect internal hosts. It
|
// dnsFilter is the DNS filter for filtering client's DNS requests and
|
||||||
// must be a valid domain name plus dots on each side.
|
// responses.
|
||||||
localDomainSuffix string
|
dnsFilter *filtering.DNSFilter
|
||||||
|
|
||||||
|
// dnsProxy is the DNS proxy for forwarding client's DNS requests.
|
||||||
|
dnsProxy *proxy.Proxy
|
||||||
|
|
||||||
|
// internalProxy resolves internal requests from the application itself. It
|
||||||
|
// isn't started and so no listen ports are required.
|
||||||
|
internalProxy *proxy.Proxy
|
||||||
|
|
||||||
// ipset processes DNS requests using ipset data. It must not be nil after
|
// ipset processes DNS requests using ipset data. It must not be nil after
|
||||||
// initialization. See [newIpsetHandler].
|
// initialization. See [newIpsetHandler].
|
||||||
ipset *ipsetHandler
|
ipset *ipsetHandler
|
||||||
|
|
||||||
// privateNets is the configured set of IP networks considered private.
|
// dns64Pref is the NAT64 prefix used for DNS64 response mapping. The major
|
||||||
privateNets netutil.SubnetSet
|
// part of DNS64 happens inside the [proxy] package, but there still are
|
||||||
|
// some places where response mapping is needed (e.g. DHCP).
|
||||||
|
dns64Pref netip.Prefix
|
||||||
|
|
||||||
// addrProc, if not nil, is used to process clients' IP addresses with rDNS,
|
// localDomainSuffix is the suffix used to detect internal hosts. It
|
||||||
// WHOIS, etc.
|
// must be a valid domain name plus dots on each side.
|
||||||
addrProc client.AddressProcessor
|
localDomainSuffix string
|
||||||
|
|
||||||
// sysResolvers used to fetch system resolvers to use by default for private
|
|
||||||
// PTR resolving.
|
|
||||||
sysResolvers SystemResolvers
|
|
||||||
|
|
||||||
// etcHosts contains the current data from the system's hosts files.
|
|
||||||
etcHosts upstream.Resolver
|
|
||||||
|
|
||||||
// bootstrap is the resolver for upstreams' hostnames.
|
|
||||||
bootstrap upstream.Resolver
|
|
||||||
|
|
||||||
// bootResolvers are the resolvers that should be used for
|
// bootResolvers are the resolvers that should be used for
|
||||||
// bootstrapping along with [etcHosts].
|
// bootstrapping along with [etcHosts].
|
||||||
@@ -159,34 +175,26 @@ type Server struct {
|
|||||||
// [upstream.Resolver] interface.
|
// [upstream.Resolver] interface.
|
||||||
bootResolvers []*upstream.UpstreamResolver
|
bootResolvers []*upstream.UpstreamResolver
|
||||||
|
|
||||||
// dns64Pref is the NAT64 prefix used for DNS64 response mapping. The major
|
// dnsNames are the DNS names from certificate (SAN) or CN value from
|
||||||
// part of DNS64 happens inside the [proxy] package, but there still are
|
// Subject.
|
||||||
// some places where response mapping is needed (e.g. DHCP).
|
dnsNames []string
|
||||||
dns64Pref netip.Prefix
|
|
||||||
|
|
||||||
// anonymizer masks the client's IP addresses if needed.
|
|
||||||
anonymizer *aghnet.IPMut
|
|
||||||
|
|
||||||
// clientIDCache is a temporary storage for ClientIDs that were extracted
|
|
||||||
// during the BeforeRequestHandler stage.
|
|
||||||
clientIDCache cache.Cache
|
|
||||||
|
|
||||||
// internalProxy resolves internal requests from the application itself. It
|
|
||||||
// isn't started and so no listen ports are required.
|
|
||||||
internalProxy *proxy.Proxy
|
|
||||||
|
|
||||||
// isRunning is true if the DNS server is running.
|
|
||||||
isRunning bool
|
|
||||||
|
|
||||||
// protectionUpdateInProgress is used to make sure that only one goroutine
|
|
||||||
// updating the protection configuration after a pause is running at a time.
|
|
||||||
protectionUpdateInProgress atomic.Bool
|
|
||||||
|
|
||||||
// conf is the current configuration of the server.
|
// conf is the current configuration of the server.
|
||||||
conf ServerConfig
|
conf ServerConfig
|
||||||
|
|
||||||
// serverLock protects Server.
|
// serverLock protects Server.
|
||||||
serverLock sync.RWMutex
|
serverLock sync.RWMutex
|
||||||
|
|
||||||
|
// protectionUpdateInProgress is used to make sure that only one goroutine
|
||||||
|
// updating the protection configuration after a pause is running at a time.
|
||||||
|
protectionUpdateInProgress atomic.Bool
|
||||||
|
|
||||||
|
// isRunning is true if the DNS server is running.
|
||||||
|
isRunning bool
|
||||||
|
|
||||||
|
// hasIPAddrs is set during the certificate parsing and is true if the
|
||||||
|
// configured certificate contains at least a single IP address.
|
||||||
|
hasIPAddrs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultLocalDomainSuffix is the default suffix used to detect internal hosts
|
// defaultLocalDomainSuffix is the default suffix used to detect internal hosts
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||||
@@ -106,6 +107,21 @@ func startDeferStop(t *testing.T, s *Server) {
|
|||||||
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyEmptyClientFiltering is a helper function for tests with
|
||||||
|
// [filtering.Config] that does nothing.
|
||||||
|
func applyEmptyClientFiltering(_ string, _ netip.Addr, _ *filtering.Settings) {}
|
||||||
|
|
||||||
|
// emptyFilteringBlockedServices is a helper function that returns an empty
|
||||||
|
// filtering blocked services for tests.
|
||||||
|
func emptyFilteringBlockedServices() (bsvc *filtering.BlockedServices) {
|
||||||
|
return &filtering.BlockedServices{
|
||||||
|
Schedule: schedule.EmptyWeekly(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTestServer is a helper function that returns a properly initialized
|
||||||
|
// *Server for use in tests, given the provided parameters. It also populates
|
||||||
|
// the filtering configuration with default parameters.
|
||||||
func createTestServer(
|
func createTestServer(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
filterConf *filtering.Config,
|
filterConf *filtering.Config,
|
||||||
@@ -123,6 +139,12 @@ func createTestServer(
|
|||||||
Data: []byte(rules),
|
Data: []byte(rules),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
filterConf.BlockedServices = cmp.Or(filterConf.BlockedServices, emptyFilteringBlockedServices())
|
||||||
|
|
||||||
|
if filterConf.ApplyClientFiltering == nil {
|
||||||
|
filterConf.ApplyClientFiltering = applyEmptyClientFiltering
|
||||||
|
}
|
||||||
|
|
||||||
f, err := filtering.New(filterConf, filters)
|
f, err := filtering.New(filterConf, filters)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -191,17 +213,23 @@ func createServerTLSConfig(t *testing.T) (*tls.Config, []byte, []byte) {
|
|||||||
}, certPem, keyPem
|
}, certPem, keyPem
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) {
|
func createTestTLS(t *testing.T, tlsConf *TLSConfig) (s *Server, certPem []byte) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var keyPem []byte
|
var keyPem []byte
|
||||||
_, certPem, keyPem = createServerTLSConfig(t)
|
_, certPem, keyPem = createServerTLSConfig(t)
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tlsConf.Cert = &cert
|
||||||
|
|
||||||
s = createTestServer(t, &filtering.Config{
|
s = createTestServer(t, &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: tlsConf,
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -210,10 +238,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
|||||||
ServePlainDNS: true,
|
ServePlainDNS: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
|
err = s.Prepare(&s.conf)
|
||||||
s.conf.TLSConfig = tlsConf
|
|
||||||
|
|
||||||
err := s.Prepare(&s.conf)
|
|
||||||
require.NoErrorf(t, err, "failed to prepare server: %s", err)
|
require.NoErrorf(t, err, "failed to prepare server: %s", err)
|
||||||
|
|
||||||
return s, certPem
|
return s, certPem
|
||||||
@@ -332,6 +357,7 @@ func TestServer(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -373,6 +399,7 @@ func TestServer_timeout(t *testing.T) {
|
|||||||
t.Run("custom", func(t *testing.T) {
|
t.Run("custom", func(t *testing.T) {
|
||||||
srvConf := &ServerConfig{
|
srvConf := &ServerConfig{
|
||||||
UpstreamTimeout: testTimeout,
|
UpstreamTimeout: testTimeout,
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -400,6 +427,7 @@ func TestServer_timeout(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s.conf.TLSConf = &TLSConfig{}
|
||||||
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
||||||
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{
|
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
@@ -414,6 +442,7 @@ func TestServer_timeout(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_Prepare_fallbacks(t *testing.T) {
|
func TestServer_Prepare_fallbacks(t *testing.T) {
|
||||||
srvConf := &ServerConfig{
|
srvConf := &ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
FallbackDNS: []string{
|
FallbackDNS: []string{
|
||||||
"#tls://1.1.1.1",
|
"#tls://1.1.1.1",
|
||||||
@@ -444,6 +473,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -465,7 +495,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDoTServer(t *testing.T) {
|
func TestDoTServer(t *testing.T) {
|
||||||
s, certPem := createTestTLS(t, TLSConfig{
|
s, certPem := createTestTLS(t, &TLSConfig{
|
||||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||||
})
|
})
|
||||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||||
@@ -489,7 +519,7 @@ func TestDoTServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDoQServer(t *testing.T) {
|
func TestDoQServer(t *testing.T) {
|
||||||
s, _ := createTestTLS(t, TLSConfig{
|
s, _ := createTestTLS(t, &TLSConfig{
|
||||||
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
|
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
|
||||||
})
|
})
|
||||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||||
@@ -574,6 +604,7 @@ func TestSafeSearch(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -668,6 +699,7 @@ func TestInvalidRequest(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -699,6 +731,7 @@ func TestBlockedRequest(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -736,6 +769,7 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
CacheSize: defaultCacheSize,
|
CacheSize: defaultCacheSize,
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -816,6 +850,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -851,6 +886,7 @@ func TestBlockCNAME(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -925,10 +961,8 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) {
|
|
||||||
settings.FilteringEnabled = false
|
|
||||||
},
|
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
@@ -975,6 +1009,7 @@ func TestNullBlockedRequest(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -1020,10 +1055,12 @@ func TestBlockedCustomIP(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
f, err := filtering.New(&filtering.Config{
|
f, err := filtering.New(&filtering.Config{
|
||||||
ProtectionEnabled: true,
|
ProtectionEnabled: true,
|
||||||
BlockingMode: filtering.BlockingModeCustomIP,
|
ApplyClientFiltering: applyEmptyClientFiltering,
|
||||||
BlockingIPv4: netip.Addr{},
|
BlockedServices: emptyFilteringBlockedServices(),
|
||||||
BlockingIPv6: netip.Addr{},
|
BlockingMode: filtering.BlockingModeCustomIP,
|
||||||
|
BlockingIPv4: netip.Addr{},
|
||||||
|
BlockingIPv6: netip.Addr{},
|
||||||
}, filters)
|
}, filters)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1043,6 +1080,7 @@ func TestBlockedCustomIP(t *testing.T) {
|
|||||||
conf := &ServerConfig{
|
conf := &ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -1098,6 +1136,7 @@ func TestBlockedByHosts(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -1151,6 +1190,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -1176,7 +1216,9 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
|||||||
|
|
||||||
func TestRewrite(t *testing.T) {
|
func TestRewrite(t *testing.T) {
|
||||||
c := &filtering.Config{
|
c := &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
ApplyClientFiltering: applyEmptyClientFiltering,
|
||||||
|
BlockedServices: emptyFilteringBlockedServices(),
|
||||||
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
Rewrites: []*filtering.LegacyRewrite{{
|
Rewrites: []*filtering.LegacyRewrite{{
|
||||||
Domain: "test.com",
|
Domain: "test.com",
|
||||||
Answer: "1.2.3.4",
|
Answer: "1.2.3.4",
|
||||||
@@ -1212,6 +1254,7 @@ func TestRewrite(t *testing.T) {
|
|||||||
assert.NoError(t, s.Prepare(&ServerConfig{
|
assert.NoError(t, s.Prepare(&ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{"8.8.8.8:53"},
|
UpstreamDNS: []string{"8.8.8.8:53"},
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -1322,7 +1365,9 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
|||||||
const localDomain = "lan"
|
const localDomain = "lan"
|
||||||
|
|
||||||
flt, err := filtering.New(&filtering.Config{
|
flt, err := filtering.New(&filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
ApplyClientFiltering: applyEmptyClientFiltering,
|
||||||
|
BlockedServices: emptyFilteringBlockedServices(),
|
||||||
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1344,6 +1389,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
|||||||
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
||||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||||
|
s.conf.TLSConf = &TLSConfig{}
|
||||||
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
||||||
s.conf.Config.ClientsContainer = EmptyClientsContainer{}
|
s.conf.Config.ClientsContainer = EmptyClientsContainer{}
|
||||||
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
||||||
@@ -1411,8 +1457,10 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
flt, err := filtering.New(&filtering.Config{
|
flt, err := filtering.New(&filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
ApplyClientFiltering: applyEmptyClientFiltering,
|
||||||
EtcHosts: hc,
|
BlockedServices: emptyFilteringBlockedServices(),
|
||||||
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
|
EtcHosts: hc,
|
||||||
}, nil)
|
}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1430,6 +1478,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
|||||||
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
s.conf.UDPListenAddrs = []*net.UDPAddr{{}}
|
||||||
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
s.conf.TCPListenAddrs = []*net.TCPAddr{{}}
|
||||||
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
|
||||||
|
s.conf.TLSConf = &TLSConfig{}
|
||||||
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false}
|
||||||
s.conf.Config.ClientsContainer = EmptyClientsContainer{}
|
s.conf.Config.ClientsContainer = EmptyClientsContainer{}
|
||||||
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
s.conf.Config.UpstreamMode = UpstreamModeLoadBalance
|
||||||
@@ -1696,6 +1745,7 @@ func TestServer_Exchange(t *testing.T) {
|
|||||||
srv := createTestServer(t, &filtering.Config{
|
srv := createTestServer(t, &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{upsAddr},
|
UpstreamDNS: []string{upsAddr},
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -1719,6 +1769,7 @@ func TestServer_Exchange(t *testing.T) {
|
|||||||
srv := createTestServer(t, &filtering.Config{
|
srv := createTestServer(t, &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{upsAddr},
|
UpstreamDNS: []string{upsAddr},
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -37,6 +37,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
|
|||||||
srv := createTestServer(t, &filtering.Config{
|
srv := createTestServer(t, &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -17,9 +17,7 @@ import (
|
|||||||
func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filtering.Settings) {
|
func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filtering.Settings) {
|
||||||
setts = s.dnsFilter.Settings()
|
setts = s.dnsFilter.Settings()
|
||||||
setts.ProtectionEnabled = dctx.protectionEnabled
|
setts.ProtectionEnabled = dctx.protectionEnabled
|
||||||
if s.conf.FilterHandler != nil {
|
s.dnsFilter.ApplyAdditionalFiltering(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
|
||||||
s.conf.FilterHandler(dctx.proxyCtx.Addr.Addr(), dctx.clientID, setts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return setts
|
return setts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{
|
EDNSClientSubnet: &EDNSClientSubnet{
|
||||||
@@ -45,8 +46,10 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
f, err := filtering.New(&filtering.Config{
|
f, err := filtering.New(&filtering.Config{
|
||||||
ProtectionEnabled: true,
|
ProtectionEnabled: true,
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
ApplyClientFiltering: applyEmptyClientFiltering,
|
||||||
|
BlockedServices: emptyFilteringBlockedServices(),
|
||||||
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, filters)
|
}, filters)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
f.SetEnabled(true)
|
f.SetEnabled(true)
|
||||||
@@ -76,6 +76,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{},
|
UDPListenAddrs: []*net.UDPAddr{},
|
||||||
TCPListenAddrs: []*net.TCPAddr{},
|
TCPListenAddrs: []*net.TCPAddr{},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||||
FallbackDNS: []string{"9.9.9.10"},
|
FallbackDNS: []string{"9.9.9.10"},
|
||||||
@@ -159,6 +160,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
|||||||
forwardConf := ServerConfig{
|
forwardConf := ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{},
|
UDPListenAddrs: []*net.UDPAddr{},
|
||||||
TCPListenAddrs: []*net.TCPAddr{},
|
TCPListenAddrs: []*net.TCPAddr{},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"},
|
||||||
RatelimitSubnetLenIPv4: 24,
|
RatelimitSubnetLenIPv4: 24,
|
||||||
@@ -369,6 +371,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
|||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
UpstreamTimeout: upsTimeout,
|
UpstreamTimeout: upsTimeout,
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -246,9 +246,9 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
|||||||
|
|
||||||
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
// TODO(e.burkov): Think about storing the FQDN version of the server's
|
||||||
// name somewhere.
|
// name somewhere.
|
||||||
domainName := dns.Fqdn(s.conf.ServerName)
|
domainName := dns.Fqdn(s.conf.TLSConf.ServerName)
|
||||||
|
|
||||||
for _, addr := range s.conf.HTTPSListenAddrs {
|
for _, addr := range s.conf.TLSConf.HTTPSListenAddrs {
|
||||||
values := []dns.SVCBKeyValue{
|
values := []dns.SVCBKeyValue{
|
||||||
&dns.SVCBAlpn{Alpn: []string{"h2"}},
|
&dns.SVCBAlpn{Alpn: []string{"h2"}},
|
||||||
&dns.SVCBPort{Port: uint16(addr.Port)},
|
&dns.SVCBPort{Port: uint16(addr.Port)},
|
||||||
@@ -265,7 +265,7 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
|
|||||||
resp.Answer = append(resp.Answer, ans)
|
resp.Answer = append(resp.Answer, ans)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.hasIPAddrs {
|
if s.hasIPAddrs {
|
||||||
// Only add DNS-over-TLS resolvers in case the certificate contains IP
|
// Only add DNS-over-TLS resolvers in case the certificate contains IP
|
||||||
// addresses.
|
// addresses.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dnsforward
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -77,6 +78,7 @@ func TestServer_ProcessInitial(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := ServerConfig{
|
c := ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
AAAADisabled: tc.aaaaDisabled,
|
AAAADisabled: tc.aaaaDisabled,
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -177,6 +179,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := ServerConfig{
|
c := ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
AAAADisabled: tc.aaaaDisabled,
|
AAAADisabled: tc.aaaaDisabled,
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
@@ -316,6 +319,8 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
_, certPem, keyPem := createServerTLSConfig(t)
|
_, certPem, keyPem := createServerTLSConfig(t)
|
||||||
|
cert, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -328,19 +333,18 @@ func TestServer_ProcessDDRQuery(t *testing.T) {
|
|||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
ClientsContainer: EmptyClientsContainer{},
|
ClientsContainer: EmptyClientsContainer{},
|
||||||
},
|
},
|
||||||
TLSConfig: TLSConfig{
|
TLSConf: &TLSConfig{
|
||||||
ServerName: ddrTestDomainName,
|
ServerName: ddrTestDomainName,
|
||||||
CertificateChainData: certPem,
|
Cert: &cert,
|
||||||
PrivateKeyData: keyPem,
|
TLSListenAddrs: tc.addrsDoT,
|
||||||
TLSListenAddrs: tc.addrsDoT,
|
HTTPSListenAddrs: tc.addrsDoH,
|
||||||
HTTPSListenAddrs: tc.addrsDoH,
|
QUICListenAddrs: tc.addrsDoQ,
|
||||||
QUICListenAddrs: tc.addrsDoQ,
|
|
||||||
},
|
},
|
||||||
ServePlainDNS: true,
|
ServePlainDNS: true,
|
||||||
})
|
})
|
||||||
// TODO(e.burkov): Generate a certificate actually containing the
|
// TODO(e.burkov): Generate a certificate actually containing the
|
||||||
// IP addresses.
|
// IP addresses.
|
||||||
s.conf.hasIPAddrs = true
|
s.hasIPAddrs = true
|
||||||
|
|
||||||
req := createTestMessageWithType(tc.host, tc.qtype)
|
req := createTestMessageWithType(tc.host, tc.qtype)
|
||||||
|
|
||||||
@@ -657,6 +661,7 @@ func TestServer_HandleDNSRequest_restrictLocal(t *testing.T) {
|
|||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
// TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true.
|
// TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true.
|
||||||
// Improve Config declaration for tests.
|
// Improve Config declaration for tests.
|
||||||
Config: Config{
|
Config: Config{
|
||||||
@@ -789,6 +794,7 @@ func TestServer_ProcessUpstream_localPTR(t *testing.T) {
|
|||||||
ServerConfig{
|
ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -818,6 +824,7 @@ func TestServer_ProcessUpstream_localPTR(t *testing.T) {
|
|||||||
ServerConfig{
|
ServerConfig{
|
||||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) {
|
|||||||
s := createTestServer(t, &filtering.Config{
|
s := createTestServer(t, &filtering.Config{
|
||||||
BlockingMode: filtering.BlockingModeDefault,
|
BlockingMode: filtering.BlockingModeDefault,
|
||||||
}, ServerConfig{
|
}, ServerConfig{
|
||||||
|
TLSConf: &TLSConfig{},
|
||||||
Config: Config{
|
Config: Config{
|
||||||
UpstreamMode: UpstreamModeLoadBalance,
|
UpstreamMode: UpstreamModeLoadBalance,
|
||||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||||
@@ -49,6 +49,9 @@ func initBlockedServices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BlockedServices is the configuration of blocked services.
|
// BlockedServices is the configuration of blocked services.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Move to a higher-level package to allow importing the client
|
||||||
|
// package into the filtering package.
|
||||||
type BlockedServices struct {
|
type BlockedServices struct {
|
||||||
// Schedule is blocked services schedule for every day of the week.
|
// Schedule is blocked services schedule for every day of the week.
|
||||||
Schedule *schedule.Weekly `json:"schedule" yaml:"schedule"`
|
Schedule *schedule.Weekly `json:"schedule" yaml:"schedule"`
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package filtering
|
package filtering_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/golibs/netutil"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -50,8 +51,17 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
|1.2.3.5.in-addr.arpa^$dnsrewrite=NOERROR;PTR;new-ptr-with-dot.
|
|1.2.3.5.in-addr.arpa^$dnsrewrite=NOERROR;PTR;new-ptr-with-dot.
|
||||||
`
|
`
|
||||||
|
|
||||||
f, _ := newForTest(t, nil, []Filter{{ID: 0, Data: []byte(text)}})
|
conf := &filtering.Config{
|
||||||
setts := &Settings{
|
SafeBrowsingCacheSize: 10000,
|
||||||
|
ParentalCacheSize: 10000,
|
||||||
|
SafeSearchCacheSize: 1000,
|
||||||
|
CacheTime: 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := filtering.New(conf, []filtering.Filter{{ID: 0, Data: []byte(text)}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
setts := &filtering.Settings{
|
||||||
FilteringEnabled: true,
|
FilteringEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +127,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
host := path.Base(tc.name)
|
host := path.Base(tc.name)
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, tc.dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, tc.dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
dnsrr := res.DNSRewriteResult
|
dnsrr := res.DNSRewriteResult
|
||||||
@@ -141,7 +152,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
dtyp := dns.TypeA
|
dtyp := dns.TypeA
|
||||||
host := path.Base(t.Name())
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "new-cname", res.CanonName)
|
assert.Equal(t, "new-cname", res.CanonName)
|
||||||
@@ -151,7 +163,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
dtyp := dns.TypeA
|
dtyp := dns.TypeA
|
||||||
host := path.Base(t.Name())
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "new-cname-2", res.CanonName)
|
assert.Equal(t, "new-cname-2", res.CanonName)
|
||||||
@@ -162,7 +175,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
dtyp := dns.TypeA
|
dtyp := dns.TypeA
|
||||||
host := path.Base(t.Name())
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Empty(t, res.CanonName)
|
assert.Empty(t, res.CanonName)
|
||||||
@@ -173,7 +187,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
dtyp := dns.TypePTR
|
dtyp := dns.TypePTR
|
||||||
host := path.Base(t.Name())
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, res.DNSRewriteResult)
|
require.NotNil(t, res.DNSRewriteResult)
|
||||||
|
|
||||||
@@ -193,7 +208,8 @@ func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) {
|
|||||||
dtyp := dns.TypePTR
|
dtyp := dns.TypePTR
|
||||||
host := path.Base(t.Name())
|
host := path.Base(t.Name())
|
||||||
|
|
||||||
res, err := f.CheckHostRules(host, dtyp, setts)
|
var res filtering.Result
|
||||||
|
res, err = f.CheckHostRules(host, dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, res.DNSRewriteResult)
|
require.NotNil(t, res.DNSRewriteResult)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -629,3 +630,20 @@ func (d *DNSFilter) enableFiltersLocked(async bool) {
|
|||||||
|
|
||||||
d.SetEnabled(d.conf.FilteringEnabled)
|
d.SetEnabled(d.conf.FilteringEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyAdditionalFiltering enhances the provided filtering settings with
|
||||||
|
// blocked services and client-specific configurations.
|
||||||
|
func (d *DNSFilter) ApplyAdditionalFiltering(cliAddr netip.Addr, clientID string, setts *Settings) {
|
||||||
|
setts.ClientIP = cliAddr
|
||||||
|
|
||||||
|
d.ApplyBlockedServices(setts)
|
||||||
|
d.applyClientFiltering(clientID, cliAddr, setts)
|
||||||
|
if setts.BlockedServices != nil {
|
||||||
|
// TODO(e.burkov): Get rid of this crutch.
|
||||||
|
setts.ServicesRules = nil
|
||||||
|
svcs := setts.BlockedServices.IDs
|
||||||
|
if !setts.BlockedServices.Schedule.Contains(time.Now()) {
|
||||||
|
d.ApplyBlockedServicesList(setts, svcs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ type ServiceEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings are custom filtering settings for a client.
|
// Settings are custom filtering settings for a client.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Move to the client package.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
ClientName string
|
ClientName string
|
||||||
ClientIP netip.Addr
|
ClientIP netip.Addr
|
||||||
@@ -47,6 +49,10 @@ type Settings struct {
|
|||||||
|
|
||||||
ServicesRules []ServiceEntry
|
ServicesRules []ServiceEntry
|
||||||
|
|
||||||
|
// BlockedServices is the configuration of blocked services of a client. It
|
||||||
|
// is nil if the client does not have any blocked services.
|
||||||
|
BlockedServices *BlockedServices
|
||||||
|
|
||||||
ProtectionEnabled bool
|
ProtectionEnabled bool
|
||||||
FilteringEnabled bool
|
FilteringEnabled bool
|
||||||
SafeSearchEnabled bool
|
SafeSearchEnabled bool
|
||||||
@@ -78,6 +84,11 @@ type Config struct {
|
|||||||
|
|
||||||
SafeSearch SafeSearch `yaml:"-"`
|
SafeSearch SafeSearch `yaml:"-"`
|
||||||
|
|
||||||
|
// ApplyClientFiltering retrieves persistent client information using the
|
||||||
|
// ClientID or client IP address, and applies it to the filtering settings.
|
||||||
|
// It must not be nil.
|
||||||
|
ApplyClientFiltering func(clientID string, cliAddr netip.Addr, setts *Settings) `yaml:"-"`
|
||||||
|
|
||||||
// BlockedServices is the configuration of blocked services.
|
// BlockedServices is the configuration of blocked services.
|
||||||
// Per-client settings can override this configuration.
|
// Per-client settings can override this configuration.
|
||||||
BlockedServices *BlockedServices `yaml:"blocked_services"`
|
BlockedServices *BlockedServices `yaml:"blocked_services"`
|
||||||
@@ -244,6 +255,13 @@ type DNSFilter struct {
|
|||||||
// parentalControl is the parental control hash-prefix checker.
|
// parentalControl is the parental control hash-prefix checker.
|
||||||
parentalControlChecker Checker
|
parentalControlChecker Checker
|
||||||
|
|
||||||
|
// applyClientFiltering retrieves persistent client information using the
|
||||||
|
// ClientID or client IP address, and applies it to the filtering settings.
|
||||||
|
//
|
||||||
|
// TODO(s.chzhen): Consider finding a better approach while taking an
|
||||||
|
// import cycle into account.
|
||||||
|
applyClientFiltering func(clientID string, cliAddr netip.Addr, setts *Settings)
|
||||||
|
|
||||||
engineLock sync.RWMutex
|
engineLock sync.RWMutex
|
||||||
|
|
||||||
// confMu protects conf.
|
// confMu protects conf.
|
||||||
@@ -914,10 +932,9 @@ func (d *DNSFilter) matchHost(
|
|||||||
ufReq := &urlfilter.DNSRequest{
|
ufReq := &urlfilter.DNSRequest{
|
||||||
Hostname: host,
|
Hostname: host,
|
||||||
SortedClientTags: setts.ClientTags,
|
SortedClientTags: setts.ClientTags,
|
||||||
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
ClientIP: setts.ClientIP,
|
||||||
ClientIP: setts.ClientIP,
|
ClientName: setts.ClientName,
|
||||||
ClientName: setts.ClientName,
|
DNSType: rrtype,
|
||||||
DNSType: rrtype,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d.engineLock.RLock()
|
d.engineLock.RLock()
|
||||||
@@ -998,6 +1015,7 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
|||||||
refreshLock: &sync.Mutex{},
|
refreshLock: &sync.Mutex{},
|
||||||
safeBrowsingChecker: c.SafeBrowsingChecker,
|
safeBrowsingChecker: c.SafeBrowsingChecker,
|
||||||
parentalControlChecker: c.ParentalControlChecker,
|
parentalControlChecker: c.ParentalControlChecker,
|
||||||
|
applyClientFiltering: c.ApplyClientFiltering,
|
||||||
confMu: &sync.RWMutex{},
|
confMu: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ func TestHostnameToHashes(t *testing.T) {
|
|||||||
wantLen: 2,
|
wantLen: 2,
|
||||||
}, {
|
}, {
|
||||||
name: "private_domain_v2",
|
name: "private_domain_v2",
|
||||||
host: "foo.blogspot.co.uk",
|
host: "foo.dyndns.org",
|
||||||
wantLen: 4,
|
wantLen: 3,
|
||||||
}, {
|
}, {
|
||||||
name: "sub_private_domain_v2",
|
name: "sub_private_domain_v2",
|
||||||
host: "bar.foo.blogspot.co.uk",
|
host: "bar.foo.dyndns.org",
|
||||||
wantLen: 4,
|
wantLen: 4,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package filtering
|
package filtering_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||||
"github.com/AdguardTeam/golibs/testutil"
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
"github.com/AdguardTeam/urlfilter/rules"
|
"github.com/AdguardTeam/urlfilter/rules"
|
||||||
@@ -50,27 +51,27 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||||
|
|
||||||
conf := &Config{
|
conf := &filtering.Config{
|
||||||
EtcHosts: hc,
|
EtcHosts: hc,
|
||||||
}
|
}
|
||||||
f, err := New(conf, nil)
|
f, err := filtering.New(conf, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
setts := &Settings{
|
setts := &filtering.Settings{
|
||||||
FilteringEnabled: true,
|
FilteringEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
host string
|
host string
|
||||||
wantRules []*ResultRule
|
wantRules []*filtering.ResultRule
|
||||||
wantResps []rules.RRValue
|
wantResps []rules.RRValue
|
||||||
dtyp uint16
|
dtyp uint16
|
||||||
}{{
|
}{{
|
||||||
name: "v4",
|
name: "v4",
|
||||||
host: "v4.host.example",
|
host: "v4.host.example",
|
||||||
dtyp: dns.TypeA,
|
dtyp: dns.TypeA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "1.2.3.4 v4.host.example",
|
Text: "1.2.3.4 v4.host.example",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -79,7 +80,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "v6",
|
name: "v6",
|
||||||
host: "v6.host.example",
|
host: "v6.host.example",
|
||||||
dtyp: dns.TypeAAAA,
|
dtyp: dns.TypeAAAA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "::1 v6.host.example",
|
Text: "::1 v6.host.example",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -88,7 +89,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "mapped",
|
name: "mapped",
|
||||||
host: "mapped.host.example",
|
host: "mapped.host.example",
|
||||||
dtyp: dns.TypeAAAA,
|
dtyp: dns.TypeAAAA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "::ffff:1.2.3.4 mapped.host.example",
|
Text: "::ffff:1.2.3.4 mapped.host.example",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -97,7 +98,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "ptr",
|
name: "ptr",
|
||||||
host: "4.3.2.1.in-addr.arpa",
|
host: "4.3.2.1.in-addr.arpa",
|
||||||
dtyp: dns.TypePTR,
|
dtyp: dns.TypePTR,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "1.2.3.4 v4.host.example",
|
Text: "1.2.3.4 v4.host.example",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -106,7 +107,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "ptr-mapped",
|
name: "ptr-mapped",
|
||||||
host: "4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
host: "4.0.3.0.2.0.1.0.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
|
||||||
dtyp: dns.TypePTR,
|
dtyp: dns.TypePTR,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "::ffff:1.2.3.4 mapped.host.example",
|
Text: "::ffff:1.2.3.4 mapped.host.example",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -133,7 +134,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "v4_mismatch",
|
name: "v4_mismatch",
|
||||||
host: "v4.host.example",
|
host: "v4.host.example",
|
||||||
dtyp: dns.TypeAAAA,
|
dtyp: dns.TypeAAAA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: fmt.Sprintf("%s v4.host.example", addrv4),
|
Text: fmt.Sprintf("%s v4.host.example", addrv4),
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -142,7 +143,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "v6_mismatch",
|
name: "v6_mismatch",
|
||||||
host: "v6.host.example",
|
host: "v6.host.example",
|
||||||
dtyp: dns.TypeA,
|
dtyp: dns.TypeA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: fmt.Sprintf("%s v6.host.example", addrv6),
|
Text: fmt.Sprintf("%s v6.host.example", addrv6),
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -163,7 +164,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
name: "v4_dup",
|
name: "v4_dup",
|
||||||
host: "v4.host.with-dup",
|
host: "v4.host.with-dup",
|
||||||
dtyp: dns.TypeA,
|
dtyp: dns.TypeA,
|
||||||
wantRules: []*ResultRule{{
|
wantRules: []*filtering.ResultRule{{
|
||||||
Text: "4.3.2.1 v4.host.with-dup",
|
Text: "4.3.2.1 v4.host.with-dup",
|
||||||
FilterListID: rulelist.URLFilterIDEtcHosts,
|
FilterListID: rulelist.URLFilterIDEtcHosts,
|
||||||
}},
|
}},
|
||||||
@@ -172,7 +173,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var res Result
|
var res filtering.Result
|
||||||
res, err = f.CheckHost(tc.host, tc.dtyp, setts)
|
res, err = f.CheckHost(tc.host, tc.dtyp, setts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user