Compare commits
22 Commits
v0.108.0-b
...
v0.108.0-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37736289e2 | ||
|
|
2a546aa609 | ||
|
|
d338451faf | ||
|
|
7f83707449 | ||
|
|
24baee0f8e | ||
|
|
a13d120317 | ||
|
|
32bd78347c | ||
|
|
332621f268 | ||
|
|
02ea4a362c | ||
|
|
56b98080ff | ||
|
|
bf38a7ce40 | ||
|
|
34aa81ca99 | ||
|
|
b98e093157 | ||
|
|
60a978c9a6 | ||
|
|
2c055fea11 | ||
|
|
aa872dfe98 | ||
|
|
713901c2a0 | ||
|
|
8936c95eca | ||
|
|
6178ad5762 | ||
|
|
ea620d76be | ||
|
|
136c028600 | ||
|
|
bb2f1b4eb4 |
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -102,6 +102,9 @@
|
||||
the best way. For crashes, please provide a full failure log.
|
||||
'label': 'Action'
|
||||
'value': |
|
||||
Replace the following command with the one you're calling or a
|
||||
description of the failing action:
|
||||
|
||||
```sh
|
||||
nslookup -debug -type=a 'www.example.com' '$YOUR_AGH_ADDRESS'
|
||||
```
|
||||
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'build'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.20.12'
|
||||
'GO_VERSION': '1.21.7'
|
||||
'NODE_VERSION': '16'
|
||||
|
||||
'on':
|
||||
@@ -101,7 +101,10 @@
|
||||
- 'name': 'Set up Docker Buildx'
|
||||
'uses': 'docker/setup-buildx-action@v1'
|
||||
- 'name': 'Run snapshot build'
|
||||
'run': 'make SIGN=0 VERBOSE=1 build-release build-docker'
|
||||
# Set a custom version string, since the checkout@v2 action does not seem
|
||||
# to know about the master branch, while the version script uses it to
|
||||
# count the number of commits within the branch.
|
||||
'run': 'make SIGN=0 VERBOSE=1 VERSION="v0.0.0-github" build-release build-docker'
|
||||
|
||||
'notify':
|
||||
'needs':
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'lint'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.20.12'
|
||||
'GO_VERSION': '1.21.7'
|
||||
|
||||
'on':
|
||||
'push':
|
||||
|
||||
92
CHANGELOG.md
92
CHANGELOG.md
@@ -14,28 +14,86 @@ and this project adheres to
|
||||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.44] - 2023-12-20 (APPROX.)
|
||||
## [v0.107.45] - 2024-03-05 (APPROX.)
|
||||
|
||||
See also the [v0.107.44 GitHub milestone][ms-v0.107.44].
|
||||
See also the [v0.107.45 GitHub milestone][ms-v0.107.45].
|
||||
|
||||
[ms-v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/milestone/79?closed=1
|
||||
[ms-v0.107.45]: https://github.com/AdguardTeam/AdGuardHome/milestone/80?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the Go
|
||||
vulnerabilities fixed in Go 1.21.6 and Go 1.21.7.
|
||||
|
||||
### Added
|
||||
|
||||
- Etc timezones to the timezone list ([#6568]).
|
||||
- Ability to define custom directories for storage of query log files and
|
||||
statistics ([#5992]).
|
||||
- Context menu item in the Query Log to add a Client to the Persistent client
|
||||
list ([#6679]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Starting with this release our scripts are using Go's [forward compatibility
|
||||
mechanism][go-toolchain] for updating the Go version.
|
||||
|
||||
**Important note for porters:** This change means that if your `go` version
|
||||
is 1.21+ but is different from the one required by AdGuard Home, the `go` tool
|
||||
will automatically download the required version.
|
||||
|
||||
If you want to use the version installed on your builder, run:
|
||||
|
||||
```sh
|
||||
go get go@$YOUR_VERSION
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
and call `make` with `GOTOOLCHAIN=local`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Go 1.21 support. Future versions will require at least Go 1.22 to build.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Incorrect tracking of the system hosts file's changes ([#6711]).
|
||||
|
||||
### Removed
|
||||
|
||||
- Go 1.20 support, as it has reached end of life.
|
||||
|
||||
[#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992
|
||||
[#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679
|
||||
[#6711]: https://github.com/AdguardTeam/AdGuardHome/issues/6711
|
||||
|
||||
[go-toolchain]: https://go.dev/blog/toolchain
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.44] - 2024-02-06
|
||||
|
||||
See also the [v0.107.44 GitHub milestone][ms-v0.107.44].
|
||||
|
||||
### Added
|
||||
|
||||
- Timezones in the Etc/ area to the timezone list ([#6568]).
|
||||
- The schema version of the configuration file to the output of running
|
||||
`AdGuardHome` (or `AdGuardHome.exe) with `-v --version` command-line options
|
||||
`AdGuardHome` (or `AdGuardHome.exe`) with `-v --version` command-line options
|
||||
([#6545]).
|
||||
- Ability to disable plain-DNS serving via UI if an encrypted protocol is
|
||||
already used ([#1660]).
|
||||
|
||||
### Changed
|
||||
|
||||
- The bootstrapped upstream addresses now updated according to the TTL of the
|
||||
bootstrap DNS response ([#6321]).
|
||||
- The bootstrapped upstream addresses are now updated according to the TTL of
|
||||
the bootstrap DNS response ([#6321]).
|
||||
- Logging level of timeout errors is now `error` instead of `debug` ([#6574]).
|
||||
- The field `"upstream_mode"` in `POST /control/dns_config` and
|
||||
`GET /control/dns_info` HTTP APIs now accepts `load_balance` value. Check
|
||||
@@ -45,8 +103,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||
|
||||
In this release, the schema version has changed from 27 to 28.
|
||||
|
||||
- The new property `clients.persistent.*.uid`, which is unique identifier of the
|
||||
persistent client.
|
||||
- The new property `clients.persistent.*.uid`, which is a unique identifier of
|
||||
the persistent client.
|
||||
- The properties `dns.all_servers` and `dns.fastest_addr` were removed, their
|
||||
values migrated to newly added field `dns.upstream_mode` that describes the
|
||||
logic through which upstreams will be used. See also a [Wiki
|
||||
@@ -71,6 +129,8 @@ In this release, the schema version has changed from 27 to 28.
|
||||
|
||||
### Fixed
|
||||
|
||||
- “Invalid AddrPort” in the *Private reverse DNS servers* section on the
|
||||
*Settings → DNS settings* page.
|
||||
- Panic on using `--no-etc-hosts` flag ([#6644]).
|
||||
- Schedule display in the client settings after creating or updating.
|
||||
- Zero value in `querylog.size_memory` disables logging ([#6570]).
|
||||
@@ -98,11 +158,8 @@ In this release, the schema version has changed from 27 to 28.
|
||||
[#6584]: https://github.com/AdguardTeam/AdGuardHome/issues/6584
|
||||
[#6644]: https://github.com/AdguardTeam/AdGuardHome/issues/6644
|
||||
|
||||
[wiki-config]: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
[ms-v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/milestone/79?closed=1
|
||||
[wiki-config]: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration
|
||||
|
||||
|
||||
|
||||
@@ -2750,11 +2807,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...HEAD
|
||||
[v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...v0.107.44
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...HEAD
|
||||
[v0.107.45]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...v0.107.45
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...HEAD
|
||||
[v0.107.44]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.43...v0.107.44
|
||||
[v0.107.43]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.42...v0.107.43
|
||||
[v0.107.42]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.41...v0.107.42
|
||||
[v0.107.41]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.40...v0.107.41
|
||||
|
||||
19
Makefile
19
Makefile
@@ -8,7 +8,7 @@
|
||||
# Makefile. Bump this number every time a significant change is made to
|
||||
# this Makefile.
|
||||
#
|
||||
# AdGuard-Project-Version: 2
|
||||
# AdGuard-Project-Version: 4
|
||||
|
||||
# Don't name these macros "GO" etc., because GNU Make apparently makes
|
||||
# them exported environment variables with the literal value of
|
||||
@@ -27,6 +27,7 @@ DIST_DIR = dist
|
||||
GOAMD64 = v1
|
||||
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
|
||||
GOSUMDB = sum.golang.google.cn
|
||||
GOTOOLCHAIN = go1.21.7
|
||||
GPG_KEY = devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||
NPM = npm
|
||||
@@ -56,15 +57,16 @@ BUILD_RELEASE_DEPS_0 = deps js-build
|
||||
BUILD_RELEASE_DEPS_1 = go-deps
|
||||
|
||||
ENV = env\
|
||||
COMMIT='$(COMMIT)'\
|
||||
CHANNEL='$(CHANNEL)'\
|
||||
GPG_KEY='$(GPG_KEY)'\
|
||||
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
|
||||
COMMIT='$(COMMIT)'\
|
||||
DIST_DIR='$(DIST_DIR)'\
|
||||
GO="$(GO.MACRO)"\
|
||||
GOAMD64="$(GOAMD64)"\
|
||||
GOPROXY='$(GOPROXY)'\
|
||||
GOSUMDB='$(GOSUMDB)'\
|
||||
GOTOOLCHAIN='$(GOTOOLCHAIN)'\
|
||||
GPG_KEY='$(GPG_KEY)'\
|
||||
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
|
||||
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
|
||||
RACE='$(RACE)'\
|
||||
SIGN='$(SIGN)'\
|
||||
@@ -117,6 +119,8 @@ go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
|
||||
# targets.
|
||||
go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh
|
||||
|
||||
go-upd-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-upd-tools.sh
|
||||
|
||||
go-check: go-tools go-lint go-test
|
||||
|
||||
# A quick check to make sure that all supported operating systems can be
|
||||
@@ -132,10 +136,3 @@ openapi-lint: ; cd ./openapi/ && $(YARN) test
|
||||
openapi-show: ; cd ./openapi/ && $(YARN) start
|
||||
|
||||
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
|
||||
|
||||
# TODO(a.garipov): Consider adding to scripts/ and the common project
|
||||
# structure.
|
||||
go-upd-tools:
|
||||
cd ./internal/tools/ &&\
|
||||
"$(GO.MACRO)" get -u &&\
|
||||
"$(GO.MACRO)" mod tidy
|
||||
|
||||
@@ -473,6 +473,9 @@ bug or implementing the feature.
|
||||
[@kongfl888](https://github.com/kongfl888) (originally by
|
||||
[@rufengsuixing](https://github.com/rufengsuixing)).
|
||||
|
||||
* [AdGuardHome sync](https://github.com/bakito/adguardhome-sync) by
|
||||
[@bakito](https://github.com/bakito).
|
||||
|
||||
* [Terminal-based, real-time traffic monitoring and statistics for your AdGuard Home
|
||||
instance](https://github.com/Lissy93/AdGuardian-Term) by
|
||||
[@Lissy93](https://github.com/Lissy93)
|
||||
@@ -491,7 +494,8 @@ bug or implementing the feature.
|
||||
* [Node.js library](https://github.com/Andrea055/AdguardHomeAPI) by
|
||||
[@Andrea055](https://github.com/Andrea055/).
|
||||
|
||||
|
||||
* [Browser Extension](https://github.com/satheshshiva/Adguard-Home-Browser-Ext) by
|
||||
[@satheshshiva](https://github.com/satheshshiva/).
|
||||
|
||||
## <a href="#acknowledgments" id="acknowledgments" name="acknowledgments">Acknowledgments</a>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
@@ -272,7 +272,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final
|
||||
# release is built.
|
||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
@@ -287,4 +287,4 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
'snapcraftChannel': 'edge'
|
||||
|
||||
'stages':
|
||||
@@ -191,7 +191,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
'snapcraftChannel': 'beta'
|
||||
# release-vX.Y.Z branches are the branches from which the actual final
|
||||
# release is built.
|
||||
@@ -207,5 +207,5 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
'snapcraftChannel': 'candidate'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:7.6'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.0'
|
||||
|
||||
'stages':
|
||||
- 'Tests':
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
|
||||
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
|
||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
|
||||
"unblock": "Адблакаваць",
|
||||
"block": "Заблакаваць",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
|
||||
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
|
||||
"unblock_for_this_client_only": "Адблакаваць толькі для гэтага кліента",
|
||||
"add_persistent_client": "Дадаць у захаваныя кліенты",
|
||||
"time_table_header": "Час",
|
||||
"date": "Дата",
|
||||
"domain_name_table_header": "Дамен",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Дадаць ідэнтыфікатар",
|
||||
"form_client_name": "Увядзіце імя кліента",
|
||||
"name": "Назва",
|
||||
"client_name": "Кліент {{id}}",
|
||||
"client_global_settings": "Выкарыстаць глабальныя налады",
|
||||
"client_deleted": "Кліент «{{key}}» паспяхова выдалены",
|
||||
"client_added": "Кліент «{{key}}» паспяхова дададзены",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Odchozí servery byly úspěšně uloženy",
|
||||
"dns_test_ok_toast": "Specifikované DNS servery pracují správně",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohl být použit, zkontrolujte, zda jste ho správně napsali",
|
||||
"dns_test_parsing_error_toast": "Sekce {{section}}: řádek {{line}}: nelze použít, zkontrolujte prosím, zda jste ho správně napsali",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" neodpovídá na testovací požadavky a nemusí fungovat správně",
|
||||
"unblock": "Odblokovat",
|
||||
"block": "Blokovat",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Povolit tohoto klienta",
|
||||
"block_for_this_client_only": "Blokovat pouze pro tohoto klienta",
|
||||
"unblock_for_this_client_only": "Odblokovat pouze pro tohoto klienta",
|
||||
"add_persistent_client": "Přidat jako trvalého klienta",
|
||||
"time_table_header": "Čas",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Název domény",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Názvy hostitelů",
|
||||
"encryption_reset": "Opravdu chcete obnovit nastavení šifrování?",
|
||||
"encryption_warning": "Varování",
|
||||
"encryption_plain_dns_enable": "Povolit běžný DNS",
|
||||
"encryption_plain_dns_desc": "Ve výchozím nastavení je povolen běžný DNS. Můžete ho zakázat, aby všechna zařízení používala šifrovaný DNS. Chcete-li to provést, musíte povolit alespoň jeden šifrovaný protokol DNS",
|
||||
"encryption_plain_dns_error": "Chcete-li zakázat běžný DNS, povolte alespoň jeden šifrovaný protokol DNS",
|
||||
"topline_expiring_certificate": "Váš SSL certifikát brzy vyprší. Aktualizujte <0>Nastavení šifrování</0>.",
|
||||
"topline_expired_certificate": "Váš SSL certifikát vypršel. Aktualizujte <0>Nastavení šifrování</0>.",
|
||||
"form_error_port_range": "Zadejte číslo portu v rozmezí 80-65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Přidat identifikátor",
|
||||
"form_client_name": "Zadejte název klienta",
|
||||
"name": "Název",
|
||||
"client_name": "Klient {{id}}",
|
||||
"client_global_settings": "Použít globální nastavení",
|
||||
"client_deleted": "Klient \"{{key}}\" byl úspěšně odstraněn",
|
||||
"client_added": "Klient \"{{key}}\" byl úspěšně přidán",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream-servere er gemt",
|
||||
"dns_test_ok_toast": "Angivne DNS-servere fungerer korrekt",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": Kunne ikke bruges. Tjek, at du har angivet den korrekt",
|
||||
"dns_test_parsing_error_toast": "Sektion {{section}}: linje {{line}}: kunne ikke anvendes. Tjek at den er angivet korrekt",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" svarer ikke på testforespørgsler og fungerer muligvis ikke korrekt",
|
||||
"unblock": "Afblokering",
|
||||
"block": "Blokering",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Tillad denne klient",
|
||||
"block_for_this_client_only": "Blokér kun for denne klient",
|
||||
"unblock_for_this_client_only": "Afblokér kun for denne klient",
|
||||
"add_persistent_client": "Tilføj som vedvarende klient",
|
||||
"time_table_header": "Tid",
|
||||
"date": "Dato",
|
||||
"domain_name_table_header": "Domænenavn",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Værtsnavne",
|
||||
"encryption_reset": "Sikker på, at du vil nulstille krypteringsindstillingerne?",
|
||||
"encryption_warning": "Advarsel",
|
||||
"encryption_plain_dns_enable": "Aktivér almindelig DNS",
|
||||
"encryption_plain_dns_desc": "Almindelig DNS er aktiveret som standard. Den kan deaktiveres for at tvinge alle enheder til at bruge krypteret DNS. For at gøre dette, aktivér mindst én krypteret DNS-protokol",
|
||||
"encryption_plain_dns_error": "Aktivér mindst én krypteret DNS-protokol for at deaktivere almindelig DNS",
|
||||
"topline_expiring_certificate": "Dit SSL-certifikat er ved at udløbe. Opdatér <0>Krypteringsindstillinger</0>.",
|
||||
"topline_expired_certificate": "Dit SSL-certifikat er udløbet. Opdatér <0>Krypteringsindstillinger</0>.",
|
||||
"form_error_port_range": "Angiv portnummer i intervallet 80-65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Tilføj identifikator",
|
||||
"form_client_name": "Angiv klientnavn",
|
||||
"name": "Navn",
|
||||
"client_name": "Klient {{id}}",
|
||||
"client_global_settings": "Brug globale indstillinger",
|
||||
"client_deleted": "Klient \"{{key}}\" slettet",
|
||||
"client_added": "Klient \"{{key}}\" tilføjet",
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"form_enter_hostname": "Gerätenamen eingeben",
|
||||
"error_details": "Fehlerdetails",
|
||||
"response_details": "Einzelheiten der Antwort",
|
||||
"request_details": "Einzelheiten der Anfrage",
|
||||
"request_details": "Informationen zur Anfrage",
|
||||
"client_details": "Einzelheiten des Clients",
|
||||
"details": "Details",
|
||||
"back": "Zurück",
|
||||
@@ -121,7 +121,7 @@
|
||||
"stats_adult": "Gesperrte jugendgefährdende Websites",
|
||||
"stats_query_domain": "Am häufigsten angefragte Domains",
|
||||
"for_last_hours": "in die letzte {{count}} Stunde",
|
||||
"for_last_hours_plural": "in die letzten {{count}} Stunden",
|
||||
"for_last_hours_plural": "in den letzten {{count}} Stunden",
|
||||
"for_last_days": "am letzten {{count}} Tag",
|
||||
"for_last_days_plural": "in den letzten {{count}} Tage",
|
||||
"stats_disabled": "Die Statistik wurde deaktiviert. Sie können diese in den <0>Einstellungen</0> erneut aktivieren.",
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream-Server erfolgreich gespeichert",
|
||||
"dns_test_ok_toast": "Angegebene DNS-Server arbeiten ordnungsgemäß",
|
||||
"dns_test_not_ok_toast": "Server „{{key}}“: konnte nicht verwendet werden, bitte überprüfen Sie die korrekte Schreibweise",
|
||||
"dns_test_parsing_error_toast": "Abschnitt {{section}}: Zeile {{line}}: konnte nicht verwendet werden, bitte überprüfen Sie, ob alles richtig geschrieben ist",
|
||||
"dns_test_warning_toast": "Upstream „{{key}}“ reagiert nicht auf Testanfragen und funktioniert möglicherweise nicht fehlerfrei",
|
||||
"unblock": "Entsperren",
|
||||
"block": "Sperren",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Diesen Client zulassen",
|
||||
"block_for_this_client_only": "Nur für diesen Client sperren",
|
||||
"unblock_for_this_client_only": "Nur für diesen Client freigeben",
|
||||
"add_persistent_client": "Als dauerhaften Client hinzufügen",
|
||||
"time_table_header": "Zeit",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Domainname",
|
||||
@@ -260,7 +262,7 @@
|
||||
"next_btn": "Nächste",
|
||||
"loading_table_status": "Wird geladen …",
|
||||
"page_table_footer_text": "Seite",
|
||||
"rows_table_footer_text": "Reihen",
|
||||
"rows_table_footer_text": "Zeilen",
|
||||
"updated_custom_filtering_toast": "Benutzerdefinierten Filterregeln erfolgreich gespeichert",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt: {{rule}}",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Hostnamen",
|
||||
"encryption_reset": "Möchten Sie die Verschlüsselungseinstellungen wirklich zurücksetzen?",
|
||||
"encryption_warning": "Warnhinweis",
|
||||
"encryption_plain_dns_enable": "Einfaches DNS aktivieren",
|
||||
"encryption_plain_dns_desc": "Einfaches DNS ist standardmäßig aktiviert. Sie können es deaktivieren, um alle Geräte zu zwingen, verschlüsseltes DNS zu verwenden. Dazu müssen Sie mindestens ein verschlüsseltes DNS-Protokoll aktivieren",
|
||||
"encryption_plain_dns_error": "Um einfaches DNS zu deaktivieren, aktivieren Sie mindestens ein verschlüsseltes DNS-Protokoll",
|
||||
"topline_expiring_certificate": "Ihr SSL-Zertifikat läuft demnächst ab. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
|
||||
"topline_expired_certificate": "Ihr SSL-Zertifikat ist abgelaufen. Aktualisieren Sie Ihre <0>Verschlüsselungseinstellungen</0>.",
|
||||
"form_error_port_range": "Geben Sie die Portnummer zwischen 80 und 65535 ein",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Kennung hinzufügen",
|
||||
"form_client_name": "Clientnamen eingeben",
|
||||
"name": "Name",
|
||||
"client_name": "Client {{id}}",
|
||||
"client_global_settings": "Allgemeine Einstellungen nutzen",
|
||||
"client_deleted": "Client „{{key}}“ erfolgreich entfernt",
|
||||
"client_added": "Client „{{key}}“ erfolgreich hinzugefügt",
|
||||
@@ -680,8 +686,8 @@
|
||||
"clear_cache": "Cache leeren",
|
||||
"make_static": "Statisch machen",
|
||||
"theme_auto_desc": "Automatisch (basierend auf dem Farbschema Ihres Geräts)",
|
||||
"theme_dark_desc": "Dunkles Farbschema",
|
||||
"theme_light_desc": "Helles Farbschema",
|
||||
"theme_dark_desc": "Dunkles Design",
|
||||
"theme_light_desc": "Helles Design",
|
||||
"disable_for_seconds": "Für {{count}} Sekunde",
|
||||
"disable_for_seconds_plural": "Für {{count}} Sekunden",
|
||||
"disable_for_minutes": "Für {{count}} Minute",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream servers successfully saved",
|
||||
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
|
||||
"dns_test_parsing_error_toast": "Section {{section}}: line {{line}}: could not be used, please check that you've written it correctly",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" does not respond to test requests and may not work properly",
|
||||
"unblock": "Unblock",
|
||||
"block": "Block",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Allow this client",
|
||||
"block_for_this_client_only": "Block for this client only",
|
||||
"unblock_for_this_client_only": "Unblock for this client only",
|
||||
"add_persistent_client": "Add as persistent client",
|
||||
"time_table_header": "Time",
|
||||
"date": "Date",
|
||||
"domain_name_table_header": "Domain name",
|
||||
@@ -465,6 +467,7 @@
|
||||
"form_add_id": "Add identifier",
|
||||
"form_client_name": "Enter client name",
|
||||
"name": "Name",
|
||||
"client_name": "Client {{id}}",
|
||||
"client_global_settings": "Use global settings",
|
||||
"client_deleted": "Client \"{{key}}\" successfully deleted",
|
||||
"client_added": "Client \"{{key}}\" successfully added",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Servidores DNS de subida guardados 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_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",
|
||||
"unblock": "Desbloquear",
|
||||
"block": "Bloquear",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Permitir a este cliente",
|
||||
"block_for_this_client_only": "Bloquear solo para este cliente",
|
||||
"unblock_for_this_client_only": "Desbloquear solo para este cliente",
|
||||
"add_persistent_client": "Añadir como cliente persistente",
|
||||
"time_table_header": "Hora",
|
||||
"date": "Fecha",
|
||||
"domain_name_table_header": "Nombre del dominio",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Nombres de hosts",
|
||||
"encryption_reset": "¿Estás seguro de que deseas restablecer la configuración de cifrado?",
|
||||
"encryption_warning": "Advertencia",
|
||||
"encryption_plain_dns_enable": "Activar DNS simple (sin cifrado)",
|
||||
"encryption_plain_dns_desc": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado",
|
||||
"encryption_plain_dns_error": "Para desactivar el DNS simple, activa al menos un protocolo DNS cifrado",
|
||||
"topline_expiring_certificate": "Tu certificado SSL está a punto de expirar. Actualiza la <0>configuración de cifrado</0>.",
|
||||
"topline_expired_certificate": "Tu certificado SSL ha expirado. Actualiza la <0>configuración de cifrado</0>.",
|
||||
"form_error_port_range": "Ingresa el número del puerto en el rango de 80 a 65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Añadir identificador",
|
||||
"form_client_name": "Ingresa el nombre del cliente",
|
||||
"name": "Nombre",
|
||||
"client_name": "Cliente {{id}}",
|
||||
"client_global_settings": "Usar configuración global",
|
||||
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
|
||||
"client_added": "Cliente \"{{key}}\" añadido correctamente",
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"updated_upstream_dns_toast": "سرورهای DNS جریان ارسالی بروز رسانی شده است",
|
||||
"dns_test_ok_toast": "سرورهای DNS تعیین شده بدرستی کار می کنند",
|
||||
"dns_test_not_ok_toast": "سرور \"{{key}}\": نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
|
||||
"dns_test_parsing_error_toast": "بخش {{section}}: خط {{line}}: نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
|
||||
"unblock": "رفع انسداد",
|
||||
"block": "مسدود کردن",
|
||||
"disallow_this_client": "این مشتری را رد کنید",
|
||||
@@ -420,6 +421,7 @@
|
||||
"form_add_id": "افزودن احرازکننده",
|
||||
"form_client_name": "نام کلاینت را وارد کنید",
|
||||
"name": "نام",
|
||||
"client_name": "مشتری {{id}}",
|
||||
"client_global_settings": "استفاده از تنظیمات سراسری",
|
||||
"client_deleted": "کلاینت \"{{key}}\" را با موفقیت حذف کرد",
|
||||
"client_added": "کلاینت \"{{key}}\" را با موفقیت اضافه کرد",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Ylävirtapalvelimet tallennettiin",
|
||||
"dns_test_ok_toast": "Määritetyt DNS-palvelimet toimivat oikein",
|
||||
"dns_test_not_ok_toast": "Palvelin \"{{key}}\": Ei voitu käyttää, tarkista oikeinkirjoitus",
|
||||
"dns_test_parsing_error_toast": "Osio {{section}}: rivi {{line}}: Ei voitu käyttää, tarkista oikeinkirjoitus",
|
||||
"dns_test_warning_toast": "Datavuon \"{{key}}\" ei vastaa testipyyntöihin eikä välttämättä toimi kunnolla",
|
||||
"unblock": "Salli",
|
||||
"block": "Estä",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Salli tämä päätelaite",
|
||||
"block_for_this_client_only": "Estä vain tältä päätelaitteelta",
|
||||
"unblock_for_this_client_only": "Salli vain tälle päätelaitteelle",
|
||||
"add_persistent_client": "Lisää pysyvänä päätelaitteena",
|
||||
"time_table_header": "Aika",
|
||||
"date": "Päiväys",
|
||||
"domain_name_table_header": "Verkkotunnus",
|
||||
@@ -270,12 +272,12 @@
|
||||
"query_log_cleared": "Pyyntöhistorian tyhjennys onnistui",
|
||||
"query_log_updated": "Pyyntöhistorian päivitys onnistui",
|
||||
"query_log_clear": "Tyhjennä pyyntöhistoria",
|
||||
"query_log_retention": "Kyselylokien kierto",
|
||||
"query_log_retention": "Pyyntöhistorian kierto",
|
||||
"query_log_enable": "Käytä historiaa",
|
||||
"query_log_configuration": "Historian määritys",
|
||||
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksissa</0>",
|
||||
"query_log_disabled": "Pyyntöhistoria ei ole käytössä. Voit ottaa sen käyttöön <0>asetuksista</0>.",
|
||||
"query_log_strict_search": "Käytä tarkalle haulle lainausmerkkejä",
|
||||
"query_log_retention_confirm": "Haluatko varmasti muuttaa kyselylokin kiertoa? Jos pienennät intervalliarvoa, osa tiedoista menetetään",
|
||||
"query_log_retention_confirm": "Haluatko varmasti muuttaa pyyntöhistorian kiertoa? Jos pienennät aikaväliä, osa tiedoista menetetään.",
|
||||
"anonymize_client_ip": "Piilota päätelaitteen IP-osoite",
|
||||
"anonymize_client_ip_desc": "Älä tallenna päätelaitteen täydellistä IP-osoitetta historiaan ja tilastoihin.",
|
||||
"dns_config": "DNS-palvelimen määritys",
|
||||
@@ -303,22 +305,22 @@
|
||||
"download_mobileconfig_dot": "Lataa .mobileconfig-tiedosto DNS-over-TLS -käytölle",
|
||||
"download_mobileconfig": "Lataa asetustiedosto",
|
||||
"plain_dns": "Tavallinen DNS",
|
||||
"form_enter_rate_limit": "Syötä rajoitus",
|
||||
"rate_limit": "Pyyntöjen ajoitus",
|
||||
"form_enter_rate_limit": "Syötä pyyntörajoitus",
|
||||
"rate_limit": "Pyyntöajoitus",
|
||||
"edns_enable": "Käytä EDNS-päätelaitealivekkoa",
|
||||
"edns_cs_desc": "Lähetä päätelaitteiden aliverkot DNS-palvelimille.",
|
||||
"edns_cs_desc": "Lisää EDNS Client Subnet (ECS) -valinta ylävirran pyyntöihin ja kirjaa päätelaitteiden lähettämät arvot pyyntöhistoriaan.",
|
||||
"edns_use_custom_ip": "Käytä omaa IP-osoitetta EDNS:lle",
|
||||
"edns_use_custom_ip_desc": "Salli oman IP-osoitteen käyttö EDNS-mekanismille.",
|
||||
"rate_limit_desc": "Päätelaitteelle sallittu pyyntöjen enimmäismäärä sekunnissa. Arvo 0 tarkoittaa rajatonta.",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4-osoitteiden aliverkon etuliitteen pituus",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Aliverkon etuliitteen pituus IPv4-osoitteille, joita käytetään nopeuden rajoittamiseen. Oletusarvo on 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4-aliverkon etuliitteen pituuden tulee olla 0–32",
|
||||
"rate_limit_subnet_len_ipv4_desc": "Pyyntörajoitukseen käytettävien IPv4-osoitteiden aliverkon etuliitteen pituus. Oletusarvo on 24.",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4-aliverkon etuliitteen pituuden tulee olla väliltä 0–32.",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6-osoitteiden aliverkon etuliitteen pituus",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Aliverkon etuliitteen pituus IPv6-osoitteille, joita käytetään nopeuden rajoittamiseen. Oletusarvo on 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6-aliverkon etuliitteen pituuden tulee olla 0–128",
|
||||
"form_enter_rate_limit_subnet_len": "Anna aliverkon etuliitteen pituus nopeuden rajoittamista varten",
|
||||
"rate_limit_whitelist": "Nopeutta rajoittava sallittu luettelo",
|
||||
"rate_limit_whitelist_desc": "IP-osoitteet, jotka eivät kuulu nopeusrajoituksen piiriin",
|
||||
"rate_limit_subnet_len_ipv6_desc": "Pyyntörajoitukseen käytettävien IPv6-osoitteiden aliverkon etuliitteen pituus. Oletusarvo on 56.",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6-aliverkon etuliitteen pituuden tulee olla väliltä 0–128.",
|
||||
"form_enter_rate_limit_subnet_len": "Syötä pyyntörajoitukseen käytettävä aliverkon etuliitteen pituus",
|
||||
"rate_limit_whitelist": "Pyyntörajoituksen ohituslista",
|
||||
"rate_limit_whitelist_desc": "IP-osoitteet, jotka eivät kuulu pyyntörajoituksen piiriin.",
|
||||
"rate_limit_whitelist_placeholder": "Syötä yksi IP-osoite per rivi",
|
||||
"blocking_ipv4_desc": "Estettyyn A-pyyntöön palautettava IP-osoite",
|
||||
"blocking_ipv6_desc": "Estettyyn AAAA-pyyntöön palautettava IP-osoite",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Isäntänimet",
|
||||
"encryption_reset": "Haluatko varmasti palauttaa salausasetukset?",
|
||||
"encryption_warning": "Varoitus",
|
||||
"encryption_plain_dns_enable": "Käytä tavallista DNS:ää",
|
||||
"encryption_plain_dns_desc": "Tavallinen DNS on oletusarvoisesti käytössä. Voit poistaa sen käytöstä pakottaaksesi kaikki laitteet käyttämään salattua DNS:ää. Tätä varten sinun on otettava käyttöön ainakin yksi salattu DNS-protokolla.",
|
||||
"encryption_plain_dns_error": "Voit poistaa tavallisen DNS:n käytöstä ottamalla käyttöön ainakin yhden salatun DNS-protokollan.",
|
||||
"topline_expiring_certificate": "SSL-varmenteesi on erääntymässä. Päivitä <0>Salausasetukset</0>.",
|
||||
"topline_expired_certificate": "SSL-varmenteesi on erääntynyt. Päivitä <0>Salausasetukset</0>.",
|
||||
"form_error_port_range": "Syötä portti väliltä 80-65535",
|
||||
@@ -443,7 +448,7 @@
|
||||
"manual_update": "Seuraa <a>näitä ohjeita</a> päivittääksesi manuaalisesti.",
|
||||
"processing_update": "Odota kun AdGuard Home päivittyy",
|
||||
"clients_title": "Pysyvät päätelaitteet",
|
||||
"clients_desc": "Määritä pysyvät AdGuard Homeen yhdistetyt päätelaitetiedot.",
|
||||
"clients_desc": "Määritä AdGuard Homeen pysyvästi yhdistettyjen päätelaitteiden tiedot.",
|
||||
"settings_global": "Yleinen",
|
||||
"settings_custom": "Mukautettu",
|
||||
"table_client": "Asiakas",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Lisää tunniste",
|
||||
"form_client_name": "Syötä päätelaitteen nimi",
|
||||
"name": "Nimi",
|
||||
"client_name": "Päätelaite {{id}}",
|
||||
"client_global_settings": "Käytä yleisiä asetuksia",
|
||||
"client_deleted": "Päätelaite \"{{key}}\" poistettiin",
|
||||
"client_added": "Päätelaite \"{{key}}\" lisättiin",
|
||||
@@ -475,10 +481,10 @@
|
||||
"access_desc": "Tässä voidaan määrittää AdGuard Homen DNS-palvelimen käyttöoikeussääntöjä.",
|
||||
"access_allowed_title": "Sallitut päätelaitteet",
|
||||
"access_allowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>ClientID</a>-tunnisteista. Jos listalla on kohteita, hyväksyy AdGuard Home pyyntöjä vain näiltä päätelaitteilta.",
|
||||
"access_disallowed_title": "Kielletyt päätelaitteet",
|
||||
"access_disallowed_title": "Estetyt päätelaitteet",
|
||||
"access_disallowed_desc": "Lista CIDR-merkinnöistä, IP-osoitteista tai <a>ClientID</a>-tunnisteista. Jos listalla on kohteita, hylkää AdGuard Home näiden päätelaitteiden pyynnöt. Tätä kenttää ei huomioida, jos sallittuja päätelaitteita on määritetty.",
|
||||
"access_blocked_title": "Kielletyt verkkotunnukset",
|
||||
"access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt näy edes pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
|
||||
"access_blocked_title": "Estetyt verkkotunnukset",
|
||||
"access_blocked_desc": "Ei pidä sekoittaa suodattimiin. AdGuard Home hylkää näiden verkkotunnusten DNS-pyynnöt, eivätkä nämä pyynnöt myöskään näy pyyntöhistoriassa. Tähän voidaan syöttää tarkkoja verkkotunnuksia, jokerimerkkejä tai URL-suodatussääntöjä, kuten \"example.org\", \"*.example.org\" tai \"||example.org^\".",
|
||||
"access_settings_saved": "Käytön asetukset tallennettiin",
|
||||
"updates_checked": "Uusi versio AdGuard Home -ohjelmasta on saatavana\n",
|
||||
"updates_version_equal": "AdGuard Home on ajan tasalla",
|
||||
@@ -557,7 +563,7 @@
|
||||
"ignore_domains": "Ohitettavat verkkotunnukset (erotettu rivinvaihdolla)",
|
||||
"ignore_domains_title": "Ohitettavat verkkotunnukset",
|
||||
"ignore_domains_desc_stats": "Sääntöihin sopivat kyselyt eivät kirjoitu tilastoihin",
|
||||
"ignore_domains_desc_query": "Sääntöihin sopivat kyselyt eivät tallennu kyselylokiin",
|
||||
"ignore_domains_desc_query": "Näitä sääntöjä vastaavia pyyntöjä ei tallenneta pyyntöhistoriaan.",
|
||||
"interval_hours": "{{count}} tunti",
|
||||
"interval_hours_plural": "{{count}} tuntia",
|
||||
"filters_configuration": "Suodatinten määritys",
|
||||
@@ -700,8 +706,8 @@
|
||||
"custom_retention_input": "Syötä säilytysaika tunteina",
|
||||
"custom_rotation_input": "Syötä uudistusaika tunteina",
|
||||
"protection_section_label": "Suojaus",
|
||||
"log_and_stats_section_label": "Kyselyhistoria ja tilastot",
|
||||
"ignore_query_log": "Älä huomioi tätä päätettä kyselyhistoriassa",
|
||||
"log_and_stats_section_label": "Pyyntöhistoria ja tilastot",
|
||||
"ignore_query_log": "Älä huomioi tätä päätelaitetta pyyntöhistoriassa",
|
||||
"ignore_statistics": "Älä huomioi tätä päätettä tilastoissa",
|
||||
"schedule_services": "Keskeytä palveluesto",
|
||||
"schedule_services_desc": "Määritä palvelunestosuodattimen keskeytysajoitus.",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Serveurs en amont enregistrés",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
|
||||
"dns_test_parsing_error_toast": "La section {{section}}: ligne {{line}}: n'a pas pu être utilisée, veuillez vérifier que vous l'avez écrite correctement",
|
||||
"dns_test_warning_toast": "L'amont « {{key}} » ne répond pas aux demandes de test et peut ne pas fonctionner correctement",
|
||||
"unblock": "Débloquer",
|
||||
"block": "Bloquer",
|
||||
@@ -423,6 +424,9 @@
|
||||
"encryption_hostnames": "Noms d'hôte",
|
||||
"encryption_reset": "Voulez-vous vraiment réinitialiser les paramètres de chiffrement ?",
|
||||
"encryption_warning": "Attention",
|
||||
"encryption_plain_dns_enable": "Activer le DNS simple",
|
||||
"encryption_plain_dns_desc": "Le DNS simple est activé par défaut. Vous pouvez le désactiver pour forcer tous les appareils à utiliser un DNS crypté. Pour faire ça, vous devez activer au moins un protocole DNS crypté",
|
||||
"encryption_plain_dns_error": "Pour désactiver le DNS simple, activez au moins un protocole DNS crypté",
|
||||
"topline_expiring_certificate": "Votre certificat SSL est sur le point d'expirer. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
|
||||
"topline_expired_certificate": "Votre certificat SSL a expiré. Mettez à jour vos <0>Paramètres de chiffrement</0>.",
|
||||
"form_error_port_range": "Saisissez une valeur de port entre 80 et 65535",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Uzvodni poslužitelji uspješno su spremljeni",
|
||||
"dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
|
||||
"dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
|
||||
"dns_test_parsing_error_toast": "Odjeljak {{section}}: redak {{line}}: nije moguće koristiti, provjerite jeste li ispravno napisali",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" ne odgovara na zahtjeve za testiranje i možda neće raditi ispravno",
|
||||
"unblock": "Odblokiraj",
|
||||
"block": "Blokiraj",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Omogući ovog klijenta",
|
||||
"block_for_this_client_only": "Blokiraj samo za ovog klijenta",
|
||||
"unblock_for_this_client_only": "Odblokiraj samo za ovog klijenta",
|
||||
"add_persistent_client": "Dodaj u spremljene klijente",
|
||||
"time_table_header": "Vrijeme",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Naziv domene",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Dodaj identifikator",
|
||||
"form_client_name": "Unesite naziv klijenta",
|
||||
"name": "Naziv",
|
||||
"client_name": "Klijent {{id}}",
|
||||
"client_global_settings": "Koristi globalne postavke",
|
||||
"client_deleted": "Klijent \"{{key}}\" je uspješno uklonjen",
|
||||
"client_added": "Klijent \"{{key}}\" je uspješno dodan",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream szerverek sikeresen mentve",
|
||||
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
|
||||
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
|
||||
"dns_test_parsing_error_toast": "Szekció {{section}}: sor {{line}}: nem használható, ellenőrizze, hogy helyesen írta-e be",
|
||||
"dns_test_warning_toast": "A \"{{key}}\" feltöltés nem válaszol a tesztkérelmekre, és lehet, hogy nem működik megfelelően",
|
||||
"unblock": "Feloldás",
|
||||
"block": "Blokkolás",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Engedélyezés ennek a kliensnek",
|
||||
"block_for_this_client_only": "Tiltás csak ennek a kliensnek",
|
||||
"unblock_for_this_client_only": "Feloldás csak ennek a kliensnek",
|
||||
"add_persistent_client": "Hozzáadás állandó ügyfélként",
|
||||
"time_table_header": "Idő",
|
||||
"date": "Dátum",
|
||||
"domain_name_table_header": "Domain név",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Azonosító hozzáadása",
|
||||
"form_client_name": "Adja meg a kliens nevét",
|
||||
"name": "Név",
|
||||
"client_name": "Ügyfél {{id}}",
|
||||
"client_global_settings": "Globális beállítások használata",
|
||||
"client_deleted": "A(z) \"{{key}}\" kliens sikeresen el lett távolítva",
|
||||
"client_added": "A(z) \"{{key}}\" kliens sikeresen hozzá lett adva",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"bootstrap_dns": "Server DNS bootstrap",
|
||||
"bootstrap_dns_desc": "Alamat IP server DNS yang digunakan untuk menyelesaikan alamat IP resolver DoH/DoT yang Anda tentukan sebagai upstream. Komentar tidak diizinkan.",
|
||||
"fallback_dns_title": "Server DNS cadangan",
|
||||
"fallback_dns_desc": "Daftar server DNS cadangan yang digunakan ketika server DNS hulu tidak merespons. Sintaksnya sama dengan bidang hulu utama di atas.",
|
||||
"fallback_dns_desc": "Daftar server DNS cadangan yang digunakan ketika server hulu DNS tidak merespons. Sintaksnya sama dengan kolom hulu utama di atas.",
|
||||
"fallback_dns_placeholder": "Masukkan satu server DNS cadangan per baris",
|
||||
"local_ptr_title": "Server pembalik DNS pribadi",
|
||||
"local_ptr_desc": "Server DNS yang digunakan AdGuard Home untuk kueri PTR lokal. Server ini digunakan untuk menyelesaikan nama host klien dengan alamat IP pribadi, misalnya \"192.168.12.34\", menggunakan DNS terbalik. Jika tidak disetel, AdGuard Home menggunakan alamat resolver DNS default OS Anda kecuali untuk alamat AdGuard Home itu sendiri.",
|
||||
@@ -20,7 +20,7 @@
|
||||
"resolve_clients_title": "Aktifkan resolusi hostname klien",
|
||||
"resolve_clients_desc": "Menyelesaikan alamat IP klien secara terbalik ke nama host mereka dengan mengirimkan kueri PTR ke resolver yang sesuai (server DNS pribadi untuk klien lokal, server upstream untuk klien dengan alamat IP publik).",
|
||||
"use_private_ptr_resolvers_title": "Gunakan server pembalik DNS pribadi",
|
||||
"use_private_ptr_resolvers_desc": "Lakukan pencarian DNS terbalik untuk alamat yang disajikan secara lokal menggunakan server upstream ini. Jika dinonaktifkan, Adguard Home merespon dengan NXDOMAIN untuk semua permintaan PTR tersebut kecuali untuk klien yang diketahui dari DHCP, /etc/hosts, dan seterusnya.",
|
||||
"use_private_ptr_resolvers_desc": "Lakukan pencarian DNS terbalik untuk alamat yang disajikan secara lokal menggunakan server hulu ini. Jika dinonaktifkan, Adguard Home merespons dengan NXDOMAIN untuk semua permintaan PTR tersebut kecuali untuk klien yang diketahui dari DHCP, /etc/hosts, dan seterusnya.",
|
||||
"check_dhcp_servers": "Cek untuk server DHCP",
|
||||
"save_config": "Simpan pengaturan",
|
||||
"enabled_dhcp": "Server DHCP diaktifkan",
|
||||
@@ -68,7 +68,7 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Nama host",
|
||||
"dhcp_table_expires": "Kadaluwarsa",
|
||||
"dhcp_warning": "Jika anda ingin mengaktifkan server DHCP bawaan, pastikan tidak ada server DHCP lain yang aktif. Jika tidak, akan memutus koneksi internet pada perangkat yang telah terhubung!",
|
||||
"dhcp_warning": "Jika Anda tetap ingin mengaktifkan server DHCP, pastikan tidak ada server DHCP lain yang aktif di jaringan Anda, karena hal ini dapat memutus konektivitas Internet untuk perangkat di jaringan!",
|
||||
"dhcp_error": "AdGuard Home tidak dapat menentukan apakah ada server DHCP aktif lain pada jaringan",
|
||||
"dhcp_static_ip_error": "Jika ingin menggunakan server DHCP, alamat IP statis harus diatur. AdGuard Home gagal menentukan jika antarmuka jaringan ini dikonfigurasi menggunakan alamat IP statis. Silakan atur alamat IP statis secara manual.",
|
||||
"dhcp_dynamic_ip_found": "Sistem Anda menggunakan konfigurasi alamat IP dinamis untuk antarmuka <0>{{interfaceName}}</0>. Untuk menggunakan server DHCP, alamat IP statis harus ditetapkan. Alamat IP Anda saat ini adalah <0>{{ipAddress}}</0>. AdGuard Home akan secara otomatis menetapkan alamat IP ini sebagai statis jika Anda menekan tombol Aktifkan DHCP.",
|
||||
@@ -144,7 +144,7 @@
|
||||
"enforced_save_search": "Paksa pencarian aman",
|
||||
"number_of_dns_query_to_safe_search": "Jumlah perminataan DNS ke mesin pencari yang dipaksa Pencarian Aman",
|
||||
"average_processing_time": "Rata-rata waktu pemrosesan",
|
||||
"average_upstream_response_time": "Waktu respons server upstream rata-rata",
|
||||
"average_upstream_response_time": "Rata-rata waktu respons hulu",
|
||||
"response_time": "Waktu respons",
|
||||
"average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS",
|
||||
"block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan file hosts",
|
||||
@@ -166,7 +166,7 @@
|
||||
"encryption_settings": "Pengaturan enkripsi",
|
||||
"dhcp_settings": "Pengaturan DHCP",
|
||||
"upstream_dns": "Server DNS hulu",
|
||||
"upstream_dns_help": "Masukkan alamat server per baris. <a>Pelajari lebih</a> mengenai konfigurasi upstream server DNS.",
|
||||
"upstream_dns_help": "Masukkan satu alamat server per baris. <a>Pelajari lebih lanjut</a> mengenai cara mengonfigurasi server DNS hulu.",
|
||||
"upstream_dns_configured_in_file": "Diatur dalam {{path}}",
|
||||
"test_upstream_btn": "Uji hulu",
|
||||
"upstreams": "Upstream",
|
||||
@@ -192,10 +192,10 @@
|
||||
"delete_table_action": "Hapus",
|
||||
"elapsed": "Berlalu",
|
||||
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
|
||||
"no_blocklist_added": "Tiada daftar hitam ditambahkan",
|
||||
"no_whitelist_added": "Tiada daftar putih ditambahkan",
|
||||
"add_blocklist": "Tambah daftar hitam",
|
||||
"add_allowlist": "Tambah daftar putih",
|
||||
"no_blocklist_added": "Tidak ada daftar hitam yang ditambahkan",
|
||||
"no_whitelist_added": "Tidak ada daftar putih yang ditambahkan",
|
||||
"add_blocklist": "Tambahkan daftar hitam",
|
||||
"add_allowlist": "Tambahkan daftar putih",
|
||||
"cancel_btn": "Batal",
|
||||
"enter_name_hint": "Masukkan nama",
|
||||
"enter_url_or_path_hint": "Masukan sebuah URL atau jalur absolut dari daftar",
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Server upstream berhasil disimpan",
|
||||
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
|
||||
"dns_test_parsing_error_toast": "Bagian {{section}}: baris {{line}}: tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan baik",
|
||||
"unblock": "Buka Blokir",
|
||||
"block": "Blok",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Ijinkan klien ini",
|
||||
"block_for_this_client_only": "Blok hanya untuk klien ini",
|
||||
"unblock_for_this_client_only": "Jangan diblok hanya untuk klien ini",
|
||||
"add_persistent_client": "Tambahkan sebagai klien persisten",
|
||||
"time_table_header": "Waktu",
|
||||
"date": "Tanggal",
|
||||
"domain_name_table_header": "Nama domain",
|
||||
@@ -289,7 +291,7 @@
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blokiran IPv4",
|
||||
"blocking_ipv6": "Blokiran IPv6",
|
||||
"blocked_response_ttl": "Respon TLL diblokir",
|
||||
"blocked_response_ttl": "TTL Respons yang diblokir",
|
||||
"blocked_response_ttl_desc": "Menentukan berapa detik klien harus menyimpan respons yang difilter dalam cache",
|
||||
"form_enter_blocked_response_ttl": "Masukkan TTL respons yang diblokir (detik)",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
@@ -330,7 +332,7 @@
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Terang",
|
||||
"theme_dark": "Gelap",
|
||||
"upstream_dns_client_desc": "Jika Anda biarkan bidang ini kosong, AdGuard Home akan memakai server yang dikonfigurasi di<0>Pengaturan DNS</0>.",
|
||||
"upstream_dns_client_desc": "Jika Anda biarkan kolom ini kosong, AdGuard Home akan menggunakan server yang dikonfigurasi di <0>pengaturan DNS</0>.",
|
||||
"tracker_source": "Sumber pelacak",
|
||||
"source_label": "Sumber",
|
||||
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
||||
@@ -340,7 +342,7 @@
|
||||
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
||||
"known_tracker": "Pelacak yang dikenal",
|
||||
"install_welcome_title": "Selamat datang di AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home adalah sebuah server DNS pemblokiran iklan dan pelacak di jaringan. Tujuannya adalah memungkinkan anda mengkontrol seluruh jaringan dan semua perangkat anda, dan ini tidak membutuhkan aplikasi tambahan di klien",
|
||||
"install_welcome_desc": "AdGuard Home adalah server DNS pemblokir iklan dan pelacak di seluruh jaringan. Tujuannya untuk memungkinkan Anda mengendalikan seluruh jaringan dan semua perangkat Anda, dan tidak perlu menggunakan program sisi klien.",
|
||||
"install_settings_title": "Antarmuka Halaman Admin",
|
||||
"install_settings_listen": "Antarmuka pengoperasian",
|
||||
"install_settings_port": "Port",
|
||||
@@ -362,10 +364,10 @@
|
||||
"install_submit_title": "Selamat!",
|
||||
"install_submit_desc": "Prosedur pengaturan telah selesai, dan anda siap untuk mulai menggunakan AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Setelan ini akan secara otomatis mencakup semua perangkat yang terhubung ke router rumah anda dan anda tak perlu mengkonfigurasikan secara manual.",
|
||||
"install_devices_router_desc": "Penyiapan ini secara otomatis mencakup semua perangkat yang terhubung ke router rumah Anda, tidak perlu mengkonfigurasi masing-masing perangkat secara manual.",
|
||||
"install_devices_address": "Server DNS AdGuard Home akan menggunakan alamat berikut",
|
||||
"install_devices_router_list_1": "Buka preferensi untuk router Anda. Biasanya, Anda dapat mengaksesnya dari browser Anda melalui URL, seperti http://192.168.0.1/ atau http://192.168.1.1/. Anda mungkin diminta untuk memasukkan kata sandi. Jika Anda tidak mengingatnya, Anda sering kali dapat mengatur ulang kata sandi dengan menekan tombol pada perute itu sendiri, tetapi perlu diketahui bahwa jika prosedur ini dipilih, Anda mungkin akan kehilangan seluruh konfigurasi perute. Jika router Anda memerlukan aplikasi untuk menyiapkannya, instal aplikasi tersebut di ponsel atau PC Anda dan gunakan untuk mengakses pengaturan router.",
|
||||
"install_devices_router_list_2": "Temukan pengaturan DHCP / DNS. Cari huruf DNS di sebelah bidang yang memungkinkan dua atau tiga set angka, masing-masing dipecah menjadi empat grup dengan satu hingga tiga digit.",
|
||||
"install_devices_router_list_2": "Temukan pengaturan DHCP / DNS. Cari huruf DNS di sebelah kolom yang memungkinkan dua atau tiga set angka, masing-masing dipecah menjadi empat kelompok dengan satu hingga tiga digit.",
|
||||
"install_devices_router_list_3": "Masukkan alamat server AdGuard Home disana",
|
||||
"install_devices_router_list_4": "Anda tidak dapat menyetel server DNS kustom pada beberapa tipe router. Dalam hal ini mungkin membantu jika Anda mengatur AdGuard Home sebagai <0>server DHCP</0>. Jika tidak, Anda harus mencari petunjuk tentang cara mengkustomisasi server DNS untuk model router khusus Anda.",
|
||||
"install_devices_windows_list_1": "Buka Panel Kontrol melalui menu Start atau pencarian Windows.",
|
||||
@@ -386,7 +388,7 @@
|
||||
"install_devices_ios_list_1": "Dari layar beranda, ketuk Pengaturan.",
|
||||
"install_devices_ios_list_2": "Pilih Wi-Fi di menu sebelah kiri (tidak mungkin untuk mengkonfigurasi DNS untuk jaringan seluler).",
|
||||
"install_devices_ios_list_3": "Ketuk nama jaringan yang saat ini aktif.",
|
||||
"install_devices_ios_list_4": "Di bidang DNS, masukkan alamat server AdGuard Home anda.",
|
||||
"install_devices_ios_list_4": "Di kolom DNS, masukkan alamat server AdGuard Home Anda.",
|
||||
"get_started": "Mari mulai",
|
||||
"next": "Selanjutnya",
|
||||
"open_dashboard": "Buka Beranda",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Nama host",
|
||||
"encryption_reset": "Anda yakin ingin mengatur ulang pengaturan enkripsi?",
|
||||
"encryption_warning": "Peringatan",
|
||||
"encryption_plain_dns_enable": "Aktifkan DNS biasa",
|
||||
"encryption_plain_dns_desc": "DNS Biasa diaktifkan secara standar. Anda dapat menonaktifkannya untuk memaksa semua perangkat menggunakan DNS terenkripsi. Untuk melakukan ini, Anda harus mengaktifkan setidaknya satu protokol DNS terenkripsi",
|
||||
"encryption_plain_dns_error": "Untuk menonaktifkan DNS biasa, aktifkan setidaknya satu protokol DNS terenkripsi",
|
||||
"topline_expiring_certificate": "Sertifikat SSL Anda hampir kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
|
||||
"topline_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
|
||||
"form_error_port_range": "Masukkan nomor port di kisaran 80-65535",
|
||||
@@ -439,7 +444,7 @@
|
||||
"fix": "Perbaiki",
|
||||
"dns_providers": "Berikut adalah <0>daftar penyedia DNS yang dikenal</0> untuk dipilih.",
|
||||
"update_now": "Perbarui sekarang",
|
||||
"update_failed": "Pembaruan otomatis gagal. Silahkan <a>ikuti petunjuk ini</a> untuk perbarui secara manual.",
|
||||
"update_failed": "Pembaruan otomatis gagal. Silakan <a>ikuti langkah-langkah berikut</a> untuk memperbarui secara manual.",
|
||||
"manual_update": "Silakan <a>mengikuti langkah berikut</a> untuk memperbarui secara manual.",
|
||||
"processing_update": "Silahkan tunggu, AdGuard Home sedang diperbarui",
|
||||
"clients_title": "Klien yang gigih",
|
||||
@@ -449,7 +454,7 @@
|
||||
"table_client": "Klien",
|
||||
"table_name": "Nama",
|
||||
"save_btn": "Simpan",
|
||||
"client_add": "Tambah Klien",
|
||||
"client_add": "Tambahkan Klien",
|
||||
"client_new": "Klien Baru",
|
||||
"client_edit": "Ubah Klien",
|
||||
"client_identifier": "Identifikasi",
|
||||
@@ -459,9 +464,10 @@
|
||||
"form_enter_subnet_ip": "Masukkan alamat IP di subnet \"{{cidr}}\"",
|
||||
"form_enter_mac": "Masukkan MAC",
|
||||
"form_enter_id": "Masukkan pengenal",
|
||||
"form_add_id": "Tambah pengenal",
|
||||
"form_add_id": "Tambahkan pengenal",
|
||||
"form_client_name": "Masukkan nama klien",
|
||||
"name": "Nama",
|
||||
"client_name": "Klien {{id}}",
|
||||
"client_global_settings": "Gunakan pengaturan global",
|
||||
"client_deleted": "Klien \"{{key}}\" berhasil dihapus",
|
||||
"client_added": "Klien \"{{key}}\" berhasil ditambahkan",
|
||||
@@ -476,7 +482,7 @@
|
||||
"access_allowed_title": "Klien yang diizinkan",
|
||||
"access_allowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home hanya akan menerima permintaan dari klien ini.",
|
||||
"access_disallowed_title": "Klien yang tidak diizinkan",
|
||||
"access_disallowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home akan membatalkan permintaan dari klien ini. Bidang ini diabaikan jika ada entri di klien yang diizinkan.",
|
||||
"access_disallowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home akan membatalkan permintaan dari klien ini. Kolom ini diabaikan jika ada entri di daftar putih klien.",
|
||||
"access_blocked_title": "Domain yang diblokir",
|
||||
"access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.",
|
||||
"access_settings_saved": "Pengaturan akses berhasil disimpan",
|
||||
@@ -485,7 +491,7 @@
|
||||
"check_updates_now": "Periksa pembaruan sekarang",
|
||||
"version_request_error": "Pemeriksaan pembaruan gagal. Harap periksa koneksi internet anda.",
|
||||
"dns_privacy": "DNS Privasi",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_1": "<0>DNS melalui TLS:</0> Gunakan <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_3": "<0>Berikut daftar perangkat lunak yang dapat Anda gunakan.</0>",
|
||||
"setup_dns_privacy_4": "Di perangkat iOS 14 atau macOS Big Sur, Anda dapat mengunduh file '.mobileconfig' khusus yang menambahkan server <highlight>DNS-over-HTTPS</highlight> atau <highlight>DNS-over-TLS</highlight> ke pengaturan DNS.",
|
||||
@@ -505,7 +511,7 @@
|
||||
"rewrite_added": "DNS rewrite untuk \"{{key}}\" berhasil ditambahkan",
|
||||
"rewrite_deleted": "DNS rewrite untuk \"{{key}}\" berhasil dihapus",
|
||||
"rewrite_updated": "Penulisan ulang DNS berhasil diperbarui",
|
||||
"rewrite_add": "Tambah DNS rewrite",
|
||||
"rewrite_add": "Tambahkan penulisan ulang DNS",
|
||||
"rewrite_edit": "Edit penulisan ulang DNS",
|
||||
"rewrite_not_found": "Tidak ada DNS rewrite ditemukan",
|
||||
"rewrite_confirm_delete": "Apakah anda yakin ingin menghapus DNS rewrite untuk \"{{key}}\"?",
|
||||
@@ -594,10 +600,10 @@
|
||||
"fastest_addr": "Alamat IP tercepat",
|
||||
"fastest_addr_desc": "Kuiri semua server DNS dan kembalikan alamat IP tercepat diantara semua tanggapan. Ini memperlambat pencarian DNS Sebagai Rumah AdGuard harus menunggu tanggapan dari semua server DNS, tapi meningkatkan konektivitas keseluruhan.",
|
||||
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
|
||||
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
|
||||
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0>Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf dengan /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolved)</0>",
|
||||
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
|
||||
"tags_title": "Tag",
|
||||
"tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>.",
|
||||
"tags_desc": "Anda dapat memilih tag yang sesuai dengan klien. Sertakan tag dalam aturan pemfilteran untuk menerapkannya dengan lebih akurat. <0>Pelajari lebih lanjut</0>.",
|
||||
"form_select_tags": "Pilih tag klien",
|
||||
"check_title": "Periksa penyaringan",
|
||||
"check_desc": "Periksa apakah nama host telah tersaring.",
|
||||
@@ -605,7 +611,7 @@
|
||||
"form_enter_host": "Masukkan nama host",
|
||||
"filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan",
|
||||
"choose_from_list": "Pilih dari daftar",
|
||||
"add_custom_list": "Tambah daftar buatan",
|
||||
"add_custom_list": "Tambahkan daftar kustom",
|
||||
"host_whitelisted": "Host didaftar putihkan",
|
||||
"check_ip": "Alamat IP: {{ip}}",
|
||||
"check_cname": "CNAME: {{cname}}",
|
||||
@@ -649,7 +655,7 @@
|
||||
"enter_cache_size": "Masukkan ukuran cache (bytes)",
|
||||
"enter_cache_ttl_min_override": "Masukkan TTL minimum (detik)",
|
||||
"enter_cache_ttl_max_override": "Masukkan TTL maksimum (detik)",
|
||||
"cache_ttl_min_override_desc": "Perpanjang nilai waktu-online singkat (detik) yang diterima dari server upstream saat menyimpan respons DNS.",
|
||||
"cache_ttl_min_override_desc": "Perpanjang nilai waktu untuk hidup (detik) yang diterima dari server hulu saat menyimpan respons DNS.",
|
||||
"cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS.",
|
||||
"ttl_cache_validation": "Nilai TTL cache minimum harus kurang dari atau sama dengan nilai maksimum",
|
||||
"cache_optimistic": "Caching yang optimis",
|
||||
@@ -736,6 +742,6 @@
|
||||
"friday_short": "Jum",
|
||||
"saturday_short": "Sab",
|
||||
"upstream_dns_cache_configuration": "Konfigurasi cache DNS upstream",
|
||||
"enable_upstream_dns_cache": "Aktifkan cache DNS untuk konfigurasi upstream kustom klien ini",
|
||||
"enable_upstream_dns_cache": "Aktifkan cache DNS untuk konfigurasi hulu kustom pada klien ini",
|
||||
"dns_cache_size": "Ukuran cache DNS, dalam byte"
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "I server upstream sono stati salvati correttamente",
|
||||
"dns_test_ok_toast": "I server DNS specificati funzionano correttamente",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": non può essere utilizzato, assicurati di averlo digitato correttamente",
|
||||
"dns_test_parsing_error_toast": "Sezione {{section}}: riga {{line}}: non può essere usata, controlla se l'hai scritta correttamente",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" non risponde alle richieste di test e potrebbe non funzionare correttamente",
|
||||
"unblock": "Sblocca",
|
||||
"block": "Blocca",
|
||||
@@ -423,6 +424,9 @@
|
||||
"encryption_hostnames": "Nomi host",
|
||||
"encryption_reset": "Sei sicuro di voler ripristinare le impostazioni di crittografia?",
|
||||
"encryption_warning": "Attenzione",
|
||||
"encryption_plain_dns_enable": "Abilita DNS semplice",
|
||||
"encryption_plain_dns_desc": "Il DNS semplice è abilitato per impostazione predefinita. Puoi disabilitarlo per forzare tutti i dispositivi a usare DNS crittografati. Per fare ciò è necessario abilitare almeno un protocollo DNS crittografato",
|
||||
"encryption_plain_dns_error": "Per disabilitare il DNS semplice, abilitare almeno un protocollo DNS crittografato",
|
||||
"topline_expiring_certificate": "Il tuo certificato SSL sta per scadere. Aggiorna le<0> Impostazioni di crittografia </ 0>.",
|
||||
"topline_expired_certificate": "Il tuo certificato SSL è scaduto. Aggiorna le <0> Impostazioni di crittografia </ 0>.",
|
||||
"form_error_port_range": "Immettere il valore della porta nell'intervallo 80-65535",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "アップストリームサーバーを保存しました。",
|
||||
"dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
|
||||
"dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
|
||||
"dns_test_parsing_error_toast": "セクション {{section}}: 行 {{line}}: を使用できませんでした。正しく記述されているか確認してください",
|
||||
"dns_test_warning_toast": "アップストリーム\"{{key}}\"はテストリクエストに応答せず、正しく動作しない可能性があります。",
|
||||
"unblock": "ブロック解除",
|
||||
"block": "ブロック",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "このクライアントを許可する",
|
||||
"block_for_this_client_only": "このクライアントに対してのみブロックする",
|
||||
"unblock_for_this_client_only": "このクライアントに対してのみブロックを解除する",
|
||||
"add_persistent_client": "永続クライアントとして追加する",
|
||||
"time_table_header": "時刻",
|
||||
"date": "購入日時",
|
||||
"domain_name_table_header": "ドメイン名",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "ホスト名",
|
||||
"encryption_reset": "暗号化設定をリセットして良いですか?",
|
||||
"encryption_warning": "警告",
|
||||
"encryption_plain_dns_enable": "プレーンDNSを有効にする",
|
||||
"encryption_plain_dns_desc": "プレーンDNSはデフォルトで有効になっています。無効にして、すべてのデバイスに暗号化された DNS の使用を強制適用できます。これを行うには、少なくとも 1 つの暗号化されたDNSプロトコルを有効にする必要があります。",
|
||||
"encryption_plain_dns_error": "プレーンDNSを無効にするには、暗号化DNSプロトコルを少なくとも 1 つ有効にしてください",
|
||||
"topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
|
||||
"topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
|
||||
"form_error_port_range": "80〜65535 の範囲内でポート番号を入力してください",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "識別子を追加する",
|
||||
"form_client_name": "クライアント名を入力してください",
|
||||
"name": "名前",
|
||||
"client_name": "クライアント {{id}}",
|
||||
"client_global_settings": "グローバル設定を使用する",
|
||||
"client_deleted": "クライアント \"{{key}}\" の削除に成功しました",
|
||||
"client_added": "クライアント \"{{key}}\" の追加に成功しました",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "업스트림 서버가 성공적으로 저장되었습니다",
|
||||
"dns_test_ok_toast": "지정된 DNS 서버가 올바르게 작동하고 있습니다.",
|
||||
"dns_test_not_ok_toast": "서버 '{{key}}': 사용할 수 없습니다, 제대로 작성했는지 확인하세요",
|
||||
"dns_test_parsing_error_toast": "섹션 {{section}}: 줄 {{line}}: 사용할 수 없으며, 올바르게 작성했는지 확인하세요.",
|
||||
"dns_test_warning_toast": "업스트림 '{{key}}'이(가) 테스트 요청에 응답하지 않으며 제대로 작동하지 않을 수 있습니다",
|
||||
"unblock": "차단 해제",
|
||||
"block": "차단",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "클라이언트 허용",
|
||||
"block_for_this_client_only": "이 클라이언트에 대해서만 차단",
|
||||
"unblock_for_this_client_only": "이 클라이언트에 대해서만 차단 해제",
|
||||
"add_persistent_client": "저장된 클라이언트에 추가",
|
||||
"time_table_header": "시간",
|
||||
"date": "날짜",
|
||||
"domain_name_table_header": "도메인명",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "호스트 이름",
|
||||
"encryption_reset": "암호화 설정을 재설정하시겠습니까?",
|
||||
"encryption_warning": "주의",
|
||||
"encryption_plain_dns_enable": "평문 DNS 활성화",
|
||||
"encryption_plain_dns_desc": "평문 DNS가 기본으로 설정되어 있습니다. 비활성화해서 모든 기기가 암호화된 DNS를 사용하도록 할 수 있습니다. 그러려면 암호화된 DNS 프로토콜을 하나 이상 활성화해야 합니다.",
|
||||
"encryption_plain_dns_error": "평문 DNS를 비활성화하려면, 암호화된 DNS 프로토콜을 하나 이상 활성화하세요",
|
||||
"topline_expiring_certificate": "SSL 인증서가 곧 만료됩니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"topline_expired_certificate": "SSL 인증서가 만료되었습니다. 업데이트<0> 암호화 설정</0>.",
|
||||
"form_error_port_range": "80-65535 범위의 포트 번호를 입력하세요",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "식별자 추가",
|
||||
"form_client_name": "클라이언트 이름 입력",
|
||||
"name": "이름",
|
||||
"client_name": "클라이언트 {{id}}",
|
||||
"client_global_settings": "글로벌 설정 사용",
|
||||
"client_deleted": "클라이언트 '{{key}}'이(가) 정상적으로 삭제되었습니다",
|
||||
"client_added": "클라이언트 '{{key}}'이(가) 정상적으로 추가되었습니다",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream-servers succesvol opgeslagen",
|
||||
"dns_test_ok_toast": "Opgegeven DNS-servers werken correct",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": kon niet worden gebruikt, controleer of je het correct hebt geschreven",
|
||||
"dns_test_parsing_error_toast": "Sectie {{section}}: regel {{line}}: kan niet worden gebruikt. Controleer of je het correct hebt geschreven",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" reageert niet op testverzoeken en werkt mogelijk niet goed",
|
||||
"unblock": "Deblokkeren",
|
||||
"block": "Blokkeren",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Toepassing/systeem toelaten",
|
||||
"block_for_this_client_only": "Alleen voor deze cliënt blokkeren",
|
||||
"unblock_for_this_client_only": "Alleen voor deze cliënt deblokkeren",
|
||||
"add_persistent_client": "Toevoegen als permanente client",
|
||||
"time_table_header": "Tijd",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Domein naam",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Hostnamen",
|
||||
"encryption_reset": "Ben je zeker dat je de encryptie instellingen wil resetten?",
|
||||
"encryption_warning": "Waarschuwing",
|
||||
"encryption_plain_dns_enable": "Gewone DNS inschakelen",
|
||||
"encryption_plain_dns_desc": "Gewone DNS is standaard ingeschakeld. Je kunt het uitschakelen om alle apparaten te dwingen versleutelde DNS te gebruiken. Om dit te doen, moet je ten minste één versleuteld DNS-protocol inschakelen",
|
||||
"encryption_plain_dns_error": "Als je gewone DNS wilt uitschakelen, schakel je ten minste één versleuteld DNS-protocol in",
|
||||
"topline_expiring_certificate": "Jouw SSL-certificaat vervalt binnenkort. Werk de <0>encryptie-instellingen</0> bij.",
|
||||
"topline_expired_certificate": "Jouw SSL-certificaat is vervallen. Werk de <0>encryptie-instellingen</0> bij.",
|
||||
"form_error_port_range": "Poortnummer invoeren tussen 80 en 65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "ID toevoegen",
|
||||
"form_client_name": "Vul gebruikersnaam in",
|
||||
"name": "Naam",
|
||||
"client_name": "Client {{id}}",
|
||||
"client_global_settings": "Gebruik globale instelling",
|
||||
"client_deleted": "Gebruiker \"{{key}}\" met succes verwijderd",
|
||||
"client_added": "Gebruiker \"{{key}}\" met succes toegevoegd",
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"updated_upstream_dns_toast": "Oppdaterte oppstrøms-DNS-tjenerne",
|
||||
"dns_test_ok_toast": "De spesifiserte DNS-tjenerne fungerer riktig",
|
||||
"dns_test_not_ok_toast": "Tjeneren «{{key}}» kunne ikke brukes, vennligst dobbeltsjekk at du har skrevet den riktig",
|
||||
"dns_test_parsing_error_toast": "Seksjon {{section}}: linje {{line}}: kunne ikke brukes, vennligst sjekk at du har skrevet det riktig",
|
||||
"unblock": "Tillat",
|
||||
"block": "Blokker",
|
||||
"disallow_this_client": "Ikke tillat denne klienten",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Serwery nadrzędne zostały pomyślnie zapisane",
|
||||
"dns_test_ok_toast": "Określone serwery DNS działają poprawnie",
|
||||
"dns_test_not_ok_toast": "Serwer \"{{key}}\": nie może być użyte, sprawdź, czy zapisano go poprawnie",
|
||||
"dns_test_parsing_error_toast": "Sekcja {{section}}: linia {{line}}: nie może być użyte, sprawdź, czy zapisano go poprawnie",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" nie odpowiada na zapytania testowe i może nie działać prawidłowo",
|
||||
"unblock": "Odblokuj",
|
||||
"block": "Zablokuj",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Pozwól temu klientowi",
|
||||
"block_for_this_client_only": "Zablokuj tylko tego klienta",
|
||||
"unblock_for_this_client_only": "Odblokuj tylko tego klienta",
|
||||
"add_persistent_client": "Dodaj do zapisanych klientów",
|
||||
"time_table_header": "Czas",
|
||||
"date": "Data",
|
||||
"domain_name_table_header": "Nazwa domeny",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Dodaj identyfikator",
|
||||
"form_client_name": "Wpisz nazwę klienta",
|
||||
"name": "Nazwa",
|
||||
"client_name": "Klient {{id}}",
|
||||
"client_global_settings": "Użyj ustawień globalnych",
|
||||
"client_deleted": "Klient \"{{key}}\" został pomyślnie usunięty",
|
||||
"client_added": "Klient \"{{key}}\" został pomyślnie dodany",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Servidores DNS primário salvos com sucesso",
|
||||
"dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
|
||||
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
|
||||
"dns_test_parsing_error_toast": "A seção {{section}}: linha {{line}}: não pôde ser usada. Verifique se foi escrita corretamente",
|
||||
"dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos Solicitações de teste e pode não funcionar corretamente",
|
||||
"unblock": "Desbloquear",
|
||||
"block": "Bloquear",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Permitir este cliente",
|
||||
"block_for_this_client_only": "Bloquear apenas para este cliente",
|
||||
"unblock_for_this_client_only": "Desbloquear apenas para este cliente",
|
||||
"add_persistent_client": "Adicionar como cliente persistente",
|
||||
"time_table_header": "Data",
|
||||
"date": "Data",
|
||||
"domain_name_table_header": "Nome de domínio",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Nomes dos servidores",
|
||||
"encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?",
|
||||
"encryption_warning": "Aviso",
|
||||
"encryption_plain_dns_enable": "Ativar DNS simples (sem criptografia)",
|
||||
"encryption_plain_dns_desc": "O DNS simples (sem criptografia) está ativado por padrão. Você pode desativá-lo para forçar todos os dispositivos a usar DNS criptografado. Para fazer isso, você deve ativar pelo menos um protocolo DNS criptografado",
|
||||
"encryption_plain_dns_error": "Para desativar o DNS simples, ative pelo menos um protocolo DNS criptografado",
|
||||
"topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>",
|
||||
"topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>",
|
||||
"form_error_port_range": "Digite um número de porta entre 80 e 65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Adicionar identificador",
|
||||
"form_client_name": "Digite o nome do cliente",
|
||||
"name": "Nome",
|
||||
"client_name": "Cliente {{id}}",
|
||||
"client_global_settings": "Usar configurações global",
|
||||
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
|
||||
"client_added": "Cliente \"{{key}}\" adicionado com sucesso",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Servidores DNS primário guardados com sucesso",
|
||||
"dns_test_ok_toast": "Os servidores DNS especificados estão a funcionar corretamente",
|
||||
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se o escreveu corretamente",
|
||||
"dns_test_parsing_error_toast": "A seção {{section}}: linha {{line}}: não pôde ser usada. Verifique se foi escrita corretamente",
|
||||
"dns_test_warning_toast": "Servidor DNS primário \"{{key}}\" não responde aos solicitações de teste e pode não funcionar corretamente",
|
||||
"unblock": "Desbloquear",
|
||||
"block": "Bloquear",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Permitir este cliente",
|
||||
"block_for_this_client_only": "Bloquear apenas para este cliente",
|
||||
"unblock_for_this_client_only": "Desbloquear apenas para este cliente",
|
||||
"add_persistent_client": "Adicionar como cliente persistente",
|
||||
"time_table_header": "Data",
|
||||
"date": "Data",
|
||||
"domain_name_table_header": "Nome do domínio",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Nomes dos servidores",
|
||||
"encryption_reset": "Tem a certeza de que deseja repor a definição de criptografia?",
|
||||
"encryption_warning": "Cuidado",
|
||||
"encryption_plain_dns_enable": "Habilitar DNS simples (sem criptografia)",
|
||||
"encryption_plain_dns_desc": "O DNS simples (sem criptografia) está ativado por padrão. Pode desativá-lo para forçar todos os dispositivos a usar DNS criptografado. Para isso, deve ativar pelo menos um protocolo DNS criptografado",
|
||||
"encryption_plain_dns_error": "Para desabilitar o DNS simples, habilite pelo menos um protocolo DNS criptografado",
|
||||
"topline_expiring_certificate": "O seu certificado SSL está prestes a expirar. Atualize as suas <0>definições de criptografia</0>.",
|
||||
"topline_expired_certificate": "O seu certificado SSL está expirado. Atualize as suas <0>definições de criptografia</0>.",
|
||||
"form_error_port_range": "Digite um numero de porta entre 80 e 65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Adicionar identificador",
|
||||
"form_client_name": "Insira o nome do cliente",
|
||||
"name": "Nome",
|
||||
"client_name": "Cliente {{id}}",
|
||||
"client_global_settings": "Usar definições globais",
|
||||
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
|
||||
"client_added": "Cliente \"{{key}}\" adicionado com sucesso",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Serverele din amonte au fost salvate cu succes",
|
||||
"dns_test_ok_toast": "Serverele DNS specificate funcționează corect",
|
||||
"dns_test_not_ok_toast": "Serverul \"{{key}}\": nu a putut fi utilizat, verificați dacă l-ați scris corect",
|
||||
"dns_test_parsing_error_toast": "Secțiune {{section}}: linie {{line}}: nu a putut fi folosit, vă rugăm să verificați dacă l-ați scris corect",
|
||||
"dns_test_warning_toast": "„{{key}}” în amonte nu răspunde la solicitările de testare și s-ar putea să nu funcționeze corect",
|
||||
"unblock": "Deblocați",
|
||||
"block": "Blocați",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Permiteți acest client",
|
||||
"block_for_this_client_only": "Blocați numai pentru acest client",
|
||||
"unblock_for_this_client_only": "Deblocați numai pentru acest client",
|
||||
"add_persistent_client": "Adăugați ca client persistent",
|
||||
"time_table_header": "Ora",
|
||||
"date": "Data",
|
||||
"domain_name_table_header": "Nume domeniu",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Adăugați identificator",
|
||||
"form_client_name": "Introduceți nume client",
|
||||
"name": "Nume",
|
||||
"client_name": "Client {{id}}",
|
||||
"client_global_settings": "Folosiți setări globale",
|
||||
"client_deleted": "Clientul \"{{key}}\" a fost șters cu succes",
|
||||
"client_added": "Clientul \"{{key}}\" a fost adăugat cu succes",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "DNS-серверы успешно обновлены",
|
||||
"dns_test_ok_toast": "Указанные серверы DNS работают корректно",
|
||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: невозможно использовать, проверьте правильность написания",
|
||||
"dns_test_parsing_error_toast": "Раздел {{section}}: строка {{line}}: невозможно использовать, проверьте правильность написания",
|
||||
"dns_test_warning_toast": "Upstream «{{key}}» не отвечает на тестовые запросы и может работать некорректно",
|
||||
"unblock": "Разблокировать",
|
||||
"block": "Заблокировать",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Разрешить доступ клиенту",
|
||||
"block_for_this_client_only": "Заблокировать только для этого клиента",
|
||||
"unblock_for_this_client_only": "Разблокировать только для этого клиента",
|
||||
"add_persistent_client": "Добавить в сохранённые клиенты",
|
||||
"time_table_header": "Время",
|
||||
"date": "Дата",
|
||||
"domain_name_table_header": "Домен",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Имена хостов",
|
||||
"encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
|
||||
"encryption_warning": "Предупреждение",
|
||||
"encryption_plain_dns_enable": "Включить незашифрованный DNS",
|
||||
"encryption_plain_dns_desc": "Незашифрованный DNS включён по умолчанию. Вы можете отключить его, чтобы заставить все устройства использовать зашифрованный DNS. Для этого необходимо включить хотя бы один зашифрованный протокол DNS",
|
||||
"encryption_plain_dns_error": "Чтобы отключить незашифрованный DNS, включите хотя бы один зашифрованный протокол DNS",
|
||||
"topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
|
||||
"topline_expired_certificate": "Ваш SSL-сертификат истёк. Обновите <0>Настройки шифрования</0>.",
|
||||
"form_error_port_range": "Введите номер порта из интервала 80-65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Добавить идентификатор",
|
||||
"form_client_name": "Введите имя клиента",
|
||||
"name": "Имя",
|
||||
"client_name": "Клиент {{id}}",
|
||||
"client_global_settings": "Использовать глобальные настройки",
|
||||
"client_deleted": "Клиент «{{key}}» успешно удалён",
|
||||
"client_added": "Клиент «{{key}}» успешно добавлен",
|
||||
|
||||
@@ -208,9 +208,9 @@
|
||||
"dns_test_not_ok_toast": "\"{{key}}\" සේවාදායක(ය): භාවිතා කිරීමට නොහැකි විය, ඔබ එය නිවැරදිව ලියා ඇතිදැයි පරීක්ෂා කරන්න",
|
||||
"unblock": "අනවහිර",
|
||||
"block": "අවහිර",
|
||||
"disallow_this_client": "මෙම අනුග්රාහකයට නොඉඩ දෙන්න",
|
||||
"disallow_this_client": "මෙම අනුග්රාහකයට ඉඩ නොදෙන්න",
|
||||
"allow_this_client": "මෙම අනුග්රාහකයට ඉඩ දෙන්න",
|
||||
"block_for_this_client_only": "මෙම අනුග්රාහකයට පමණක් අවහිර කරන්න",
|
||||
"block_for_this_client_only": "මෙම අනුග්රාහකයට අවහිර කරන්න",
|
||||
"unblock_for_this_client_only": "මෙම අනුග්රාහකයට පමණක් අනවහිර කරන්න",
|
||||
"time_table_header": "වේලාව",
|
||||
"date": "දිනය",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream servery boli úspešne uložené",
|
||||
"dns_test_ok_toast": "Špecifikované DNS servery pracujú korektne",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": nemohol byť použitý, skontrolujte, či ste ho správne napísali",
|
||||
"dns_test_parsing_error_toast": "Sekcia {{section}}: riadok {{line}}: nepodarilo sa použiť, skontrolujte, či ste ho napísali správne",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" neodpovedá na testovacie dopyty a nemusí fungovať správne",
|
||||
"unblock": "Odblokovať",
|
||||
"block": "Blokovať",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Povoliť tohto klienta",
|
||||
"block_for_this_client_only": "Blokovať len pre tohto klienta",
|
||||
"unblock_for_this_client_only": "Odblokovať len pre tohto klienta",
|
||||
"add_persistent_client": "Pridať ako trvalého klienta",
|
||||
"time_table_header": "Čas",
|
||||
"date": "Dátum",
|
||||
"domain_name_table_header": "Meno domény",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Názvy hostiteľov",
|
||||
"encryption_reset": "Naozaj chcete obnoviť nastavenia šifrovania?",
|
||||
"encryption_warning": "Varovanie",
|
||||
"encryption_plain_dns_enable": "Zapnúť jednoduchý DNS",
|
||||
"encryption_plain_dns_desc": "Jednoduchý DNS je predvolene zapnutý. Môžete ho vypnúť, aby ste prinútili všetky zariadenia používať šifrovaný DNS. Ak to chcete urobiť, musíte zapnúť aspoň jeden šifrovaný DNS protokol",
|
||||
"encryption_plain_dns_error": "Ak chcete vypnúť jednoduchý DNS protokol, zapnite aspoň jeden šifrovaný DNS protokol",
|
||||
"topline_expiring_certificate": "Váš SSL certifikát čoskoro vyprší. Aktualizujte <0>Nastavenia šifrovania</0>.",
|
||||
"topline_expired_certificate": "Váš SSL certifikát vypršal. Aktualizujte <0>Nastavenia šifrovania</0>.",
|
||||
"form_error_port_range": "Zadajte číslo portu v rozsahu 80-65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Pridajte identifikátor",
|
||||
"form_client_name": "Zadajte meno klienta",
|
||||
"name": "Meno",
|
||||
"client_name": "Klient {{id}}",
|
||||
"client_global_settings": "Použiť globálne nastavenia",
|
||||
"client_deleted": "\"{{key}}\" klienta bol úspešne vymazaný",
|
||||
"client_added": "\"{{key}}\" klienta bol úspešne pridaný",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Gorvodni trežniki so uspešno shranjeni",
|
||||
"dns_test_ok_toast": "Navedeni strežniki DNS delujejo pravilno",
|
||||
"dns_test_not_ok_toast": "Ni mogoče uporabiti: strežnika \"{{key}}\". Preverite, ali ste ga pravilno napisali",
|
||||
"dns_test_parsing_error_toast": "Razdelek {{section}}: vrstica {{line}}: ni bilo mogoče uporabiti, preverite, ali ste ga pravilno zapisali",
|
||||
"dns_test_warning_toast": "Upstream \"{{key}}\" se ne odziva na testne zahteve in morda ne deluje pravilno",
|
||||
"unblock": "Omogoči",
|
||||
"block": "Onemogoči",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Dovoli tega odjemalca",
|
||||
"block_for_this_client_only": "Onemogoči samo za tega odjemalca",
|
||||
"unblock_for_this_client_only": "Omogoči samo za tega odjemalca",
|
||||
"add_persistent_client": "Dodaj kot vztrajnega odjemalca",
|
||||
"time_table_header": "Čas",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Ime domene",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Imena gostiteljev",
|
||||
"encryption_reset": "Ali ste prepričani, da želite ponastaviti nastavitve šifriranja?",
|
||||
"encryption_warning": "Opozorilo",
|
||||
"encryption_plain_dns_enable": "Omogoči navaden DNS",
|
||||
"encryption_plain_dns_desc": "Navaden DNS je privzeto omogočen. Lahko ga onemogočite, da vse naprave prisilite k uporabi šifriranega DNS-ja. Če želite to narediti, morate omogočiti vsaj en šifriran protokol DNS",
|
||||
"encryption_plain_dns_error": "Da onemogočite navaden DNS, omogočite vsaj en šifriran protokol DNS",
|
||||
"topline_expiring_certificate": "Vaš e digitalno potrdilo SSL bo kmalu poteklol. Posodobite <0>Nastavitve šifriranja</0>.",
|
||||
"topline_expired_certificate": "Vaše digitalno potrdilo SSL je poteklo. Posodobi <0>Nastavitve šifriranja</0>.",
|
||||
"form_error_port_range": "Vnesite številko vrat v razponu med 80-65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Dodaj identifikatorja",
|
||||
"form_client_name": "Vnesite ime odjemalca",
|
||||
"name": "Ime",
|
||||
"client_name": "Odjemalec {{id}}",
|
||||
"client_global_settings": "Uporabi splošne nastavitve",
|
||||
"client_deleted": "Odjemalec \"{{key}}\" je bil uspešno izbrisan",
|
||||
"client_added": "Odjemalec \"{{key}}\" je bil uspešno dodan",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Upstream serveri su uspešno sačuvani",
|
||||
"dns_test_ok_toast": "Dati DNS serveri rade ispravno",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli",
|
||||
"dns_test_parsing_error_toast": "Odeljak {{section}}: linija {{line}}: ne može se koristiti, molimo proverite da li ste ga ispravno napisali",
|
||||
"dns_test_warning_toast": "Apstrim \"{{key}}\" ne odgovara na zahteve za testiranje i možda neće raditi kako treba",
|
||||
"unblock": "Odblokiraj",
|
||||
"block": "Blokiraj",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Dozvoli ovaj klijent",
|
||||
"block_for_this_client_only": "Blokiraj samo za ovaj klijent",
|
||||
"unblock_for_this_client_only": "Odblokiraj samo za ovaj klijent",
|
||||
"add_persistent_client": "Dodati u sačuvane klijente",
|
||||
"time_table_header": "Vreme",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Ime domena",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Dodaj identifikator",
|
||||
"form_client_name": "Unesite ime klijenta",
|
||||
"name": "Ime",
|
||||
"client_name": "Klijent {{id}}",
|
||||
"client_global_settings": "Koristi globalne postavke",
|
||||
"client_deleted": "Klijent \"{{key}}\" uspešno izbrisan",
|
||||
"client_added": "Klijent \"{{key}}\" uspešno dodat",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Sparade uppströms dns-servrar",
|
||||
"dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte användas. Var snäll och kolla att du skrivit in rätt",
|
||||
"dns_test_parsing_error_toast": "Avsnitt {{section}}: rad {{line}}: kunde inte användas, kontrollera att du har skrivit det korrekt",
|
||||
"dns_test_warning_toast": "Uppströms \"{{key}}\" svarar inte på testförfrågningar och kanske inte fungerar korrekt",
|
||||
"unblock": "Avblockera",
|
||||
"block": "Blockera",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Tillåt den här klienten",
|
||||
"block_for_this_client_only": "Blockera endast för denna klient",
|
||||
"unblock_for_this_client_only": "Avblockera endast för denna klient",
|
||||
"add_persistent_client": "Lägg till som beständig klient",
|
||||
"time_table_header": "Tid",
|
||||
"date": "Datum",
|
||||
"domain_name_table_header": "Domännamn",
|
||||
@@ -461,6 +463,7 @@
|
||||
"form_add_id": "Lägg till identifierare",
|
||||
"form_client_name": "Skriv in klientnamn",
|
||||
"name": "Namn",
|
||||
"client_name": "Klient {{id}}",
|
||||
"client_global_settings": "Använda globala inställningar",
|
||||
"client_deleted": "Klient \"{{key}}\" har raderats",
|
||||
"client_added": "Klient \"{{key}}\" har lagts till",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Üst sunucular başarıyla kaydedildi",
|
||||
"dns_test_ok_toast": "Belirtilen DNS sunucuları düzgün çalışıyor",
|
||||
"dns_test_not_ok_toast": "Sunucu \"{{key}}\": kullanılamıyor, lütfen doğru yazdığınızdan emin olun",
|
||||
"dns_test_parsing_error_toast": "{{section}} bölümü: {{line}}. satır: kullanılamadı, lütfen doğru yazdığınızı kontrol edin",
|
||||
"dns_test_warning_toast": "Üst kaynak \"{{key}}\", test isteklerine yanıt vermiyor ve düzgün çalışmayabilir",
|
||||
"unblock": "Engeli kaldır",
|
||||
"block": "Engelle",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Bu istemciye izin ver",
|
||||
"block_for_this_client_only": "Yalnızca bu istemci için engelle",
|
||||
"unblock_for_this_client_only": "Yalnızca bu istemci için engellemeyi kaldır",
|
||||
"add_persistent_client": "Kalıcı istemci olarak ekle",
|
||||
"time_table_header": "Saat",
|
||||
"date": "Tarih",
|
||||
"domain_name_table_header": "Alan adı",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Ana makine adları",
|
||||
"encryption_reset": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
|
||||
"encryption_warning": "Uyarı",
|
||||
"encryption_plain_dns_enable": "Düz DNS'i etkinleştir",
|
||||
"encryption_plain_dns_desc": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir",
|
||||
"encryption_plain_dns_error": "Düz DNS'i devre dışı bırakmak için en az bir şifrelenmiş DNS protokolünü etkinleştirin",
|
||||
"topline_expiring_certificate": "SSL sertifikanızın süresi sona üzere. <0>Şifreleme ayarlarını</0> güncelleyin.",
|
||||
"topline_expired_certificate": "SSL sertifikanızın süresi sona erdi. <0>Şifreleme ayarlarını</0> güncelleyin.",
|
||||
"form_error_port_range": "80-65535 aralığında geçerli bir bağlantı noktası değeri girin",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Tanımlayıcı ekle",
|
||||
"form_client_name": "İstemci ismi girin",
|
||||
"name": "Adı",
|
||||
"client_name": "İstemci {{id}}",
|
||||
"client_global_settings": "Genel ayarları kullan",
|
||||
"client_deleted": "\"{{key}}\" istemcisi başarıyla silindi",
|
||||
"client_added": "\"{{key}}\" istemcisi başarıyla eklendi",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "DNS-сервери успішно збережено",
|
||||
"dns_test_ok_toast": "Вказані DNS сервери працюють правильно",
|
||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: неможливо використати. Перевірте правильність введення",
|
||||
"dns_test_parsing_error_toast": "Розділ {{section}}: рядок {{line}}: неможливо використати. Перевірте правильність введення",
|
||||
"dns_test_warning_toast": "Upstream «{{key}}» не відповідає на тестові запити та може працювати не правильно",
|
||||
"unblock": "Дозволити",
|
||||
"block": "Заборонити",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Дозволити цей клієнт",
|
||||
"block_for_this_client_only": "Заборонити тільки цей клієнт",
|
||||
"unblock_for_this_client_only": "Дозволити тільки цей клієнт",
|
||||
"add_persistent_client": "Додати в збережені клієнти",
|
||||
"time_table_header": "Час",
|
||||
"date": "Дата",
|
||||
"domain_name_table_header": "Назва домену",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "Назви вузлів",
|
||||
"encryption_reset": "Ви впевнені, що хочете скинути налаштування шифрування?",
|
||||
"encryption_warning": "Попередження",
|
||||
"encryption_plain_dns_enable": "Увімкнути звичайний DNS",
|
||||
"encryption_plain_dns_desc": "Звичайний DNS усталено увімкнений. Ви можете вимкнути його, щоб змусити всі пристрої використовувати зашифрований DNS. Для цього необхідно увімкнути хоча б один зашифрований протокол DNS",
|
||||
"encryption_plain_dns_error": "Щоб вимкнути звичайний DNS, увімкніть принаймні один зашифрований протокол DNS",
|
||||
"topline_expiring_certificate": "Ваш сертифікат SSL скоро закінчиться. Оновіть <0>Налаштування шифрування</0>.",
|
||||
"topline_expired_certificate": "Термін дії вашого сертифіката SSL закінчився. Оновіть <0>Налаштування шифрування</0>.",
|
||||
"form_error_port_range": "Введіть значення порту в діапазоні 80−65535",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "Додати ідентифікатор",
|
||||
"form_client_name": "Введіть ім'я клієнта",
|
||||
"name": "Ім'я",
|
||||
"client_name": "Клієнт {{id}}",
|
||||
"client_global_settings": "Використати загальні налаштування",
|
||||
"client_deleted": "Клієнта «{{key}}» успішно видалено",
|
||||
"client_added": "Клієнта «{{key}}» успішно додано",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "Các máy chủ thượng nguồn đã được lưu thành công",
|
||||
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
|
||||
"dns_test_not_ok_toast": "Máy chủ \"{{key}}\"': không thể sử dụng, vui lòng kiểm tra lại",
|
||||
"dns_test_parsing_error_toast": "Phần {{section}}: dòng {{line}}: không thể sử dụng được, vui lòng kiểm tra xem bạn đã viết đúng chưa",
|
||||
"dns_test_warning_toast": "Ngược lại \"{{key}}\" không phản hồi các yêu cầu kiểm tra và có thể không hoạt động bình thường",
|
||||
"unblock": "Bỏ chặn",
|
||||
"block": "Chặn",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "Cho phép ứng dụng khách này",
|
||||
"block_for_this_client_only": "Chỉ chặn ứng dụng khách này",
|
||||
"unblock_for_this_client_only": "Chỉ hủy chặn ứng dụng khách này",
|
||||
"add_persistent_client": "Thêm làm ứng dụng khách liên tục",
|
||||
"time_table_header": "Thời gian",
|
||||
"date": "Ngày",
|
||||
"domain_name_table_header": "Tên miền",
|
||||
@@ -462,6 +464,7 @@
|
||||
"form_add_id": "Thêm định danh",
|
||||
"form_client_name": "Nhập tên máy khách",
|
||||
"name": "Tên",
|
||||
"client_name": "Khách hàng {{id}}",
|
||||
"client_global_settings": "Sử dụng cài đặt toàn cầu",
|
||||
"client_deleted": "Máy khách \"{{key}}\" đã xóa thành công",
|
||||
"client_added": "Máy khách \"{{key}}\" đã thêm thành công",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "上游服务器保存成功",
|
||||
"dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
|
||||
"dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
|
||||
"dns_test_parsing_error_toast": "第 {{section}} 节:第 {{line}} 行:无法使用,请检查您输入的是否正确",
|
||||
"dns_test_warning_toast": "上游 “{{key}}” 不响应测试请求,可能无法正常工作",
|
||||
"unblock": "放行",
|
||||
"block": "拦截",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "允许这个客户端",
|
||||
"block_for_this_client_only": "仅对此客户端拦截",
|
||||
"unblock_for_this_client_only": "仅解除对此客户端的拦截",
|
||||
"add_persistent_client": "添加为持久客户端",
|
||||
"time_table_header": "时间",
|
||||
"date": "日期",
|
||||
"domain_name_table_header": "域名",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "主机名",
|
||||
"encryption_reset": "您确定想要重置加密设置?",
|
||||
"encryption_warning": "警告",
|
||||
"encryption_plain_dns_enable": "启用无加密 DNS",
|
||||
"encryption_plain_dns_desc": "默认情况下启用无加密 DNS。用户可以禁用它,强制所有设备使用加密 DNS。为此,必须至少启用一个加密 DNS 协议",
|
||||
"encryption_plain_dns_error": "要禁用无加密 DNS,请至少启用一个加密 DNS 协议",
|
||||
"topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
|
||||
"topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
|
||||
"form_error_port_range": "输入 80 - 65535 范围内的端口值",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "添加标识符",
|
||||
"form_client_name": "输入客户端名称",
|
||||
"name": "名称",
|
||||
"client_name": "客户端 {{id}}",
|
||||
"client_global_settings": "使用全局设置",
|
||||
"client_deleted": "客户端 \"{{key}}\" 删除成功",
|
||||
"client_added": "客户端 \"{{key}}\" 添加成功",
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "上游的伺服器被成功地儲存",
|
||||
"dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
|
||||
"dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
|
||||
"dns_test_parsing_error_toast": "第 {{section}} 節:第 {{line}} 行:無法使用,請檢查您輸入的是否正確",
|
||||
"dns_test_warning_toast": "上游 “{{key}}” 不回應測試請求,可能無法正常工作",
|
||||
"unblock": "解除封鎖",
|
||||
"block": "封鎖",
|
||||
@@ -243,6 +244,7 @@
|
||||
"allow_this_client": "允許此用戶端",
|
||||
"block_for_this_client_only": "僅對此用戶端封鎖",
|
||||
"unblock_for_this_client_only": "僅對此用戶端解除封鎖",
|
||||
"add_persistent_client": "新增為永久性客戶端",
|
||||
"time_table_header": "時間",
|
||||
"date": "日期",
|
||||
"domain_name_table_header": "域名",
|
||||
@@ -310,15 +312,15 @@
|
||||
"edns_use_custom_ip": "為 EDNS 使用自訂的 IP",
|
||||
"edns_use_custom_ip_desc": "允許為 EDNS 使用自訂的 IP",
|
||||
"rate_limit_desc": "每個用戶端被允許的每秒請求之數量。設定它為 0 表示無限制。",
|
||||
"rate_limit_subnet_len_ipv4": "IPv4 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv4": "用於 IPv4 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv4_desc": "用於速率限制的 IPv4 位址的子網路前綴長度。預設值為 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 子網路前綴長度應在 0 至 32 之間",
|
||||
"rate_limit_subnet_len_ipv6": "IPv6 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv6": "用於 IPv6 位址的子網路前綴長度",
|
||||
"rate_limit_subnet_len_ipv6_desc": "用於速率限制的 IPv6 位址的子網路前綴長度。預設值為 56",
|
||||
"rate_limit_subnet_len_ipv6_error": "IPv6 子網路前綴長度應在 0 至 128 之間",
|
||||
"form_enter_rate_limit_subnet_len": "輸入用於速率限制的子網路前綴長度",
|
||||
"rate_limit_whitelist": "速率限制允許清單",
|
||||
"rate_limit_whitelist_desc": "從速率限制中排除的 IP 位址",
|
||||
"rate_limit_whitelist_desc": "從速率限制被排除的 IP 位址",
|
||||
"rate_limit_whitelist_placeholder": "每行輸入一個 IP 位址",
|
||||
"blocking_ipv4_desc": "要被返回給已封鎖的 A 請求之 IP 位址",
|
||||
"blocking_ipv6_desc": "要被返回給已封鎖的 AAAA 請求之 IP 位址",
|
||||
@@ -423,6 +425,9 @@
|
||||
"encryption_hostnames": "主機名稱",
|
||||
"encryption_reset": "您確定您想要重置加密設定嗎?",
|
||||
"encryption_warning": "警告",
|
||||
"encryption_plain_dns_enable": "啟用一般的 DNS",
|
||||
"encryption_plain_dns_desc": "預設情況下啟用一般的 DNS。使用者可以禁用它,強制所有裝置使用一般的 DNS。為此,必須至少啟用一個一般的 DNS 協定。",
|
||||
"encryption_plain_dns_error": "要禁用一般的 DNS,請至少啟用一個一般的 DNS 協定",
|
||||
"topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
|
||||
"topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
|
||||
"form_error_port_range": "輸入在 80-65535 之範圍內的連接埠號碼",
|
||||
@@ -462,6 +467,7 @@
|
||||
"form_add_id": "新增識別碼",
|
||||
"form_client_name": "輸入用戶端名稱",
|
||||
"name": "名稱",
|
||||
"client_name": "客戶端 {{id}}",
|
||||
"client_global_settings": "使用全域的設定",
|
||||
"client_deleted": "用戶端 \"{{key}}\" 被成功地刪除",
|
||||
"client_added": "用戶端 \"{{key}}\" 被成功地加入",
|
||||
|
||||
@@ -403,6 +403,11 @@ export const testUpstream = (
|
||||
const message = upstreamResponse[key];
|
||||
if (message.startsWith('WARNING:')) {
|
||||
dispatch(addErrorToast({ error: i18next.t('dns_test_warning_toast', { key }) }));
|
||||
} else if (message.endsWith(': parsing error')) {
|
||||
const info = message.substring(0, message.indexOf(':'));
|
||||
const [sectionKey, line] = info.split(' ');
|
||||
const section = i18next.t(sectionKey);
|
||||
dispatch(addErrorToast({ error: i18next.t('dns_test_parsing_error_toast', { section, line }) }));
|
||||
} else if (message !== 'OK') {
|
||||
dispatch(addErrorToast({ error: i18next.t('dns_test_not_ok_toast', { key }) }));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const Examples = () => (
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists"
|
||||
href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax&from=ui&app=home"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
|
||||
@@ -13,6 +13,8 @@ ReactModal.setAppElement('#root');
|
||||
const MODAL_TYPE_TO_TITLE_TYPE_MAP = {
|
||||
[MODAL_TYPE.EDIT_FILTERS]: 'edit',
|
||||
[MODAL_TYPE.ADD_FILTERS]: 'new',
|
||||
[MODAL_TYPE.EDIT_CLIENT]: 'edit',
|
||||
[MODAL_TYPE.ADD_CLIENT]: 'new',
|
||||
[MODAL_TYPE.SELECT_MODAL_TYPE]: 'new',
|
||||
[MODAL_TYPE.CHOOSE_FILTERING_LIST]: 'choose',
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import propTypes from 'prop-types';
|
||||
|
||||
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
|
||||
@@ -25,12 +25,14 @@ const ClientCell = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
||||
const [isOptionsOpened, setOptionsOpened] = useState(false);
|
||||
|
||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||
const clients = useSelector((state) => state.dashboard.clients);
|
||||
const source = autoClient?.source;
|
||||
const whoisAvailable = client_info && Object.keys(client_info.whois).length > 0;
|
||||
const clientName = client_info?.name || client_id;
|
||||
@@ -55,6 +57,8 @@ const ClientCell = ({
|
||||
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const clientIds = clients.map((c) => c.ids).flat();
|
||||
|
||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||
'mt-2': isDetailed && !client_info?.name && !whoisAvailable,
|
||||
'white-space--nowrap': isDetailed,
|
||||
@@ -66,7 +70,6 @@ const ClientCell = ({
|
||||
|
||||
const renderBlockingButton = (isFiltered, domain) => {
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
const clients = useSelector((state) => state.dashboard.clients);
|
||||
|
||||
const {
|
||||
confirmMessage,
|
||||
@@ -118,6 +121,15 @@ const ClientCell = ({
|
||||
},
|
||||
];
|
||||
|
||||
if (!clientIds.includes(client)) {
|
||||
BUTTON_OPTIONS.push({
|
||||
name: 'add_persistent_client',
|
||||
onClick: () => {
|
||||
history.push(`/#clients?clientId=${client}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getOptions = (options) => {
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services';
|
||||
@@ -39,8 +40,12 @@ const ClientsTable = ({
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const services = useSelector((store) => store?.services);
|
||||
const globalSettings = useSelector((store) => store?.settings.settingsList) || {};
|
||||
const params = new URLSearchParams(location.search);
|
||||
const clientId = params.get('clientId');
|
||||
|
||||
const { safesearch } = globalSettings;
|
||||
|
||||
@@ -48,6 +53,12 @@ const ClientsTable = ({
|
||||
dispatch(getAllBlockedServices());
|
||||
dispatch(getBlockedServices());
|
||||
dispatch(initSettings());
|
||||
|
||||
if (clientId) {
|
||||
toggleClientModal({
|
||||
type: MODAL_TYPE.ADD_CLIENT,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleFormAdd = (values) => {
|
||||
@@ -85,11 +96,15 @@ const ClientsTable = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
|
||||
if (modalType === MODAL_TYPE.EDIT_CLIENT) {
|
||||
handleFormUpdate(config, modalClientName);
|
||||
} else {
|
||||
handleFormAdd(config);
|
||||
}
|
||||
|
||||
if (clientId) {
|
||||
history.push('/#clients');
|
||||
}
|
||||
};
|
||||
|
||||
const getOptionsWithLabels = (options) => (
|
||||
@@ -133,6 +148,14 @@ const ClientsTable = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
toggleClientModal();
|
||||
|
||||
if (clientId) {
|
||||
history.push('/#clients');
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: t('table_client'),
|
||||
@@ -298,7 +321,7 @@ const ClientsTable = ({
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
onClick={() => toggleClientModal({
|
||||
type: MODAL_TYPE.EDIT_FILTERS,
|
||||
type: MODAL_TYPE.EDIT_CLIENT,
|
||||
name: clientName,
|
||||
})
|
||||
}
|
||||
@@ -371,12 +394,13 @@ const ClientsTable = ({
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
modalType={modalType}
|
||||
toggleClientModal={toggleClientModal}
|
||||
handleClose={handleClose}
|
||||
currentClientData={currentClientData}
|
||||
handleSubmit={handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
processingUpdating={processingUpdating}
|
||||
tagsOptions={tagsOptions}
|
||||
clientId={clientId}
|
||||
/>
|
||||
</>
|
||||
</Card>
|
||||
|
||||
@@ -147,7 +147,7 @@ let Form = (props) => {
|
||||
useGlobalSettings,
|
||||
useGlobalServices,
|
||||
blockedServicesSchedule,
|
||||
toggleClientModal,
|
||||
handleClose,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
invalid,
|
||||
@@ -371,7 +371,7 @@ let Form = (props) => {
|
||||
</div>
|
||||
<div className="form__desc mt-0 mb-2">
|
||||
<Trans components={[
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax_ctag&from=ui&app=home"
|
||||
key="0">link</a>,
|
||||
]}>
|
||||
tags_desc
|
||||
@@ -427,7 +427,7 @@ let Form = (props) => {
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
reset();
|
||||
toggleClientModal();
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
@@ -456,7 +456,7 @@ Form.propTypes = {
|
||||
reset: PropTypes.func.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
useGlobalSettings: PropTypes.bool,
|
||||
useGlobalServices: PropTypes.bool,
|
||||
blockedServicesSchedule: PropTypes.object,
|
||||
|
||||
@@ -6,7 +6,9 @@ import ReactModal from 'react-modal';
|
||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
|
||||
const getInitialData = (initial) => {
|
||||
const getInitialData = ({
|
||||
initial, modalType, clientId, clientName,
|
||||
}) => {
|
||||
if (initial && initial.blocked_services) {
|
||||
const { blocked_services } = initial;
|
||||
const blocked = {};
|
||||
@@ -21,46 +23,60 @@ const getInitialData = (initial) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (modalType !== MODAL_TYPE.EDIT_CLIENT && clientId) {
|
||||
return {
|
||||
...initial,
|
||||
name: clientName,
|
||||
ids: [clientId],
|
||||
};
|
||||
}
|
||||
|
||||
return initial;
|
||||
};
|
||||
|
||||
const Modal = (props) => {
|
||||
const {
|
||||
isModalOpen,
|
||||
const Modal = ({
|
||||
isModalOpen,
|
||||
modalType,
|
||||
currentClientData,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
tagsOptions,
|
||||
clientId,
|
||||
t,
|
||||
}) => {
|
||||
const initialData = getInitialData({
|
||||
initial: currentClientData,
|
||||
modalType,
|
||||
currentClientData,
|
||||
handleSubmit,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
tagsOptions,
|
||||
} = props;
|
||||
const initialData = getInitialData(currentClientData);
|
||||
clientId,
|
||||
clientName: t('client_name', { id: clientId }),
|
||||
});
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => toggleClientModal()}
|
||||
onRequestClose={handleClose}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{modalType === MODAL_TYPE.EDIT_FILTERS ? (
|
||||
{modalType === MODAL_TYPE.EDIT_CLIENT ? (
|
||||
<Trans>client_edit</Trans>
|
||||
) : (
|
||||
<Trans>client_new</Trans>
|
||||
)}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleClientModal()}>
|
||||
<button type="button" className="close" onClick={handleClose}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
initialValues={{ ...initialData }}
|
||||
onSubmit={handleSubmit}
|
||||
toggleClientModal={toggleClientModal}
|
||||
handleClose={handleClose}
|
||||
processingAdding={processingAdding}
|
||||
processingUpdating={processingUpdating}
|
||||
tagsOptions={tagsOptions}
|
||||
@@ -75,10 +91,12 @@ Modal.propTypes = {
|
||||
modalType: PropTypes.string.isRequired,
|
||||
currentClientData: PropTypes.object.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
handleClose: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
tagsOptions: PropTypes.array.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
clientId: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withTranslation()(Modal);
|
||||
|
||||
@@ -182,6 +182,8 @@ export const MODAL_TYPE = {
|
||||
EDIT_REWRITE: 'EDIT_REWRITE',
|
||||
EDIT_LEASE: 'EDIT_LEASE',
|
||||
ADD_LEASE: 'ADD_LEASE',
|
||||
ADD_CLIENT: 'ADD_CLIENT',
|
||||
EDIT_CLIENT: 'EDIT_CLIENT',
|
||||
};
|
||||
|
||||
export const CLIENT_ID = {
|
||||
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_47.txt"
|
||||
},
|
||||
"hagezi_multinormal": {
|
||||
"name": "HaGeZi Multi NORMAL",
|
||||
"name": "HaGeZi's Normal Blocklist",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_34.txt"
|
||||
|
||||
32
go.mod
32
go.mod
@@ -1,11 +1,11 @@
|
||||
module github.com/AdguardTeam/AdGuardHome
|
||||
|
||||
go 1.20
|
||||
go 1.21.7
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.63.1
|
||||
github.com/AdguardTeam/golibs v0.19.0
|
||||
github.com/AdguardTeam/urlfilter v0.17.3
|
||||
github.com/AdguardTeam/dnsproxy v0.65.0
|
||||
github.com/AdguardTeam/golibs v0.20.1
|
||||
github.com/AdguardTeam/urlfilter v0.18.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||
github.com/bluele/gcache v0.0.2
|
||||
@@ -17,8 +17,8 @@ require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/renameio/v2 v2.0.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/kardianos/service v1.2.2
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
|
||||
@@ -28,14 +28,14 @@ require (
|
||||
// own code for that. Perhaps, use gopacket.
|
||||
github.com/mdlayher/raw v0.1.0
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/quic-go/quic-go v0.40.0
|
||||
github.com/quic-go/quic-go v0.41.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/ti-mo/netfilter v0.5.1
|
||||
go.etcd.io/bbolt v1.3.8
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/sys v0.17.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.1
|
||||
@@ -48,8 +48,7 @@ require (
|
||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
|
||||
// TODO(a.garipov): Upgrade to v0.5.0 once we switch to Go 1.21+.
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
@@ -57,12 +56,11 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/mod v0.15.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
gonum.org/v1/gonum v0.14.0 // indirect
|
||||
)
|
||||
|
||||
78
go.sum
78
go.sum
@@ -1,9 +1,9 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.63.1 h1:CilxSuLYcuYpbPCGB7w41UUqWRMu3dvj4c9TvkIrpBg=
|
||||
github.com/AdguardTeam/dnsproxy v0.63.1/go.mod h1:dRRAFOjrq4QYM92jGs4lt4BoY0Dm3EY3HkaleoM2Feo=
|
||||
github.com/AdguardTeam/golibs v0.19.0 h1:y/x+Xn3pDg1ZfQ+QEZapPJqaeVYUIMp/EODMtVhn7PM=
|
||||
github.com/AdguardTeam/golibs v0.19.0/go.mod h1:3WunclLLfrVAq7fYQRhd6f168FHOEMssnipVXCxDL/w=
|
||||
github.com/AdguardTeam/urlfilter v0.17.3 h1:fg/ObbnO0Cv6aw0tW6N/ETDMhhNvmcUUOZ7HlmKC3rw=
|
||||
github.com/AdguardTeam/urlfilter v0.17.3/go.mod h1:Jru7jFfeH2CoDf150uDs+rRYcZBzHHBz05r9REyDKyE=
|
||||
github.com/AdguardTeam/dnsproxy v0.65.0 h1:mqJjVSkqoqPwThY3tTvnLHQ/AYBYrfWmK2ER91fu4FE=
|
||||
github.com/AdguardTeam/dnsproxy v0.65.0/go.mod h1:AGYMLPk2zX+I3NIUYS12KUI296mkCyfoMF/luy2uqdk=
|
||||
github.com/AdguardTeam/golibs v0.20.1 h1:ol8qLjWGZhU9paMMwN+OLWVTUigGsXa29iVTyd62VKY=
|
||||
github.com/AdguardTeam/golibs v0.20.1/go.mod h1:bgcMgRviCKyU6mkrX+RtT/OsKPFzyppelfRsksMG3KU=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
|
||||
github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
@@ -30,12 +30,15 @@ github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu9
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
|
||||
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
@@ -43,25 +46,27 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1 h1:L3pm9Kf2G6gJVYawz2SrI5QnV1wzHYbqmKnSHHXJAb8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
||||
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
|
||||
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
|
||||
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHIVchZ9lJoWoEGUg8Q3M4U8aNNWA3CVSUTkW4og=
|
||||
github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
@@ -78,12 +83,13 @@ github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrG
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
|
||||
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
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/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -92,16 +98,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
@@ -110,32 +118,35 @@ github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x
|
||||
github.com/ti-mo/netfilter v0.5.1 h1:cqamEd1c1zmpfpqvInLOro0Znq/RAfw2QL5wL2rAR/8=
|
||||
github.com/ti-mo/netfilter v0.5.1/go.mod h1:h9UPQ3ZrTZGBitay+LETMxZvNgWGK/efTUcqES2YiLw=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd h1:BQJh5fdHsPa/YuMVrbcSxQKuowGCHYh0GD7hvLaHBK0=
|
||||
github.com/u-root/uio v0.0.0-20240207234124-abbebccef0fd/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
@@ -149,10 +160,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20220622161953-175b2fd9d664/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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -160,15 +170,17 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
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=
|
||||
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
|
||||
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
|
||||
@@ -5,9 +5,9 @@ package aghalg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Coalesce returns the first non-zero value. It is named after function
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package aghalg_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// elements is a helper function that returns n elements of the buffer.
|
||||
|
||||
@@ -154,8 +154,8 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
|
||||
}
|
||||
|
||||
// handleEvents concurrently handles the file system events. It closes the
|
||||
// update channel of HostsContainer when finishes. It's used to be called
|
||||
// within a separate goroutine.
|
||||
// update channel of HostsContainer when finishes. It is intended to be used as
|
||||
// a goroutine.
|
||||
func (hc *HostsContainer) handleEvents() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPrefix))
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: onEvents,
|
||||
OnAdd: onAdd,
|
||||
OnClose: func() (err error) { return nil },
|
||||
@@ -93,6 +94,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
t.Run("nil_fs", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
_, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
// Those shouldn't panic.
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(name string) (err error) { return nil },
|
||||
@@ -111,6 +113,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
const errOnAdd errors.Error = "error"
|
||||
|
||||
errWatcher := &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
|
||||
OnAdd: func(name string) (err error) { return errOnAdd },
|
||||
OnClose: func() (err error) { return nil },
|
||||
@@ -155,6 +158,7 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
t.Cleanup(func() { close(eventsCh) })
|
||||
|
||||
w := &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan event) { return eventsCh },
|
||||
OnAdd: func(name string) (err error) {
|
||||
assert.Equal(t, "dir", name)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IgnoreEngine contains the list of rules for ignoring hostnames and matches
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
)
|
||||
|
||||
// DialContextFunc is the semantic alias for dialing functions, such as
|
||||
@@ -32,7 +33,7 @@ var (
|
||||
netInterfaceAddrs = net.InterfaceAddrs
|
||||
|
||||
// rootDirFS is the filesystem pointing to the root directory.
|
||||
rootDirFS = aghos.RootDirFS()
|
||||
rootDirFS = osutil.RootDirFS()
|
||||
)
|
||||
|
||||
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
@@ -18,31 +20,38 @@ type event = struct{}
|
||||
// FSWatcher tracks all the fyle system events and notifies about those.
|
||||
//
|
||||
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
|
||||
//
|
||||
// TODO(e.burkov): Add tests.
|
||||
type FSWatcher interface {
|
||||
// Start starts watching the added files.
|
||||
Start() (err error)
|
||||
|
||||
// Close stops watching the files and closes an update channel.
|
||||
io.Closer
|
||||
|
||||
// Events should return a read-only channel which notifies about events.
|
||||
// Events returns the channel to notify about the file system events.
|
||||
Events() (e <-chan event)
|
||||
|
||||
// Add should check if the file named name is accessible and starts tracking
|
||||
// it.
|
||||
// Add starts tracking the file. It returns an error if the file can't be
|
||||
// tracked. It must not be called after Start.
|
||||
Add(name string) (err error)
|
||||
}
|
||||
|
||||
// osWatcher tracks the file system provided by the OS.
|
||||
type osWatcher struct {
|
||||
// w is the actual notifier that is handled by osWatcher.
|
||||
w *fsnotify.Watcher
|
||||
// watcher is the actual notifier that is handled by osWatcher.
|
||||
watcher *fsnotify.Watcher
|
||||
|
||||
// events is the channel to notify.
|
||||
events chan event
|
||||
|
||||
// files is the set of tracked files.
|
||||
files *stringutil.Set
|
||||
}
|
||||
|
||||
const (
|
||||
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
|
||||
// methods.
|
||||
osWatcherPref = "os watcher"
|
||||
)
|
||||
// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's
|
||||
// methods.
|
||||
const osWatcherPref = "os watcher"
|
||||
|
||||
// NewOSWritesWatcher creates FSWatcher that tracks the real file system of the
|
||||
// OS and notifies only about writing events.
|
||||
@@ -55,25 +64,27 @@ func NewOSWritesWatcher() (w FSWatcher, err error) {
|
||||
return nil, fmt.Errorf("creating watcher: %w", err)
|
||||
}
|
||||
|
||||
fsw := &osWatcher{
|
||||
w: watcher,
|
||||
events: make(chan event, 1),
|
||||
}
|
||||
|
||||
go fsw.handleErrors()
|
||||
go fsw.handleEvents()
|
||||
|
||||
return fsw, nil
|
||||
return &osWatcher{
|
||||
watcher: watcher,
|
||||
events: make(chan event, 1),
|
||||
files: stringutil.NewSet(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleErrors handles accompanying errors. It used to be called in a separate
|
||||
// goroutine.
|
||||
func (w *osWatcher) handleErrors() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
|
||||
// type check
|
||||
var _ FSWatcher = (*osWatcher)(nil)
|
||||
|
||||
for err := range w.w.Errors {
|
||||
log.Error("%s: %s", osWatcherPref, err)
|
||||
}
|
||||
// Start implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Start() (err error) {
|
||||
go w.handleErrors()
|
||||
go w.handleEvents()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Close() (err error) {
|
||||
return w.watcher.Close()
|
||||
}
|
||||
|
||||
// Events implements the FSWatcher interface for *osWatcher.
|
||||
@@ -81,34 +92,42 @@ func (w *osWatcher) Events() (e <-chan event) {
|
||||
return w.events
|
||||
}
|
||||
|
||||
// Add implements the FSWatcher interface for *osWatcher.
|
||||
// Add implements the [FSWatcher] interface for *osWatcher.
|
||||
//
|
||||
// TODO(e.burkov): Make it accept non-existing files to detect it's creating.
|
||||
func (w *osWatcher) Add(name string) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
|
||||
|
||||
if _, err = fs.Stat(RootDirFS(), name); err != nil {
|
||||
fi, err := fs.Stat(osutil.RootDirFS(), name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking file %q: %w", name, err)
|
||||
}
|
||||
|
||||
return w.w.Add(filepath.Join("/", name))
|
||||
}
|
||||
name = filepath.Join("/", name)
|
||||
w.files.Add(name)
|
||||
|
||||
// Close implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Close() (err error) {
|
||||
return w.w.Close()
|
||||
// Watch the directory and filter the events by the file name, since the
|
||||
// common recomendation to the fsnotify package is to watch the directory
|
||||
// instead of the file itself.
|
||||
//
|
||||
// See https://pkg.go.dev/github.com/fsnotify/fsnotify@v1.7.0#readme-watching-a-file-doesn-t-work-well.
|
||||
if !fi.IsDir() {
|
||||
name = filepath.Dir(name)
|
||||
}
|
||||
|
||||
return w.watcher.Add(name)
|
||||
}
|
||||
|
||||
// handleEvents notifies about the received file system's event if needed. It
|
||||
// used to be called in a separate goroutine.
|
||||
// is intended to be used as a goroutine.
|
||||
func (w *osWatcher) handleEvents() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", osWatcherPref))
|
||||
|
||||
defer close(w.events)
|
||||
|
||||
ch := w.w.Events
|
||||
ch := w.watcher.Events
|
||||
for e := range ch {
|
||||
if e.Op&fsnotify.Write == 0 {
|
||||
if e.Op&fsnotify.Write == 0 || !w.files.Has(e.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -131,3 +150,13 @@ func (w *osWatcher) handleEvents() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleErrors handles accompanying errors. It used to be called in a separate
|
||||
// goroutine.
|
||||
func (w *osWatcher) handleErrors() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
|
||||
|
||||
for err := range w.watcher.Errors {
|
||||
log.Error("%s: %s", osWatcherPref, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,16 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/mathutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// UnsupportedError is returned by functions and methods when a particular
|
||||
@@ -63,7 +61,7 @@ func RunCommand(command string, arguments ...string) (code int, output []byte, e
|
||||
cmd := exec.Command(command, arguments...)
|
||||
out, err := cmd.Output()
|
||||
|
||||
out = out[:mathutil.Min(len(out), MaxCmdOutputSize)]
|
||||
out = out[:min(len(out), MaxCmdOutputSize)]
|
||||
|
||||
if err != nil {
|
||||
if eerr := new(exec.ExitError); errors.As(err, &eerr) {
|
||||
@@ -142,7 +140,7 @@ func parsePSOutput(r io.Reader, cmdName string, ignore []int) (largest, instNum
|
||||
}
|
||||
|
||||
instNum++
|
||||
largest = mathutil.Max(largest, cur)
|
||||
largest = max(largest, cur)
|
||||
}
|
||||
if err = s.Err(); err != nil {
|
||||
return 0, 0, fmt.Errorf("scanning stdout: %w", err)
|
||||
@@ -156,13 +154,6 @@ func IsOpenWrt() (ok bool) {
|
||||
return isOpenWrt()
|
||||
}
|
||||
|
||||
// RootDirFS returns the [fs.FS] rooted at the operating system's root. On
|
||||
// Windows it returns the fs.FS rooted at the volume of the system directory
|
||||
// (usually, C:).
|
||||
func RootDirFS() (fsys fs.FS) {
|
||||
return rootDirFS()
|
||||
}
|
||||
|
||||
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
|
||||
func NotifyReconfigureSignal(c chan<- os.Signal) {
|
||||
notifyReconfigureSignal(c)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func isOpenWrt() (ok bool) {
|
||||
}
|
||||
|
||||
return nil, !stringutil.ContainsFold(string(data), osNameData), nil
|
||||
}).Walk(RootDirFS(), etcReleasePattern)
|
||||
}).Walk(osutil.RootDirFS(), etcReleasePattern)
|
||||
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func rootDirFS() (fsys fs.FS) {
|
||||
return os.DirFS("/")
|
||||
}
|
||||
|
||||
func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGHUP)
|
||||
}
|
||||
|
||||
@@ -3,29 +3,13 @@
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func rootDirFS() (fsys fs.FS) {
|
||||
// TODO(a.garipov): Use a better way if golang/go#44279 is ever resolved.
|
||||
sysDir, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
log.Error("aghos: getting root filesystem: %s; using C:", err)
|
||||
|
||||
// Assume that C: is the safe default.
|
||||
return os.DirFS("C:")
|
||||
}
|
||||
|
||||
return os.DirFS(filepath.VolumeName(sysDir))
|
||||
}
|
||||
|
||||
func setRlimit(val uint64) (err error) {
|
||||
return Unsupported("setrlimit")
|
||||
}
|
||||
|
||||
@@ -26,14 +26,25 @@ import (
|
||||
|
||||
// FSWatcher is a fake [aghos.FSWatcher] implementation for tests.
|
||||
type FSWatcher struct {
|
||||
OnStart func() (err error)
|
||||
OnClose func() (err error)
|
||||
OnEvents func() (e <-chan struct{})
|
||||
OnAdd func(name string) (err error)
|
||||
OnClose func() (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ aghos.FSWatcher = (*FSWatcher)(nil)
|
||||
|
||||
// Start implements the [aghos.FSWatcher] interface for *FSWatcher.
|
||||
func (w *FSWatcher) Start() (err error) {
|
||||
return w.OnStart()
|
||||
}
|
||||
|
||||
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
|
||||
func (w *FSWatcher) Close() (err error) {
|
||||
return w.OnClose()
|
||||
}
|
||||
|
||||
// Events implements the [aghos.FSWatcher] interface for *FSWatcher.
|
||||
func (w *FSWatcher) Events() (e <-chan struct{}) {
|
||||
return w.OnEvents()
|
||||
@@ -44,11 +55,6 @@ func (w *FSWatcher) Add(name string) (err error) {
|
||||
return w.OnAdd(name)
|
||||
}
|
||||
|
||||
// Close implements the [aghos.FSWatcher] interface for *FSWatcher.
|
||||
func (w *FSWatcher) Close() (err error) {
|
||||
return w.OnClose()
|
||||
}
|
||||
|
||||
// Package agh
|
||||
|
||||
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
)
|
||||
|
||||
// Variables and functions to substitute in tests.
|
||||
@@ -22,7 +23,7 @@ var (
|
||||
aghosRunCommand = aghos.RunCommand
|
||||
|
||||
// rootDirFS is the filesystem pointing to the root directory.
|
||||
rootDirFS = aghos.RootDirFS()
|
||||
rootDirFS = osutil.RootDirFS()
|
||||
)
|
||||
|
||||
// Interface stores and refreshes the network neighborhood reported by ARP
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/renameio/v2/maybe"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
@@ -19,7 +20,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type v4ServerConfJSON struct {
|
||||
@@ -592,7 +592,7 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
|
||||
}
|
||||
|
||||
// parseLease parses a lease from r. If there is no error returns DHCPServer
|
||||
// and *Lease. r must be non-nil.
|
||||
// and *Lease. r must be non-nil.
|
||||
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) {
|
||||
l := &leaseStatic{}
|
||||
err = json.NewDecoder(r).Decode(l)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// v4Server is a DHCPv4 server.
|
||||
|
||||
@@ -2,11 +2,11 @@ package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Config is the configuration for the DHCP service.
|
||||
@@ -19,6 +19,8 @@ type Config struct {
|
||||
// clients' hostnames.
|
||||
LocalDomainName string
|
||||
|
||||
// TODO(e.burkov): Add DB path.
|
||||
|
||||
// ICMPTimeout is the timeout for checking another DHCP server's presence.
|
||||
ICMPTimeout time.Duration
|
||||
|
||||
@@ -68,12 +70,6 @@ func (conf *Config) Validate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
// validate returns an error in ic, if any.
|
||||
func (ic *InterfaceConfig) validate() (err error) {
|
||||
if ic == nil {
|
||||
|
||||
@@ -7,48 +7,14 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
// Interface is a DHCP service.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Separate HostByIP, MACByIP, IPByHost into a separate
|
||||
// interface. This is also valid for Enabled method.
|
||||
type Interface interface {
|
||||
agh.ServiceWithConfig[*Config]
|
||||
|
||||
@@ -71,7 +37,8 @@ type Interface interface {
|
||||
// hostname, either set or generated.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// Leases returns all the active DHCP leases.
|
||||
// Leases returns all the active DHCP leases. The returned slice should be
|
||||
// a clone.
|
||||
//
|
||||
// TODO(e.burkov): Consider implementing iterating methods with appropriate
|
||||
// signatures instead of cloning the whole list.
|
||||
@@ -91,6 +58,8 @@ type Interface interface {
|
||||
RemoveLease(l *Lease) (err error)
|
||||
|
||||
// Reset removes all the DHCP leases.
|
||||
//
|
||||
// TODO(e.burkov): If it's really needed?
|
||||
Reset() (err error)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package dhcpsvc
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// errNilConfig is returned when a nil config met.
|
||||
@@ -9,3 +13,9 @@ const (
|
||||
// errNoInterfaces is returned when no interfaces found in configuration.
|
||||
errNoInterfaces errors.Error = "no interfaces specified"
|
||||
)
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
40
internal/dhcpsvc/interface.go
Normal file
40
internal/dhcpsvc/interface.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// netInterface is a common part of any network interface within the DHCP
|
||||
// server.
|
||||
//
|
||||
// TODO(e.burkov): Add other methods as [DHCPServer] evolves.
|
||||
type netInterface struct {
|
||||
// name is the name of the network interface.
|
||||
name string
|
||||
|
||||
// leases is a set of leases sorted by hardware address.
|
||||
leases []*Lease
|
||||
|
||||
// leaseTTL is the default Time-To-Live value for leases.
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// reset clears all the slices in iface for reuse.
|
||||
func (iface *netInterface) reset() {
|
||||
iface.leases = iface.leases[:0]
|
||||
}
|
||||
|
||||
// insertLease inserts the given lease into iface. It returns an error if the
|
||||
// lease can't be inserted.
|
||||
func (iface *netInterface) insertLease(l *Lease) (err error) {
|
||||
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
|
||||
if found {
|
||||
return fmt.Errorf("lease for mac %s already exists", l.HWAddr)
|
||||
}
|
||||
|
||||
iface.leases = slices.Insert(iface.leases, i, l)
|
||||
|
||||
return nil
|
||||
}
|
||||
52
internal/dhcpsvc/lease.go
Normal file
52
internal/dhcpsvc/lease.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
//
|
||||
// TODO(e.burkov): Add validation method.
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// compareLeaseMAC compares two [Lease]s by hardware address.
|
||||
func compareLeaseMAC(a, b *Lease) (res int) {
|
||||
return bytes.Compare(a.HWAddr, b.HWAddr)
|
||||
}
|
||||
@@ -2,11 +2,15 @@ package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||
@@ -15,18 +19,27 @@ type DHCPServer struct {
|
||||
// information about its clients.
|
||||
enabled *atomic.Bool
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP
|
||||
// clients' hostnames.
|
||||
// localTLD is the top-level domain name to use for resolving DHCP clients'
|
||||
// hostnames.
|
||||
localTLD string
|
||||
|
||||
// leasesMu protects the ipIndex and nameIndex fields against concurrent
|
||||
// access, as well as leaseHandlers within the interfaces.
|
||||
leasesMu *sync.RWMutex
|
||||
|
||||
// leaseByIP is a lookup shortcut for leases by their IP addresses.
|
||||
leaseByIP map[netip.Addr]*Lease
|
||||
|
||||
// leaseByName is a lookup shortcut for leases by their hostnames.
|
||||
//
|
||||
// TODO(e.burkov): Use a slice of leases with the same hostname?
|
||||
leaseByName map[string]*Lease
|
||||
|
||||
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
|
||||
interfaces4 []*iface4
|
||||
interfaces4 netInterfacesV4
|
||||
|
||||
// interfaces6 is the set of IPv6 interfaces sorted by interface name.
|
||||
interfaces6 []*iface6
|
||||
|
||||
// leases is the set of active DHCP leases.
|
||||
leases []*Lease
|
||||
interfaces6 netInterfacesV6
|
||||
|
||||
// icmpTimeout is the timeout for checking another DHCP server's presence.
|
||||
icmpTimeout time.Duration
|
||||
@@ -42,26 +55,27 @@ func New(conf *Config) (srv *DHCPServer, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ifaces4 := make([]*iface4, len(conf.Interfaces))
|
||||
ifaces6 := make([]*iface6, len(conf.Interfaces))
|
||||
// TODO(e.burkov): Add validations scoped to the network interfaces set.
|
||||
ifaces4 := make(netInterfacesV4, 0, len(conf.Interfaces))
|
||||
ifaces6 := make(netInterfacesV6, 0, len(conf.Interfaces))
|
||||
|
||||
ifaceNames := maps.Keys(conf.Interfaces)
|
||||
slices.Sort(ifaceNames)
|
||||
|
||||
var i4 *iface4
|
||||
var i6 *iface6
|
||||
var i4 *netInterfaceV4
|
||||
var i6 *netInterfaceV6
|
||||
|
||||
for _, ifaceName := range ifaceNames {
|
||||
iface := conf.Interfaces[ifaceName]
|
||||
|
||||
i4, err = newIface4(ifaceName, iface.IPv4)
|
||||
i4, err = newNetInterfaceV4(ifaceName, iface.IPv4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("interface %q: ipv4: %w", ifaceName, err)
|
||||
} else if i4 != nil {
|
||||
ifaces4 = append(ifaces4, i4)
|
||||
}
|
||||
|
||||
i6 = newIface6(ifaceName, iface.IPv6)
|
||||
i6 = newNetInterfaceV6(ifaceName, iface.IPv6)
|
||||
if i6 != nil {
|
||||
ifaces6 = append(ifaces6, i6)
|
||||
}
|
||||
@@ -70,13 +84,20 @@ func New(conf *Config) (srv *DHCPServer, err error) {
|
||||
enabled := &atomic.Bool{}
|
||||
enabled.Store(conf.Enabled)
|
||||
|
||||
return &DHCPServer{
|
||||
srv = &DHCPServer{
|
||||
enabled: enabled,
|
||||
localTLD: conf.LocalDomainName,
|
||||
leasesMu: &sync.RWMutex{},
|
||||
leaseByIP: map[netip.Addr]*Lease{},
|
||||
leaseByName: map[string]*Lease{},
|
||||
interfaces4: ifaces4,
|
||||
interfaces6: ifaces6,
|
||||
localTLD: conf.LocalDomainName,
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Load leases.
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -91,10 +112,100 @@ func (srv *DHCPServer) Enabled() (ok bool) {
|
||||
|
||||
// Leases implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Leases() (leases []*Lease) {
|
||||
leases = make([]*Lease, 0, len(srv.leases))
|
||||
for _, lease := range srv.leases {
|
||||
leases = append(leases, lease.Clone())
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
for _, lease := range iface.leases {
|
||||
leases = append(leases, lease.Clone())
|
||||
}
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// HostByIP implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) HostByIP(ip netip.Addr) (host string) {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByIP[ip]; ok {
|
||||
return l.Hostname
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// MACByIP implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) MACByIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByIP[ip]; ok {
|
||||
return l.HWAddr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) IPByHost(host string) (ip netip.Addr) {
|
||||
lowered := strings.ToLower(host)
|
||||
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByName[lowered]; ok {
|
||||
return l.IP
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// Reset implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Reset() (err error) {
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
iface.reset()
|
||||
}
|
||||
for _, iface := range srv.interfaces6 {
|
||||
iface.reset()
|
||||
}
|
||||
|
||||
clear(srv.leaseByIP)
|
||||
clear(srv.leaseByName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLease implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
|
||||
var ok bool
|
||||
var iface *netInterface
|
||||
|
||||
addr := l.IP
|
||||
|
||||
if addr.Is4() {
|
||||
iface, ok = srv.interfaces4.find(addr)
|
||||
} else {
|
||||
iface, ok = srv.interfaces6.find(addr)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("no interface for IP address %s", addr)
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
err = iface.insertLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leaseByIP[l.IP] = l
|
||||
srv.leaseByName[strings.ToLower(l.Hostname)] = l
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testLocalTLD is a common local TLD for tests.
|
||||
@@ -113,3 +116,113 @@ func TestNew(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_index(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
GatewayIP: netip.MustParseAddr("192.168.0.1"),
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
RangeStart: netip.MustParseAddr("192.168.0.2"),
|
||||
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
},
|
||||
IPv6: &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
RangeStart: netip.MustParseAddr("2001:db8::1"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
},
|
||||
},
|
||||
"eth1": {
|
||||
IPv4: &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
GatewayIP: netip.MustParseAddr("172.16.0.1"),
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
RangeStart: netip.MustParseAddr("172.16.0.2"),
|
||||
RangeEnd: netip.MustParseAddr("172.16.0.255"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
},
|
||||
IPv6: &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
RangeStart: netip.MustParseAddr("2001:db9::1"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
host4 = "host4"
|
||||
host5 = "host5"
|
||||
)
|
||||
|
||||
ip1 := netip.MustParseAddr("192.168.0.2")
|
||||
ip2 := netip.MustParseAddr("192.168.0.3")
|
||||
ip3 := netip.MustParseAddr("172.16.0.3")
|
||||
ip4 := netip.MustParseAddr("172.16.0.4")
|
||||
|
||||
mac1 := net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
|
||||
mac2 := net.HardwareAddr{0x06, 0x05, 0x04, 0x03, 0x02, 0x01}
|
||||
mac3 := net.HardwareAddr{0x05, 0x04, 0x03, 0x02, 0x01, 0x00}
|
||||
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host2,
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host4,
|
||||
IP: ip4,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}}
|
||||
for _, l := range leases {
|
||||
require.NoError(t, srv.AddLease(l))
|
||||
}
|
||||
|
||||
t.Run("ip_idx", func(t *testing.T) {
|
||||
assert.Equal(t, ip1, srv.IPByHost(host1))
|
||||
assert.Equal(t, ip2, srv.IPByHost(host2))
|
||||
assert.Equal(t, ip3, srv.IPByHost(host3))
|
||||
assert.Equal(t, ip4, srv.IPByHost(host4))
|
||||
assert.Equal(t, netip.Addr{}, srv.IPByHost(host5))
|
||||
})
|
||||
|
||||
t.Run("name_idx", func(t *testing.T) {
|
||||
assert.Equal(t, host1, srv.HostByIP(ip1))
|
||||
assert.Equal(t, host2, srv.HostByIP(ip2))
|
||||
assert.Equal(t, host3, srv.HostByIP(ip3))
|
||||
assert.Equal(t, host4, srv.HostByIP(ip4))
|
||||
assert.Equal(t, "", srv.HostByIP(netip.Addr{}))
|
||||
})
|
||||
|
||||
t.Run("mac_idx", func(t *testing.T) {
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip1))
|
||||
assert.Equal(t, mac2, srv.MACByIP(ip2))
|
||||
assert.Equal(t, mac3, srv.MACByIP(ip3))
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip4))
|
||||
assert.Nil(t, srv.MACByIP(netip.Addr{}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
@@ -64,69 +64,6 @@ func (conf *IPv4Config) validate() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// iface4 is a DHCP interface for IPv4 address family.
|
||||
type iface4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
addrSpace ipRange
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// newIface4 creates a new DHCP interface for IPv4 address family with the given
|
||||
// configuration. It returns an error if the given configuration can't be used.
|
||||
func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
|
||||
if !conf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||
|
||||
switch {
|
||||
case !subnet.Contains(conf.RangeStart):
|
||||
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||
case !subnet.Contains(conf.RangeEnd):
|
||||
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||
}
|
||||
|
||||
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if addrSpace.contains(conf.GatewayIP) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
i = &iface4{
|
||||
name: name,
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
@@ -318,3 +255,83 @@ func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
|
||||
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
||||
// netInterfaceV4 is a DHCP interface for IPv4 address family.
|
||||
type netInterfaceV4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
addrSpace ipRange
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
}
|
||||
|
||||
// newNetInterfaceV4 creates a new DHCP interface for IPv4 address family with
|
||||
// the given configuration. It returns an error if the given configuration
|
||||
// can't be used.
|
||||
func newNetInterfaceV4(name string, conf *IPv4Config) (i *netInterfaceV4, err error) {
|
||||
if !conf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||
|
||||
switch {
|
||||
case !subnet.Contains(conf.RangeStart):
|
||||
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||
case !subnet.Contains(conf.RangeEnd):
|
||||
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||
}
|
||||
|
||||
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if addrSpace.contains(conf.GatewayIP) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
i = &netInterfaceV4{
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV4 []*netInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package dhcpsvc
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
@@ -52,57 +53,6 @@ func (conf *IPv6Config) validate() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// iface6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type iface6 struct {
|
||||
// rangeStart is the first IP address in the range.
|
||||
rangeStart netip.Addr
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPv6Options
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPv6Options
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
|
||||
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
|
||||
// flags.
|
||||
raSLAACOnly bool
|
||||
|
||||
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
|
||||
raAllowSLAAC bool
|
||||
}
|
||||
|
||||
// newIface6 creates a new DHCP interface for IPv6 address family with the given
|
||||
// configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newIface6(name string, conf *IPv6Config) (i *iface6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
i = &iface6{
|
||||
name: name,
|
||||
rangeStart: conf.RangeStart,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
@@ -133,3 +83,79 @@ func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
|
||||
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
|
||||
return int(a.Code) - int(b.Code)
|
||||
}
|
||||
|
||||
// netInterfaceV6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type netInterfaceV6 struct {
|
||||
// rangeStart is the first IP address in the range.
|
||||
rangeStart netip.Addr
|
||||
|
||||
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPv6Options
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPv6Options
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
|
||||
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
|
||||
// flags.
|
||||
raSLAACOnly bool
|
||||
|
||||
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
|
||||
raAllowSLAAC bool
|
||||
}
|
||||
|
||||
// newNetInterfaceV6 creates a new DHCP interface for IPv6 address family with
|
||||
// the given configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newNetInterfaceV6(name string, conf *IPv6Config) (i *netInterfaceV6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
i = &netInterfaceV6{
|
||||
rangeStart: conf.RangeStart,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV6 []*netInterfaceV6
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool) {
|
||||
// prefLen is the length of prefix to match ip against.
|
||||
//
|
||||
// TODO(e.burkov): DHCPv6 inherits the weird behavior of legacy
|
||||
// implementation where the allocated range constrained by the first address
|
||||
// and the first address with last byte set to 0xff. Proper prefixes should
|
||||
// be used instead.
|
||||
const prefLen = netutil.IPv6BitLen - 8
|
||||
|
||||
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV6) (contains bool) {
|
||||
return !iface.rangeStart.Less(ip) &&
|
||||
netip.PrefixFrom(iface.rangeStart, prefLen).Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -24,7 +25,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// ClientsContainer provides information about preconfigured DNS clients.
|
||||
@@ -40,7 +40,7 @@ type ClientsContainer interface {
|
||||
) (conf *proxy.CustomUpstreamConfig, err error)
|
||||
}
|
||||
|
||||
// Config represents the DNS filtering configuration of AdGuard Home. The zero
|
||||
// Config represents the DNS filtering configuration of AdGuard Home. The zero
|
||||
// Config is empty and ready for use.
|
||||
type Config struct {
|
||||
// Callbacks for other modules
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestAnyNameMatches(t *testing.T) {
|
||||
|
||||
@@ -2,54 +2,56 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// upstreamConfigValidator parses the [*proxy.UpstreamConfig] and checks the
|
||||
// actual DNS availability of each upstream.
|
||||
// upstreamConfigValidator parses each section of an upstream configuration into
|
||||
// a corresponding [*proxy.UpstreamConfig] and checks the actual DNS
|
||||
// availability of each upstream.
|
||||
type upstreamConfigValidator struct {
|
||||
// general is the general upstream configuration.
|
||||
general []*upstreamResult
|
||||
// generalUpstreamResults contains upstream results of a general section.
|
||||
generalUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// fallback is the fallback upstream configuration.
|
||||
fallback []*upstreamResult
|
||||
// fallbackUpstreamResults contains upstream results of a fallback section.
|
||||
fallbackUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// private is the private upstream configuration.
|
||||
private []*upstreamResult
|
||||
// privateUpstreamResults contains upstream results of a private section.
|
||||
privateUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// generalParseResults contains parsing results of a general section.
|
||||
generalParseResults []*parseResult
|
||||
|
||||
// fallbackParseResults contains parsing results of a fallback section.
|
||||
fallbackParseResults []*parseResult
|
||||
|
||||
// privateParseResults contains parsing results of a private section.
|
||||
privateParseResults []*parseResult
|
||||
}
|
||||
|
||||
// upstreamResult is a result of validation of an [upstream.Upstream] within an
|
||||
// upstreamResult is a result of parsing of an [upstream.Upstream] within an
|
||||
// [proxy.UpstreamConfig].
|
||||
type upstreamResult struct {
|
||||
// server is the parsed upstream. It is nil when there was an error during
|
||||
// parsing.
|
||||
// server is the parsed upstream.
|
||||
server upstream.Upstream
|
||||
|
||||
// err is the error either from parsing or from checking the upstream.
|
||||
// err is the upstream check error.
|
||||
err error
|
||||
|
||||
// original is the piece of configuration that have either been turned to an
|
||||
// upstream or caused an error.
|
||||
original string
|
||||
|
||||
// isSpecific is true if the upstream is domain-specific.
|
||||
isSpecific bool
|
||||
}
|
||||
|
||||
// compare compares two [upstreamResult]s. It returns 0 if they are equal, -1
|
||||
// if ur should be sorted before other, and 1 otherwise.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps it makes sense to sort the results with errors near
|
||||
// the end.
|
||||
func (ur *upstreamResult) compare(other *upstreamResult) (res int) {
|
||||
return strings.Compare(ur.original, other.original)
|
||||
// parseResult contains a original piece of upstream configuration and a
|
||||
// corresponding error.
|
||||
type parseResult struct {
|
||||
err *proxy.ParseError
|
||||
original string
|
||||
}
|
||||
|
||||
// newUpstreamConfigValidator parses the upstream configuration and returns a
|
||||
@@ -61,97 +63,99 @@ func newUpstreamConfigValidator(
|
||||
private []string,
|
||||
opts *upstream.Options,
|
||||
) (cv *upstreamConfigValidator) {
|
||||
cv = &upstreamConfigValidator{}
|
||||
cv = &upstreamConfigValidator{
|
||||
generalUpstreamResults: map[string]*upstreamResult{},
|
||||
fallbackUpstreamResults: map[string]*upstreamResult{},
|
||||
privateUpstreamResults: map[string]*upstreamResult{},
|
||||
}
|
||||
|
||||
for _, line := range general {
|
||||
cv.general = cv.insertLineResults(cv.general, line, opts)
|
||||
}
|
||||
for _, line := range fallback {
|
||||
cv.fallback = cv.insertLineResults(cv.fallback, line, opts)
|
||||
}
|
||||
for _, line := range private {
|
||||
cv.private = cv.insertLineResults(cv.private, line, opts)
|
||||
}
|
||||
conf, err := proxy.ParseUpstreamsConfig(general, opts)
|
||||
cv.generalParseResults = collectErrResults(general, err)
|
||||
insertConfResults(conf, cv.generalUpstreamResults)
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(fallback, opts)
|
||||
cv.fallbackParseResults = collectErrResults(fallback, err)
|
||||
insertConfResults(conf, cv.fallbackUpstreamResults)
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(private, opts)
|
||||
cv.privateParseResults = collectErrResults(private, err)
|
||||
insertConfResults(conf, cv.privateUpstreamResults)
|
||||
|
||||
return cv
|
||||
}
|
||||
|
||||
// insertLineResults parses line and inserts the result into s. It can insert
|
||||
// multiple results as well as none.
|
||||
func (cv *upstreamConfigValidator) insertLineResults(
|
||||
s []*upstreamResult,
|
||||
line string,
|
||||
opts *upstream.Options,
|
||||
) (result []*upstreamResult) {
|
||||
upstreams, isSpecific, err := splitUpstreamLine(line)
|
||||
if err != nil {
|
||||
return cv.insert(s, &upstreamResult{
|
||||
err: err,
|
||||
original: line,
|
||||
})
|
||||
// collectErrResults parses err and returns parsing results containing the
|
||||
// original upstream configuration line and the corresponding error. err can be
|
||||
// nil.
|
||||
func collectErrResults(lines []string, err error) (results []*parseResult) {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, upstreamAddr := range upstreams {
|
||||
var res *upstreamResult
|
||||
if upstreamAddr != "#" {
|
||||
res = cv.parseUpstream(upstreamAddr, opts)
|
||||
} else if !isSpecific {
|
||||
res = &upstreamResult{
|
||||
err: errNotDomainSpecific,
|
||||
original: upstreamAddr,
|
||||
}
|
||||
} else {
|
||||
// limit is a maximum length for upstream configuration lines.
|
||||
const limit = 80
|
||||
|
||||
wrapper, ok := err.(errors.WrapperSlice)
|
||||
if !ok {
|
||||
log.Debug("dnsforward: configvalidator: unwrapping: %s", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := wrapper.Unwrap()
|
||||
results = make([]*parseResult, 0, len(errs))
|
||||
for i, e := range errs {
|
||||
var parseErr *proxy.ParseError
|
||||
if !errors.As(e, &parseErr) {
|
||||
log.Debug("dnsforward: configvalidator: inserting unexpected error %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
res.isSpecific = isSpecific
|
||||
s = cv.insert(s, res)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// insert inserts r into slice in a sorted order, except duplicates. slice must
|
||||
// not be nil.
|
||||
func (cv *upstreamConfigValidator) insert(
|
||||
s []*upstreamResult,
|
||||
r *upstreamResult,
|
||||
) (result []*upstreamResult) {
|
||||
i, has := slices.BinarySearchFunc(s, r, (*upstreamResult).compare)
|
||||
if has {
|
||||
log.Debug("dnsforward: duplicate configuration %q", r.original)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
return slices.Insert(s, i, r)
|
||||
}
|
||||
|
||||
// parseUpstream parses addr and returns the result of parsing. It returns nil
|
||||
// if the specified server points at the default upstream server which is
|
||||
// validated separately.
|
||||
func (cv *upstreamConfigValidator) parseUpstream(
|
||||
addr string,
|
||||
opts *upstream.Options,
|
||||
) (r *upstreamResult) {
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
if proto, _, ok := strings.Cut(addr, "://"); ok {
|
||||
if !slices.Contains(protocols, proto) {
|
||||
return &upstreamResult{
|
||||
err: fmt.Errorf("bad protocol %q", proto),
|
||||
original: addr,
|
||||
}
|
||||
idx := parseErr.Idx
|
||||
line := []rune(lines[idx])
|
||||
if len(line) > limit {
|
||||
line = line[:limit]
|
||||
line[limit-1] = '…'
|
||||
}
|
||||
|
||||
results = append(results, &parseResult{
|
||||
original: string(line),
|
||||
err: parseErr,
|
||||
})
|
||||
}
|
||||
|
||||
ups, err := upstream.AddressToUpstream(addr, opts)
|
||||
return results
|
||||
}
|
||||
|
||||
return &upstreamResult{
|
||||
server: ups,
|
||||
err: err,
|
||||
original: addr,
|
||||
// insertConfResults parses conf and inserts the upstream result into results.
|
||||
// It can insert multiple results as well as none.
|
||||
func insertConfResults(conf *proxy.UpstreamConfig, results map[string]*upstreamResult) {
|
||||
insertListResults(conf.Upstreams, results, false)
|
||||
|
||||
for _, ups := range conf.DomainReservedUpstreams {
|
||||
insertListResults(ups, results, true)
|
||||
}
|
||||
|
||||
for _, ups := range conf.SpecifiedDomainUpstreams {
|
||||
insertListResults(ups, results, true)
|
||||
}
|
||||
}
|
||||
|
||||
// insertListResults constructs upstream results from the upstream list and
|
||||
// inserts them into results. It can insert multiple results as well as none.
|
||||
func insertListResults(ups []upstream.Upstream, results map[string]*upstreamResult, specific bool) {
|
||||
for _, u := range ups {
|
||||
addr := u.Address()
|
||||
_, ok := results[addr]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
results[addr] = &upstreamResult{
|
||||
server: u,
|
||||
isSpecific: specific,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,35 +191,30 @@ func (cv *upstreamConfigValidator) check() {
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(cv.general) + len(cv.fallback) + len(cv.private))
|
||||
wg.Add(len(cv.generalUpstreamResults) +
|
||||
len(cv.fallbackUpstreamResults) +
|
||||
len(cv.privateUpstreamResults))
|
||||
|
||||
for _, res := range cv.general {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
for _, res := range cv.generalUpstreamResults {
|
||||
go checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
for _, res := range cv.fallbackUpstreamResults {
|
||||
go checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
go cv.checkSrv(res, wg, arpaChecker)
|
||||
for _, res := range cv.privateUpstreamResults {
|
||||
go checkSrv(res, wg, arpaChecker)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// checkSrv runs hc on the server from res, if any, and stores any occurred
|
||||
// error in res. wg is always marked done in the end. It used to be called in
|
||||
// a separate goroutine.
|
||||
func (cv *upstreamConfigValidator) checkSrv(
|
||||
res *upstreamResult,
|
||||
wg *sync.WaitGroup,
|
||||
hc *healthchecker,
|
||||
) {
|
||||
// error in res. wg is always marked done in the end. It is intended to be
|
||||
// used as a goroutine.
|
||||
func checkSrv(res *upstreamResult, wg *sync.WaitGroup, hc *healthchecker) {
|
||||
defer log.OnPanic(fmt.Sprintf("dnsforward: checking upstream %s", res.server.Address()))
|
||||
defer wg.Done()
|
||||
|
||||
if res.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
res.err = hc.check(res.server)
|
||||
if res.err != nil && res.isSpecific {
|
||||
res.err = domainSpecificTestError{Err: res.err}
|
||||
@@ -225,65 +224,126 @@ func (cv *upstreamConfigValidator) checkSrv(
|
||||
// close closes all the upstreams that were successfully parsed. It enriches
|
||||
// the results with deferred closing errors.
|
||||
func (cv *upstreamConfigValidator) close() {
|
||||
for _, slice := range [][]*upstreamResult{cv.general, cv.fallback, cv.private} {
|
||||
for _, r := range slice {
|
||||
if r.server != nil {
|
||||
r.err = errors.WithDeferred(r.err, r.server.Close())
|
||||
}
|
||||
all := []map[string]*upstreamResult{
|
||||
cv.generalUpstreamResults,
|
||||
cv.fallbackUpstreamResults,
|
||||
cv.privateUpstreamResults,
|
||||
}
|
||||
|
||||
for _, m := range all {
|
||||
for _, r := range m {
|
||||
r.err = errors.WithDeferred(r.err, r.server.Close())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sections of the upstream configuration according to the text label of the
|
||||
// localization.
|
||||
//
|
||||
// Keep in sync with client/src/__locales/en.json.
|
||||
//
|
||||
// TODO(s.chzhen): Refactor.
|
||||
const (
|
||||
generalTextLabel = "upstream_dns"
|
||||
fallbackTextLabel = "fallback_dns_title"
|
||||
privateTextLabel = "local_ptr_title"
|
||||
)
|
||||
|
||||
// status returns all the data collected during parsing, healthcheck, and
|
||||
// closing of the upstreams. The returned map is keyed by the original upstream
|
||||
// configuration piece and contains the corresponding error or "OK" if there was
|
||||
// no error.
|
||||
func (cv *upstreamConfigValidator) status() (results map[string]string) {
|
||||
result := map[string]string{}
|
||||
// Names of the upstream configuration sections for logging.
|
||||
const (
|
||||
generalSection = "general"
|
||||
fallbackSection = "fallback"
|
||||
privateSection = "private"
|
||||
)
|
||||
|
||||
for _, res := range cv.general {
|
||||
resultToStatus("general", res, result)
|
||||
results = map[string]string{}
|
||||
|
||||
for original, res := range cv.generalUpstreamResults {
|
||||
upstreamResultToStatus(generalSection, string(original), res, results)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
resultToStatus("fallback", res, result)
|
||||
for original, res := range cv.fallbackUpstreamResults {
|
||||
upstreamResultToStatus(fallbackSection, string(original), res, results)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
resultToStatus("private", res, result)
|
||||
for original, res := range cv.privateUpstreamResults {
|
||||
upstreamResultToStatus(privateSection, string(original), res, results)
|
||||
}
|
||||
|
||||
return result
|
||||
parseResultToStatus(generalTextLabel, generalSection, cv.generalParseResults, results)
|
||||
parseResultToStatus(fallbackTextLabel, fallbackSection, cv.fallbackParseResults, results)
|
||||
parseResultToStatus(privateTextLabel, privateSection, cv.privateParseResults, results)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// resultToStatus puts "OK" or an error message from res into resMap. section
|
||||
// is the name of the upstream configuration section, i.e. "general",
|
||||
// upstreamResultToStatus puts "OK" or an error message from res into resMap.
|
||||
// section is the name of the upstream configuration section, i.e. "general",
|
||||
// "fallback", or "private", and only used for logging.
|
||||
//
|
||||
// TODO(e.burkov): Currently, the HTTP handler expects that all the results are
|
||||
// put together in a single map, which may lead to collisions, see AG-27539.
|
||||
// Improve the results compilation.
|
||||
func resultToStatus(section string, res *upstreamResult, resMap map[string]string) {
|
||||
func upstreamResultToStatus(
|
||||
section string,
|
||||
original string,
|
||||
res *upstreamResult,
|
||||
resMap map[string]string,
|
||||
) {
|
||||
val := "OK"
|
||||
if res.err != nil {
|
||||
val = res.err.Error()
|
||||
}
|
||||
|
||||
prevVal := resMap[res.original]
|
||||
prevVal := resMap[original]
|
||||
switch prevVal {
|
||||
case "":
|
||||
resMap[res.original] = val
|
||||
resMap[original] = val
|
||||
case val:
|
||||
log.Debug("dnsforward: duplicating %s config line %q", section, res.original)
|
||||
log.Debug("dnsforward: duplicating %s config line %q", section, original)
|
||||
default:
|
||||
log.Debug(
|
||||
"dnsforward: warning: %s config line %q (%v) had different result %v",
|
||||
section,
|
||||
val,
|
||||
res.original,
|
||||
original,
|
||||
prevVal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// parseResultToStatus puts parsing error messages from results into resMap.
|
||||
// section is the name of the upstream configuration section, i.e. "general",
|
||||
// "fallback", or "private", and only used for logging.
|
||||
//
|
||||
// Parsing error message has the following format:
|
||||
//
|
||||
// sectionTextLabel line: parsing error
|
||||
//
|
||||
// Where sectionTextLabel is a section text label of a localization and line is
|
||||
// a line number.
|
||||
func parseResultToStatus(
|
||||
textLabel string,
|
||||
section string,
|
||||
results []*parseResult,
|
||||
resMap map[string]string,
|
||||
) {
|
||||
for _, res := range results {
|
||||
original := res.original
|
||||
_, ok := resMap[original]
|
||||
if ok {
|
||||
log.Debug("dnsforward: duplicating %s parsing error %q", section, original)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resMap[original] = fmt.Sprintf("%s %d: parsing error", textLabel, res.err.Idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
|
||||
// the tested upstream domain-specific and therefore consider its errors
|
||||
// non-critical.
|
||||
@@ -342,7 +402,7 @@ func (h *healthchecker) check(u upstream.Upstream) (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
} else if h.ansEmpty && len(reply.Answer) > 0 {
|
||||
return errWrongResponse
|
||||
return errors.Error("wrong response")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/netutil/sysresolv"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// DefaultTimeout is the default upstream timeout
|
||||
@@ -83,7 +83,9 @@ type DHCP interface {
|
||||
|
||||
// SystemResolvers is an interface for accessing the OS-provided resolvers.
|
||||
type SystemResolvers interface {
|
||||
// Addrs returns the list of system resolvers' addresses.
|
||||
// Addrs returns the list of system resolvers' addresses. Callers must
|
||||
// clone the returned slice before modifying it. Implementations of Addrs
|
||||
// must be safe for concurrent use.
|
||||
Addrs() (addrs []netip.AddrPort)
|
||||
}
|
||||
|
||||
|
||||
@@ -1330,6 +1330,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
|
||||
var eventsCalledCounter uint32
|
||||
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan struct{}) {
|
||||
assert.Equal(t, uint32(1), atomic.AddUint32(&eventsCalledCounter, 1))
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// beforeRequestHandler is the handler that is called before any other
|
||||
|
||||
@@ -6,16 +6,17 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// jsonDNSConfig is the JSON representation of the DNS server configuration.
|
||||
@@ -218,7 +219,7 @@ func (s *Server) defaultLocalPTRUpstreams() (ups []string, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysResolvers := slices.DeleteFunc(s.sysResolvers.Addrs(), matcher.Has)
|
||||
sysResolvers := slices.DeleteFunc(slices.Clone(s.sysResolvers.Addrs()), matcher.Has)
|
||||
ups = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
ups = append(ups, r.String())
|
||||
@@ -294,7 +295,7 @@ func (req *jsonDNSConfig) checkFallbacks() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ValidateUpstreams(*req.Fallbacks)
|
||||
_, err = proxy.ParseUpstreamsConfig(*req.Fallbacks, &upstream.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fallback servers: %w", err)
|
||||
}
|
||||
@@ -344,7 +345,7 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) {
|
||||
// validateUpstreamDNSServers returns an error if any field of req is invalid.
|
||||
func (req *jsonDNSConfig) validateUpstreamDNSServers(privateNets netutil.SubnetSet) (err error) {
|
||||
if req.Upstreams != nil {
|
||||
err = ValidateUpstreams(*req.Upstreams)
|
||||
_, err = proxy.ParseUpstreamsConfig(*req.Upstreams, &upstream.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("upstream servers: %w", err)
|
||||
}
|
||||
@@ -580,9 +581,6 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req.Upstreams = stringutil.FilterOut(req.Upstreams, IsCommentOrEmpty)
|
||||
req.FallbackDNS = stringutil.FilterOut(req.FallbackDNS, IsCommentOrEmpty)
|
||||
req.PrivateUpstreams = stringutil.FilterOut(req.PrivateUpstreams, IsCommentOrEmpty)
|
||||
req.BootstrapDNS = stringutil.FilterOut(req.BootstrapDNS, IsCommentOrEmpty)
|
||||
|
||||
opts := &upstream.Options{
|
||||
|
||||
@@ -223,8 +223,9 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) {
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "upstream_dns_bad",
|
||||
wantSet: `validating dns config: ` +
|
||||
`upstream servers: validating upstream "!!!": not an ip:port`,
|
||||
wantSet: `validating dns config: upstream servers: parsing error at index 0: ` +
|
||||
`cannot prepare the upstream: invalid address !!!: bad hostname "!!!": ` +
|
||||
`bad top-level domain name label "!!!": bad top-level domain name label rune '!'`,
|
||||
}, {
|
||||
name: "bootstraps_bad",
|
||||
wantSet: `validating dns config: checking bootstrap a: not a bootstrap: ParseAddr("a"): ` +
|
||||
@@ -313,98 +314,6 @@ func TestIsCommentOrEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreams(t *testing.T) {
|
||||
const sdnsStamp = `sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_J` +
|
||||
`S3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczE` +
|
||||
`uYWRndWFyZC5jb20`
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr string
|
||||
set []string
|
||||
}{{
|
||||
name: "empty",
|
||||
wantErr: ``,
|
||||
set: nil,
|
||||
}, {
|
||||
name: "comment",
|
||||
wantErr: ``,
|
||||
set: []string{"# comment"},
|
||||
}, {
|
||||
name: "no_default",
|
||||
wantErr: `no default upstreams specified`,
|
||||
set: []string{
|
||||
"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]" + sdnsStamp,
|
||||
},
|
||||
}, {
|
||||
name: "with_default",
|
||||
wantErr: ``,
|
||||
set: []string{
|
||||
"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]" + sdnsStamp,
|
||||
"8.8.8.8",
|
||||
},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `validating upstream "dhcp://fake.dns": bad protocol "dhcp"`,
|
||||
set: []string{"dhcp://fake.dns"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `validating upstream "1.2.3.4.5": not an ip:port`,
|
||||
set: []string{"1.2.3.4.5"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `validating upstream "123.3.7m": not an ip:port`,
|
||||
set: []string{"123.3.7m"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `splitting upstream line "[/host.com]tls://dns.adguard.com": ` +
|
||||
`missing separator`,
|
||||
set: []string{"[/host.com]tls://dns.adguard.com"},
|
||||
}, {
|
||||
name: "invalid",
|
||||
wantErr: `validating upstream "[host.ru]#": not an ip:port`,
|
||||
set: []string{"[host.ru]#"},
|
||||
}, {
|
||||
name: "valid_default",
|
||||
wantErr: ``,
|
||||
set: []string{
|
||||
"1.1.1.1",
|
||||
"tls://1.1.1.1",
|
||||
"https://dns.adguard.com/dns-query",
|
||||
sdnsStamp,
|
||||
"udp://dns.google",
|
||||
"udp://8.8.8.8",
|
||||
"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]" + sdnsStamp,
|
||||
"[/пример.рф/]8.8.8.8",
|
||||
},
|
||||
}, {
|
||||
name: "bad_domain",
|
||||
wantErr: `splitting upstream line "[/!/]8.8.8.8": domain at index 0: ` +
|
||||
`bad domain name "!": bad top-level domain name label "!": ` +
|
||||
`bad top-level domain name label rune '!'`,
|
||||
set: []string{"[/!/]8.8.8.8"},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateUpstreams(tc.set)
|
||||
testutil.AssertErrorMsg(t, tc.wantErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreamsPrivate(t *testing.T) {
|
||||
ss := netutil.SubnetSetFunc(netutil.IsLocallyServed)
|
||||
|
||||
@@ -509,6 +418,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
&aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(_ string) (err error) { return nil },
|
||||
OnClose: func() (err error) { return nil },
|
||||
|
||||
@@ -2,13 +2,13 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// makeResponse creates a DNS response by req and sets necessary flags. It also
|
||||
|
||||
@@ -2,10 +2,9 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
@@ -16,29 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
// errNotDomainSpecific is returned when the upstream should be
|
||||
// domain-specific, but isn't.
|
||||
errNotDomainSpecific errors.Error = "not a domain-specific upstream"
|
||||
|
||||
// errMissingSeparator is returned when the domain-specific part of the
|
||||
// upstream configuration line isn't closed.
|
||||
errMissingSeparator errors.Error = "missing separator"
|
||||
|
||||
// errDupSeparator is returned when the domain-specific part of the upstream
|
||||
// configuration line contains more than one ending separator.
|
||||
errDupSeparator errors.Error = "duplicated separator"
|
||||
|
||||
// errNoDefaultUpstreams is returned when there are no default upstreams
|
||||
// specified in the upstream configuration.
|
||||
errNoDefaultUpstreams errors.Error = "no default upstreams specified"
|
||||
|
||||
// errWrongResponse is returned when the checked upstream replies in an
|
||||
// unexpected way.
|
||||
errWrongResponse errors.Error = "wrong response"
|
||||
)
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
@@ -199,84 +175,12 @@ func IsCommentOrEmpty(s string) (ok bool) {
|
||||
return len(s) == 0 || s[0] == '#'
|
||||
}
|
||||
|
||||
// newUpstreamConfig validates upstreams and returns an appropriate upstream
|
||||
// configuration or nil if it can't be built.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps proxy.ParseUpstreamsConfig should validate upstreams
|
||||
// slice already so that this function may be considered useless.
|
||||
func newUpstreamConfig(upstreams []string) (conf *proxy.UpstreamConfig, err error) {
|
||||
// No need to validate comments and empty lines.
|
||||
upstreams = stringutil.FilterOut(upstreams, IsCommentOrEmpty)
|
||||
if len(upstreams) == 0 {
|
||||
// Consider this case valid since it means the default server should be
|
||||
// used.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = validateUpstreamConfig(upstreams)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(
|
||||
upstreams,
|
||||
&upstream.Options{
|
||||
Bootstrap: net.DefaultResolver,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return nil, err
|
||||
} else if len(conf.Upstreams) == 0 {
|
||||
return nil, errNoDefaultUpstreams
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// validateUpstreamConfig validates each upstream from the upstream
|
||||
// configuration and returns an error if any upstream is invalid.
|
||||
//
|
||||
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
|
||||
func validateUpstreamConfig(conf []string) (err error) {
|
||||
for _, u := range conf {
|
||||
var ups []string
|
||||
var isSpecific bool
|
||||
ups, isSpecific, err = splitUpstreamLine(u)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
for _, addr := range ups {
|
||||
_, err = validateUpstream(addr, isSpecific)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating upstream %q: %w", addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified.
|
||||
//
|
||||
// TODO(e.burkov): Merge with [upstreamConfigValidator] somehow.
|
||||
func ValidateUpstreams(upstreams []string) (err error) {
|
||||
_, err = newUpstreamConfig(upstreams)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateUpstreamsPrivate validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified. It also
|
||||
// checks each domain of domain-specific upstreams for being ARPA pointing to
|
||||
// a locally-served network. privateNets must not be nil.
|
||||
func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet) (err error) {
|
||||
conf, err := newUpstreamConfig(upstreams)
|
||||
conf, err := proxy.ParseUpstreamsConfig(upstreams, &upstream.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating config: %w", err)
|
||||
}
|
||||
@@ -308,66 +212,3 @@ func ValidateUpstreamsPrivate(upstreams []string, privateNets netutil.SubnetSet)
|
||||
|
||||
return errors.Annotate(errors.Join(errs...), "checking domain-specific upstreams: %w")
|
||||
}
|
||||
|
||||
// protocols are the supported URL schemes for upstreams.
|
||||
var protocols = []string{"h3", "https", "quic", "sdns", "tcp", "tls", "udp"}
|
||||
|
||||
// validateUpstream returns an error if u alongside with domains is not a valid
|
||||
// upstream configuration. useDefault is true if the upstream is
|
||||
// domain-specific and is configured to point at the default upstream server
|
||||
// which is validated separately. The upstream is considered domain-specific
|
||||
// only if domains is at least not nil.
|
||||
func validateUpstream(u string, isSpecific bool) (useDefault bool, err error) {
|
||||
// The special server address '#' means that default server must be used.
|
||||
if useDefault = u == "#" && isSpecific; useDefault {
|
||||
return useDefault, nil
|
||||
}
|
||||
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
if proto, _, ok := strings.Cut(u, "://"); ok {
|
||||
if !slices.Contains(protocols, proto) {
|
||||
return false, fmt.Errorf("bad protocol %q", proto)
|
||||
}
|
||||
} else if _, err = netip.ParseAddr(u); err == nil {
|
||||
return false, nil
|
||||
} else if _, err = netip.ParseAddrPort(u); err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// splitUpstreamLine returns the upstreams and the specified domains. domains
|
||||
// is nil when the upstream is not domains-specific. Otherwise it may also be
|
||||
// empty.
|
||||
func splitUpstreamLine(upstreamStr string) (upstreams []string, isSpecific bool, err error) {
|
||||
if !strings.HasPrefix(upstreamStr, "[/") {
|
||||
return []string{upstreamStr}, false, nil
|
||||
}
|
||||
|
||||
defer func() { err = errors.Annotate(err, "splitting upstream line %q: %w", upstreamStr) }()
|
||||
|
||||
doms, ups, found := strings.Cut(upstreamStr[2:], "/]")
|
||||
if !found {
|
||||
return nil, false, errMissingSeparator
|
||||
} else if strings.Contains(ups, "/]") {
|
||||
return nil, false, errDupSeparator
|
||||
}
|
||||
|
||||
for i, host := range strings.Split(doms, "/") {
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err = netutil.ValidateDomainName(strings.TrimPrefix(host, "*."))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("domain at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
isSpecific = true
|
||||
}
|
||||
|
||||
return strings.Fields(ups), isSpecific, nil
|
||||
}
|
||||
|
||||
@@ -100,8 +100,7 @@ func TestUpstreamConfigValidator(t *testing.T) {
|
||||
name: "bad_specification",
|
||||
general: []string{"[/domain.example/]/]1.2.3.4"},
|
||||
want: map[string]string{
|
||||
"[/domain.example/]/]1.2.3.4": `splitting upstream line ` +
|
||||
`"[/domain.example/]/]1.2.3.4": duplicated separator`,
|
||||
"[/domain.example/]/]1.2.3.4": generalTextLabel + " 1: parsing error",
|
||||
},
|
||||
}, {
|
||||
name: "all_different",
|
||||
@@ -120,23 +119,9 @@ func TestUpstreamConfigValidator(t *testing.T) {
|
||||
fallback: []string{"[/example/" + goodUps},
|
||||
private: []string{"[/example//bad.123/]" + goodUps},
|
||||
want: map[string]string{
|
||||
`[/example/]/]` + goodUps: `splitting upstream line ` +
|
||||
`"[/example/]/]` + goodUps + `": duplicated separator`,
|
||||
`[/example/` + goodUps: `splitting upstream line ` +
|
||||
`"[/example/` + goodUps + `": missing separator`,
|
||||
`[/example//bad.123/]` + goodUps: `splitting upstream line ` +
|
||||
`"[/example//bad.123/]` + goodUps + `": domain at index 2: ` +
|
||||
`bad domain name "bad.123": ` +
|
||||
`bad top-level domain name label "123": all octets are numeric`,
|
||||
},
|
||||
}, {
|
||||
name: "non-specific_default",
|
||||
general: []string{
|
||||
"#",
|
||||
"[/example/]#",
|
||||
},
|
||||
want: map[string]string{
|
||||
"#": "not a domain-specific upstream",
|
||||
"[/example/]/]" + goodUps: generalTextLabel + " 1: parsing error",
|
||||
"[/example/" + goodUps: fallbackTextLabel + " 1: parsing error",
|
||||
"[/example//bad.123/]" + goodUps: privateTextLabel + " 1: parsing error",
|
||||
},
|
||||
}, {
|
||||
name: "bad_proto",
|
||||
@@ -144,7 +129,15 @@ func TestUpstreamConfigValidator(t *testing.T) {
|
||||
"bad://1.2.3.4",
|
||||
},
|
||||
want: map[string]string{
|
||||
"bad://1.2.3.4": `bad protocol "bad"`,
|
||||
"bad://1.2.3.4": generalTextLabel + " 1: parsing error",
|
||||
},
|
||||
}, {
|
||||
name: "truncated_line",
|
||||
general: []string{
|
||||
"This is a very long line. It will cause a parsing error and will be truncated here.",
|
||||
},
|
||||
want: map[string]string{
|
||||
"This is a very long line. It will cause a parsing error and will be truncated …": "upstream_dns 1: parsing error",
|
||||
},
|
||||
}}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// serviceRules maps a service ID to its filtering rules.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// filterDir is the subdirectory of a data directory to store downloaded
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// The IDs of built-in filter lists.
|
||||
@@ -1113,8 +1113,7 @@ func (d *DNSFilter) periodicallyRefreshFilters(ivl time.Duration) (nextIvl time.
|
||||
ivl = maxInterval
|
||||
} else if isNetErr {
|
||||
ivl *= 2
|
||||
// TODO(s.chzhen): Use built-in function max in Go 1.21.
|
||||
ivl = mathutil.Max(ivl, maxInterval)
|
||||
ivl = max(ivl, maxInterval)
|
||||
}
|
||||
|
||||
return ivl
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package hashprefix
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -40,6 +40,7 @@ func TestDNSFilter_CheckHost_hostsContainer(t *testing.T) {
|
||||
},
|
||||
}
|
||||
watcher := &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(name string) (err error) { return nil },
|
||||
OnClose: func() (err error) { return nil },
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// validateFilterURL validates the filter list URL or file name.
|
||||
|
||||
@@ -3,6 +3,7 @@ package rewrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Storage is a storage for rewrite rules.
|
||||
|
||||
@@ -3,10 +3,10 @@ package filtering
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// TODO(d.kolyshev): Use [rewrite.Item] instead.
|
||||
|
||||
@@ -3,13 +3,12 @@ package filtering
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/mathutil"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Legacy DNS rewrites
|
||||
@@ -181,7 +180,7 @@ func findRewrites(
|
||||
if isWildcard(r.Domain) {
|
||||
// Don't use rewrites[:0], because we need to return at least one
|
||||
// item here.
|
||||
rewrites = rewrites[:mathutil.Max(1, i)]
|
||||
rewrites = rewrites[:max(1, i)]
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Parser is a filtering-rule parser that collects data, such as the checksum
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user