Compare commits

..

22 Commits

Author SHA1 Message Date
Ainar Garipov
37736289e2 Pull request 2151: upd-all
Squashed commit of the following:

commit 3115356ba147778afdda27fa69d1d49287e26ea7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 13 18:14:18 2024 +0300

    all: upd deps, flts, i18n, svcs, tools
2024-02-13 18:30:14 +03:00
Eugene Burkov
2a546aa609 Pull request 2149: 6711 watch hosts
Updates #6711.

Squashed commit of the following:

commit 3ddfe809f76325c2d4cda0715a7bcc15e76a2388
Merge: 185957cd0 d338451fa
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 13 13:01:30 2024 +0300

    Merge branch 'master' into 6711-watch-hosts

commit 185957cd01516e5955e388108615e6f131d6ad71
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 18:11:41 2024 +0300

    aghos: imp docs

commit 3afbbcbb7ab6cc60c7c40ef8acd5b3ddf52cb3d1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 15:40:02 2024 +0300

    all: upd golibs, imp fswatcher
2024-02-13 13:19:22 +03:00
Stanislav Chzhen
d338451faf Pull request 2145: 5992-stats-qlog-custom-dir
Updates #5992.

Squashed commit of the following:

commit 39d3df705ef68672ec9406d81e00daf52a3b3c70
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 12 15:25:22 2024 +0300

    all: fix typo

commit 21e03e4d5a7624a68add53734a127652053845a2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 12 15:22:35 2024 +0300

    all: upd docs

commit 11180061619f4352774d4bc8c85b481ae28f0d0b
Merge: ac5fd8dc8 7f8370744
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 12 14:57:35 2024 +0300

    Merge branch 'master' into 5992-stats-qlog-custom-dir

commit ac5fd8dc82c9c6e88a182cd6e6aed07bf3548639
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 7 14:55:51 2024 +0300

    all: upd chlog

commit fe00652e158db65e0e735a19cf88aa999ece3e62
Merge: 21ad1ecf7 56b98080f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 7 14:53:24 2024 +0300

    Merge branch 'master' into 5992-stats-qlog-custom-dir

commit 21ad1ecf7b30c3c8f45d54210d7297966126b0f7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Feb 7 14:52:12 2024 +0300

    home: imp docs

commit 739b158de77e673ef80efdaa523044939ea879d5
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Feb 5 20:15:50 2024 +0300

    home: add stats qlog custom dir
2024-02-12 18:45:51 +03:00
Eugene Burkov
7f83707449 Pull request 2143: AG-29441 QA versions
Squashed commit of the following:

commit c004644dd6b86847a654764e2055349919ac0de0
Merge: f26ec4440 24baee0f8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 9 13:09:44 2024 +0300

    Merge branch 'master' into AG-29441-qa-versions

commit f26ec4440cd406d0753d1225313b3cc5f60bf3e2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Feb 7 13:27:15 2024 +0300

    github: imp workflow doc

commit 825a00c8dde47f9bc9b837885978a135bfb1dc61
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 6 18:53:32 2024 +0300

    scripts: imp doc

commit ef62d96cec66f186799db175a813fca0913482b7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 6 15:00:42 2024 +0300

    scrits: imp count

commit 2d02b23da426063b9a852c06a04ba2fef874b9bf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 6 14:22:28 2024 +0300

    scripts: imp code, docs

commit 549bfb982fa32cd30aa339f4458e60b6fc22e53a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 5 18:54:55 2024 +0300

    scripts: imp dev version
2024-02-09 13:47:04 +03:00
Ainar Garipov
24baee0f8e Pull request 2148: 6703-adguardhome-sync
Updates #6703.

* commit 'a13d120317281e397aa3f1f68d76d6f95da6145a':
  all: fix markdown
  Mention AdGuardHome sync in usages
2024-02-09 12:06:30 +03:00
Ainar Garipov
a13d120317 all: fix markdown 2024-02-08 21:23:26 +03:00
Ainar Garipov
32bd78347c Merge branch 'master' into 6703-adguardhome-sync 2024-02-08 21:22:54 +03:00
Ainar Garipov
332621f268 Pull request 2147: all: upd deps, go, scripts
Squashed commit of the following:

commit 425f1bd28074d22890629d06f43257e0353ce3d5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Feb 8 20:15:58 2024 +0300

    all: upd deps, go, scripts
2024-02-08 20:39:18 +03:00
Ildar Kamalov
02ea4a362c Pull request: add persistent client from the query log context menu
Updates #6679

Squashed commit of the following:

commit 2e051c9528085182a22ec40a1df11780012a5001
Merge: a001f52ab 56b98080f
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Feb 8 15:02:22 2024 +0300

    Merge branch 'master' into ADG-8179

commit a001f52ab5dadcfc1116ac46da01c0344e51b656
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 5 18:59:13 2024 +0300

    fix changelog

commit 5bac6a2446413b157da6bb404e0e21bb35ac6a10
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 5 16:01:01 2024 +0300

    fix

commit 14a7190ebb18fbed99a897723c27b80144d56825
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Feb 5 15:59:35 2024 +0300

    ADG-8179 add persistent client from query log context menu
2024-02-08 15:10:12 +03:00
Ainar Garipov
56b98080ff Pull request 2146: upd-chlog
Squashed commit of the following:

commit ddac33162c96583276561b8d6714fe0389f92277
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 6 19:48:34 2024 +0300

    all: upd chlog
2024-02-06 19:59:05 +03:00
Ainar Garipov
bf38a7ce40 Pull request 2144: gh-tmpl
Squashed commit of the following:

commit 55880402b3aac1b88009c9e97cc11200a549fd48
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 5 19:49:52 2024 +0300

    all: imp gh tmpl
2024-02-05 20:04:39 +03:00
Stanislav Chzhen
34aa81ca99 Pull request 2107: AG-28327-upstream-config-parser
Squashed commit of the following:

commit e496653b10de52676826ed8e0c461e91405603a8
Merge: db2cd04e9 60a978c9a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Feb 1 18:23:50 2024 +0300

    Merge branch 'master' into AG-28327-upstream-config-parser

commit db2cd04e981dd24998d87f4935ff6590ea7854cd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jan 31 16:21:53 2024 +0300

    all: upd proxy

commit e8878179b6d094321d56fb2b75c16c1ba8cf637d
Merge: ccbbae6d6 aa872dfe9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jan 31 16:17:34 2024 +0300

    Merge branch 'master' into AG-28327-upstream-config-parser

commit ccbbae6d615e110d7d2d4c2a6b35954311153bcf
Merge: d947d900e 8936c95ec
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jan 29 18:31:17 2024 +0300

    Merge branch 'master' into AG-28327-upstream-config-parser

commit d947d900e1f759159bc9068589ffe852483cfdd0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jan 29 18:26:01 2024 +0300

    dnsforward: imp docs

commit cf9678c098951e2a4bebae7a3a5808d7de4c14c6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jan 25 14:18:04 2024 +0300

    dnsforward: imp code

commit 22792a9311cb93b2bb3b804293f87f091b9b81e2
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jan 24 13:59:28 2024 +0300

    dnsforward: imp code

commit 57ddaaaaaf1009c65f0d9d6b2b1671211f194c85
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jan 22 20:19:44 2024 +0300

    all: add tests

commit d6732d13adae4ee46410252a33d092e67da3c34a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jan 22 18:44:57 2024 +0300

    all: imp errors

commit e14456571ce2ef43fb217f45445729ce6299daf6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Jan 18 19:05:31 2024 +0300

    dnsforward: imp code

commit a5c106eae902fbc0a169ef9e4d7bf968f1e40bec
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jan 15 18:36:30 2024 +0300

    dnsforward: imp logs

commit 333b8561aa21d778007f808fb8e931ef3e95d721
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Dec 21 15:06:42 2023 +0300

    all: imp tests

commit 5b19d6b039755577e03ffcc03952724a36f21aa4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Dec 15 14:21:58 2023 +0300

    all: imp code

commit 15fbd229de336425bde107a4f32175b8af41d876
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Dec 13 14:49:40 2023 +0300

    all: upstream config parser
2024-02-05 16:12:27 +03:00
Marc Brugger
b98e093157 Mention AdGuardHome sync in usages 2024-02-03 10:00:12 +01:00
Ainar Garipov
60a978c9a6 Pull request 2141: upd-i18n
Squashed commit of the following:

commit fd143da4328b7b018146c503cf09ab7532e7c6df
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Feb 1 15:04:00 2024 +0300

    client: upd i18n
2024-02-01 15:59:55 +03:00
Ainar Garipov
2c055fea11 Pull request 2139: fix-invalid-addr
Squashed commit of the following:

commit 12e9e53ec20685989425411801bd836cbdefe9aa
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jan 31 19:34:40 2024 +0300

    all: fix chlog

commit 6a8d872dcf49ff685454297e36b888309e058fcf
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jan 31 19:33:18 2024 +0300

    all: doc

commit 12cd89634f9ae97876d8dd79e30de704b57ef47d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jan 31 19:20:27 2024 +0300

    dnsforward: fix http private ptrs
2024-02-01 13:46:28 +03:00
Eugene Burkov
aa872dfe98 Pull request 2130: 4923 gopacket dhcp vol.6
Updates #4923.

Squashed commit of the following:

commit 14ae8dc3680eae7d3ecb9e37a44c2e68221c5085
Merge: 280a4dbc7 713901c2a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 31 13:52:52 2024 +0300

    Merge branch 'master' into 4923-gopacket-dhcp-vol.6

commit 280a4dbc728ff67c7659f91734a74c87bf0bda43
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 30 20:20:04 2024 +0300

    dhcpsvc: imp docs

commit 310ed67b9bf22f88c4414095bfbfc1a29c6db6d5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 30 18:51:49 2024 +0300

    dhcpsvc: generalize

commit e4c2cae73a729be4db244d3042d93fcc9742bb34
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 30 12:37:38 2024 +0300

    dhcpsvc: imp code

commit 9a60d3529293ce1f0e8da70da05958f81e1d0092
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jan 26 16:28:04 2024 +0300

    dhcpsvc: imp code

commit 120c0472f3a3df2ebc0495a40936c8f94156db4b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 25 20:44:09 2024 +0300

    dhcpsvc: imp code, names, docs

commit a92f44c75279868d8e07fe7d468278025a245d13
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 24 16:01:35 2024 +0300

    dhcpsvc: imp code, docs

commit 18b3f237b7523f649b49563e852c298fe02fa8ae
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 18 15:29:36 2024 +0300

    dhcpsvc: add some lease-related methods
2024-01-31 14:50:27 +03:00
Ainar Garipov
713901c2a0 Pull request 2136: upd-all
Squashed commit of the following:

commit 96c544aa561b087463d6ec3e6176656aba920e84
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jan 30 18:40:38 2024 +0300

    all: upd i18n, svcs, trackers
2024-01-30 18:58:47 +03:00
Eugene Burkov
8936c95eca Pull request 2135: Fix changelog
Squashed commit of the following:

commit 857c53d263c4aa2de671e616b33b5a1abb0d1aac
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 25 21:07:29 2024 +0300

    all: fix chlog
2024-01-26 13:54:13 +03:00
Ainar Garipov
6178ad5762 Pull request 2134: 6675-readme-ext
Closes #6671.
Updates #6675.

* commit 'ea620d76bec60565d5ecb4c5178107f84f98f286':
  Readme - Added browser extension
2024-01-25 16:41:00 +03:00
Sathesh Sivashanmugam
ea620d76be Merge branch 'AdguardTeam:master' into master 2024-01-24 13:50:44 -07:00
Stanislav Chzhen
136c028600 Pull request 2133: AG-29704-filtering-syntax
Squashed commit of the following:

commit 834ed8480e0942d2b2f5522a0d78f59445d4fe88
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jan 24 17:40:42 2024 +0300

    client: upd filtering syntax links
2024-01-24 18:19:37 +03:00
Sathesh Sivashanmugam
bb2f1b4eb4 Readme - Added browser extension 2024-01-23 23:31:54 -07:00
129 changed files with 1594 additions and 1010 deletions

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.20.12'
'GO_VERSION': '1.21.7'
'on':
'push':

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}}» паспяхова дададзены",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}}\" را با موفقیت اضافه کرد",

View File

@@ -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 032",
"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ä 032.",
"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 0128",
"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ä 0128.",
"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.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}}\" の追加に成功しました",

View File

@@ -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}}'이(가) 정상적으로 추가되었습니다",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}}» успешно добавлен",

View File

@@ -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": "දිනය",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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": "Введіть значення порту в діапазоні 8065535",
@@ -462,6 +467,7 @@
"form_add_id": "Додати ідентифікатор",
"form_client_name": "Введіть ім'я клієнта",
"name": "Ім'я",
"client_name": "Клієнт {{id}}",
"client_global_settings": "Використати загальні налаштування",
"client_deleted": "Клієнта «{{key}}» успішно видалено",
"client_added": "Клієнта «{{key}}» успішно додано",

View File

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

View File

@@ -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}}\" 添加成功",

View File

@@ -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}}\" 被成功地加入",

View File

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

View File

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

View File

@@ -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',
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
package dnsforward
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
func TestAnyNameMatches(t *testing.T) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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