Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ccf8fe116 | ||
|
|
d22f0eefe2 | ||
|
|
344c66f7ab | ||
|
|
83be002b41 | ||
|
|
9945cd3991 | ||
|
|
667263a3a8 | ||
|
|
6318fc424b | ||
|
|
e32a37a747 | ||
|
|
7805a71332 | ||
|
|
6fb2aee210 | ||
|
|
ce9bb588ed | ||
|
|
55fb914537 | ||
|
|
6f7bfd6c9c |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'build'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.21.8'
|
||||
'GO_VERSION': '1.22.3'
|
||||
'NODE_VERSION': '16'
|
||||
|
||||
'on':
|
||||
@@ -53,9 +53,9 @@
|
||||
'path': '${{ steps.npm-cache.outputs.dir }}'
|
||||
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
|
||||
'restore-keys': '${{ runner.os }}-node-'
|
||||
- 'name': 'Run make ci'
|
||||
- 'name': 'Run tests'
|
||||
'shell': 'bash'
|
||||
'run': 'make VERBOSE=1 ci'
|
||||
'run': 'make VERBOSE=1 deps test go-bench go-fuzz'
|
||||
- 'name': 'Upload coverage'
|
||||
'uses': 'codecov/codecov-action@v1'
|
||||
'if': "success() && matrix.os == 'ubuntu-latest'"
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,7 +1,7 @@
|
||||
'name': 'lint'
|
||||
|
||||
'env':
|
||||
'GO_VERSION': '1.21.8'
|
||||
'GO_VERSION': '1.22.3'
|
||||
|
||||
'on':
|
||||
'push':
|
||||
|
||||
178
CHANGELOG.md
178
CHANGELOG.md
@@ -14,15 +14,158 @@ and this project adheres to
|
||||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.46] - 2024-03-13 (APPROX.)
|
||||
## [v0.107.51] - 2024-06-22 (APPROX.)
|
||||
|
||||
See also the [v0.107.46 GitHub milestone][ms-v0.107.46].
|
||||
See also the [v0.107.51 GitHub milestone][ms-v0.107.51].
|
||||
|
||||
[ms-v0.107.46]: https://github.com/AdguardTeam/AdGuardHome/milestone/81?closed=1
|
||||
[ms-v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/milestone/86?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.50] - 2024-05-23
|
||||
|
||||
See also the [v0.107.50 GitHub milestone][ms-v0.107.50].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Broken private reverse DNS upstream servers validation causing update failures
|
||||
([#7013]).
|
||||
|
||||
[#7013]: https://github.com/AdguardTeam/AdGuardHome/issues/7013
|
||||
|
||||
[ms-v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/milestone/85?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.49] - 2024-05-21
|
||||
|
||||
See also the [v0.107.49 GitHub milestone][ms-v0.107.49].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the Go
|
||||
vulnerabilities fixed in [Go 1.22.3][go-1.22.3].
|
||||
|
||||
### Added
|
||||
|
||||
- Support for comments in the ipset file ([#5345]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Private rDNS resolution now also affects `SOA` and `NS` requests ([#6882]).
|
||||
- Rewrite rules mechanics were changed due to improved resolving in safe search.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Currently, AdGuard Home skips persistent clients that have duplicate fields
|
||||
when reading them from the configuration file. This behaviour is deprecated
|
||||
and will cause errors on startup in a future release.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Acceptance of duplicate UIDs for persistent clients at startup. See also the
|
||||
section on client settings on the [Wiki page][wiki-config].
|
||||
- Domain specifications for top-level domains not considered for requests to
|
||||
unqualified domains ([#6744]).
|
||||
- Support for link-local subnets, i.e. `fe80::/16`, as client identifiers
|
||||
([#6312]).
|
||||
- Issues with QUIC and HTTP/3 upstreams on older Linux kernel versions
|
||||
([#6422]).
|
||||
- YouTube restricted mode is not enforced by HTTPS queries on Firefox.
|
||||
- Support for link-local subnets, i.e. `fe80::/16`, in the access settings
|
||||
([#6192]).
|
||||
- The ability to apply an invalid configuration for private rDNS, which led to
|
||||
server not starting.
|
||||
- Ignoring query log for clients with ClientID set ([#5812]).
|
||||
- Subdomains of `in-addr.arpa` and `ip6.arpa` containing zero-length prefix
|
||||
incorrectly considered invalid when specified for private rDNS upstream
|
||||
servers ([#6854]).
|
||||
- Unspecified IP addresses aren't checked when using "Fastest IP address" mode
|
||||
([#6875]).
|
||||
|
||||
[#5345]: https://github.com/AdguardTeam/AdGuardHome/issues/5345
|
||||
[#5812]: https://github.com/AdguardTeam/AdGuardHome/issues/5812
|
||||
[#6192]: https://github.com/AdguardTeam/AdGuardHome/issues/6192
|
||||
[#6312]: https://github.com/AdguardTeam/AdGuardHome/issues/6312
|
||||
[#6422]: https://github.com/AdguardTeam/AdGuardHome/issues/6422
|
||||
[#6744]: https://github.com/AdguardTeam/AdGuardHome/issues/6744
|
||||
[#6854]: https://github.com/AdguardTeam/AdGuardHome/issues/6854
|
||||
[#6875]: https://github.com/AdguardTeam/AdGuardHome/issues/6875
|
||||
[#6882]: https://github.com/AdguardTeam/AdGuardHome/issues/6882
|
||||
|
||||
[go-1.22.3]: https://groups.google.com/g/golang-announce/c/wkkO4P9stm0
|
||||
[ms-v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/milestone/84?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.48] - 2024-04-05
|
||||
|
||||
See also the [v0.107.48 GitHub milestone][ms-v0.107.48].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Access settings not being applied to encrypted protocols ([#6890]).
|
||||
|
||||
[#6890]: https://github.com/AdguardTeam/AdGuardHome/issues/6890
|
||||
|
||||
[ms-v0.107.48]: https://github.com/AdguardTeam/AdGuardHome/milestone/83?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.47] - 2024-04-04
|
||||
|
||||
See also the [v0.107.47 GitHub milestone][ms-v0.107.47].
|
||||
|
||||
### Security
|
||||
|
||||
- Go version has been updated to prevent the possibility of exploiting the Go
|
||||
vulnerabilities fixed in [Go 1.22.2][go-1.22.2].
|
||||
|
||||
### Changed
|
||||
|
||||
- Time Zone Database is now embedded in the binary ([#6758]).
|
||||
- Failed authentication attempts show the originating IP address in the logs, if
|
||||
the request came from a trusted proxy ([#5829]).
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Go 1.22 support. Future versions will require at least Go 1.23 to build.
|
||||
- Currently, AdGuard Home uses a best-effort algorithm to fix invalid IDs of
|
||||
filtering-rule lists on startup. This feature is deprecated, and invalid IDs
|
||||
will cause errors on startup in a future version.
|
||||
- Node.JS 16. Future versions will require at least Node.JS 18 to build.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resetting DNS upstream mode when applying unrelated settings ([#6851]).
|
||||
- Symbolic links to the configuration file begin replaced by a copy of the real
|
||||
file upon startup on FreeBSD ([#6717]).
|
||||
|
||||
### Removed
|
||||
|
||||
- Go 1.21 support.
|
||||
|
||||
[#5829]: https://github.com/AdguardTeam/AdGuardHome/issues/5829
|
||||
[#6717]: https://github.com/AdguardTeam/AdGuardHome/issues/6717
|
||||
[#6758]: https://github.com/AdguardTeam/AdGuardHome/issues/6758
|
||||
[#6851]: https://github.com/AdguardTeam/AdGuardHome/issues/6851
|
||||
|
||||
[go-1.22.2]: https://groups.google.com/g/golang-announce/c/YgW0sx8mN3M/
|
||||
[ms-v0.107.47]: https://github.com/AdguardTeam/AdGuardHome/milestone/82?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.46] - 2024-03-20
|
||||
|
||||
See also the [v0.107.46 GitHub milestone][ms-v0.107.46].
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to disable the use of system hosts file information for query
|
||||
@@ -30,17 +173,29 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||
- Ability to define custom directories for storage of query log files and
|
||||
statistics ([#5992]).
|
||||
|
||||
### Changed
|
||||
|
||||
- Private rDNS resolution (`dns.use_private_ptr_resolvers` in YAML
|
||||
configuration) now requires a valid "Private reverse DNS servers", when
|
||||
enabled ([#6820]).
|
||||
|
||||
**NOTE:** Disabling private rDNS resolution behaves effectively the same as if
|
||||
no private reverse DNS servers provided by user and by the OS.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Statistics for 7 days displayed by day on the dashboard graph ([#6712]).
|
||||
- Missing "served from cache" label on long DNS server strings ([#6740]).
|
||||
- Incorrect tracking of the system hosts file's changes ([#6711]).
|
||||
|
||||
[#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992
|
||||
[#6610]: https://github.com/AdguardTeam/AdGuardHome/issues/6610
|
||||
[#6711]: https://github.com/AdguardTeam/AdGuardHome/issues/6711
|
||||
[#6712]: https://github.com/AdguardTeam/AdGuardHome/issues/6712
|
||||
[#6740]: https://github.com/AdguardTeam/AdGuardHome/issues/6740
|
||||
[#6820]: https://github.com/AdguardTeam/AdGuardHome/issues/6820
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
[ms-v0.107.46]: https://github.com/AdguardTeam/AdGuardHome/milestone/81?closed=1
|
||||
|
||||
|
||||
|
||||
@@ -2831,11 +2986,16 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
||||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.46...HEAD
|
||||
[v0.107.46]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...v0.107.46
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.51...HEAD
|
||||
[v0.107.51]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...v0.107.51
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.50...HEAD
|
||||
[v0.107.50]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.49...v0.107.50
|
||||
[v0.107.49]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.48...v0.107.49
|
||||
[v0.107.48]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.47...v0.107.48
|
||||
[v0.107.47]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.46...v0.107.47
|
||||
[v0.107.46]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.45...v0.107.46
|
||||
[v0.107.45]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.44...v0.107.45
|
||||
[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
|
||||
|
||||
@@ -1,89 +1,57 @@
|
||||
# Contributing to AdGuard Home
|
||||
# Contributing to AdGuard Home
|
||||
|
||||
If you want to contribute to AdGuard Home by filing or commenting on an issue or
|
||||
opening a pull request, please follow the instructions below.
|
||||
If you want to contribute to AdGuard Home by filing or commenting on an issue or opening a pull request, please follow the instructions below.
|
||||
|
||||
## General recommendations
|
||||
|
||||
Please don’t:
|
||||
|
||||
## General recommendations
|
||||
- post comments like “+1” or “this”. Use the :+1: reaction on the issue instead, as this allows us to actually see the level of support for issues.
|
||||
|
||||
Please don't:
|
||||
- file issues about localization errors or send localization updates as PRs. We’re using [CrowdIn] to manage our translations and we generally update them before each Beta and Release build. You can learn more about translating AdGuard products [in our Knowledge Base][kb-trans].
|
||||
|
||||
* post comments like “+1” or “this”. Use the :+1: reaction on the issue
|
||||
instead, as this allows us to actually see the level of support for issues.
|
||||
- file issues about a particular filtering-rule list misbehaving. These are tracked through the [separate form for filtering issues][form].
|
||||
|
||||
* file issues about localization errors or send localization updates as PRs.
|
||||
We're using [CrowdIn] to manage our translations and we generally update
|
||||
them before each Beta and Release build. You can learn more about
|
||||
translating AdGuard products [in our Knowledge Base][kb-trans].
|
||||
|
||||
* file issues about a particular filtering-rule list misbehaving. These are
|
||||
tracked through the [separate form for filtering issues][form].
|
||||
|
||||
* send updates to filtering-rule lists, such as the ones for the Blocked
|
||||
Services feature or the list of approved filtering-rule lists. We update
|
||||
them once before each Beta and Release build.
|
||||
- send or request updates to filtering-rule lists, such as the ones for the Blocked Services feature or the list of approved filtering-rule lists. We update them from the [separate repository][hostlist] once before each Beta and Release build.
|
||||
|
||||
Please do:
|
||||
|
||||
* follow the template instructions and provide data for reproducing issues.
|
||||
- follow the template instructions and provide data for reproducing issues.
|
||||
|
||||
* write the title of your issue or pull request in English. Any language is
|
||||
fine in the body, but it is important to keep the title in English to make
|
||||
it easier for people and bots to look up duplicated issues.
|
||||
- write the title of your issue or pull request in English. Any language is fine in the body, but it is important to keep the title in English to make it easier for people and bots to look up duplicated issues.
|
||||
|
||||
[CrowdIn]: https://crowdin.com/project/adguard-applications/en#/adguard-home
|
||||
[form]: https://link.adtidy.org/forward.html?action=report&app=home&from=github
|
||||
[hostlist]: https://github.com/AdguardTeam/HostlistsRegistry
|
||||
[kb-trans]: https://kb.adguard.com/en/general/adguard-translations
|
||||
|
||||
## Issues
|
||||
|
||||
### Search first
|
||||
|
||||
## Issues
|
||||
|
||||
### Search first
|
||||
|
||||
Please make sure that the issue is not a duplicate or a question. If it's a
|
||||
duplicate, please react to the original issue with a thumbs up. If it's a
|
||||
question, please look through our [Wiki] and, if you haven't found the answer,
|
||||
post it to the GitHub [Discussions] page.
|
||||
Please make sure that the issue is not a duplicate or a question. If it’s a duplicate, please react to the original issue with a thumbs up. If it’s a question, please look through our [Wiki] and, if you haven’t found the answer, post it to the GitHub [Discussions] page.
|
||||
|
||||
[Discussions]: https://github.com/AdguardTeam/AdGuardHome/discussions/categories/q-a
|
||||
[Wiki]: https://github.com/AdguardTeam/AdGuardHome/wiki
|
||||
|
||||
### Follow the issue template
|
||||
|
||||
Developers need to be able to reproduce the faulty behavior in order to fix an issue, so please make sure that you follow the instructions in the issue template carefully.
|
||||
|
||||
### Follow the issue template
|
||||
## Pull requests
|
||||
|
||||
Developers need to be able to reproduce the faulty behavior in order to fix an
|
||||
issue, so please make sure that you follow the instructions in the issue
|
||||
template carefully.
|
||||
### Discuss your changes first
|
||||
|
||||
Please discuss your changes by opening an issue. The maintainers should evaluate your proposal, and it’s generally better if that’s done before any code is written.
|
||||
|
||||
### Review your changes for style
|
||||
|
||||
## Pull requests
|
||||
|
||||
### Discuss your changes first
|
||||
|
||||
Please discuss your changes by opening an issue. The maintainers should
|
||||
evaluate your proposal, and it's generally better if that's done before any code
|
||||
is written.
|
||||
|
||||
|
||||
|
||||
### Review your changes for style
|
||||
|
||||
We have a set of [code guidelines][hacking] that we expect the code to follow.
|
||||
Please make sure you follow it.
|
||||
We have a set of [code guidelines][hacking] that we expect the code to follow. Please make sure you follow it.
|
||||
|
||||
[hacking]: https://github.com/AdguardTeam/CodeGuidelines/blob/master/Go/Go.md
|
||||
|
||||
### Test your changes
|
||||
|
||||
Make sure that it passes linters and tests by running the corresponding Make targets. For backend changes, it’s `make go-check`. For frontend, run `make js-lint`.
|
||||
|
||||
### Test your changes
|
||||
|
||||
Make sure that it passes linters and tests by running the corresponding Make
|
||||
targets. For backend changes, it's `make go-check`. For frontend, run
|
||||
`make js-lint`.
|
||||
|
||||
Additionally, a manual test is often required. While we're constantly working
|
||||
on improving our test suites, they're still not as good as we'd like them to be.
|
||||
Additionally, a manual test is often required. While we’re constantly working on improving our test suites, they’re still not as good as we’d like them to be.
|
||||
|
||||
53
HACKING.md
53
HACKING.md
@@ -1,65 +1,56 @@
|
||||
# AdGuard Home Developer Guidelines
|
||||
# AdGuard Home developer guidelines
|
||||
|
||||
This document was moved to the [AdGuard Code Guidelines repository][repo]. All
|
||||
sections with IDs now only have links to the corresponding files and sections in
|
||||
that repository.
|
||||
This document was moved to the [AdGuard Code Guidelines repository][repo]. All sections with IDs now only have links to the corresponding files and sections in that repository.
|
||||
|
||||
## <a href="#git" id="git" name="git">Git</a>
|
||||
## <a href="#git" id="git" name="git">Git</a>
|
||||
|
||||
This section was moved to [its own document][git].
|
||||
|
||||
## <a href="#go" id="go" name="go">Go</a>
|
||||
## <a href="#go" id="go" name="go">Go</a>
|
||||
|
||||
This section was moved to [its own document][go].
|
||||
|
||||
### <a href="#code" id="code" name="code">Code</a>
|
||||
### <a href="#code" id="code" name="code">Code</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][code] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][code] of the Go guidelines document.
|
||||
|
||||
### <a href="#commenting" id="commenting" name="commenting">Commenting</a>
|
||||
### <a href="#commenting" id="commenting" name="commenting">Commenting</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][cmnt] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][cmnt] of the Go guidelines document.
|
||||
|
||||
### <a href="#formatting" id="formatting" name="formatting">Formatting</a>
|
||||
### <a href="#formatting" id="formatting" name="formatting">Formatting</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][fmt] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][fmt] of the Go guidelines document.
|
||||
|
||||
### <a href="#naming" id="naming" name="naming">Naming</a>
|
||||
### <a href="#naming" id="naming" name="naming">Naming</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][name] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][name] of the Go guidelines document.
|
||||
|
||||
### <a href="#testing" id="testing" name="testing">Testing</a>
|
||||
### <a href="#testing" id="testing" name="testing">Testing</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][test] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][test] of the Go guidelines document.
|
||||
|
||||
### <a href="#recommended-reading" id="recommended-reading" name="recommended-reading">Recommended Reading</a>
|
||||
### <a href="#recommended-reading" id="recommended-reading" name="recommended-reading">Recommended Reading</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][read] of the Go
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][read] of the Go guidelines document.
|
||||
|
||||
## <a href="#markdown" id="markdown" name="markdown">Markdown</a>
|
||||
## <a href="#markdown" id="markdown" name="markdown">Markdown</a>
|
||||
|
||||
This section was moved to [its own document][md].
|
||||
|
||||
## <a href="#shell-scripting" id="shell-scripting" name="shell-scripting">Shell Scripting</a>
|
||||
## <a href="#shell-scripting" id="shell-scripting" name="shell-scripting">Shell Scripting</a>
|
||||
|
||||
This section was moved to [its own document][sh].
|
||||
|
||||
### <a href="#shell-conditionals" id="shell-conditionals" name="shell-conditionals">Shell Conditionals</a>
|
||||
### <a href="#shell-conditionals" id="shell-conditionals" name="shell-conditionals">Shell Conditionals</a>
|
||||
|
||||
This subsection was moved to the [corresponding section][cond] of the Shell
|
||||
guidelines document.
|
||||
This subsection was moved to the [corresponding section][cond] of the Shell guidelines document.
|
||||
|
||||
## <a href="#text-including-comments" id="text-including-comments" name="text-including-comments">Text, Including Comments</a>
|
||||
## <a href="#text-including-comments" id="text-including-comments" name="text-including-comments">Text, Including Comments</a>
|
||||
|
||||
This section was moved to [its own document][txt].
|
||||
|
||||
## <a href="#yaml" id="yaml" name="yaml">YAML</a>
|
||||
## <a href="#yaml" id="yaml" name="yaml">YAML</a>
|
||||
|
||||
This section was moved to [its own document][yaml].
|
||||
|
||||
|
||||
17
Makefile
17
Makefile
@@ -27,7 +27,7 @@ DIST_DIR = dist
|
||||
GOAMD64 = v1
|
||||
GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
|
||||
GOSUMDB = sum.golang.google.cn
|
||||
GOTOOLCHAIN = go1.21.8
|
||||
GOTOOLCHAIN = go1.22.3
|
||||
GPG_KEY = devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE = not-a-real-password
|
||||
NPM = npm
|
||||
@@ -82,8 +82,6 @@ build: deps quick-build
|
||||
|
||||
quick-build: js-build go-build
|
||||
|
||||
ci: deps test go-bench go-fuzz
|
||||
|
||||
deps: js-deps go-deps
|
||||
lint: js-lint go-lint
|
||||
test: js-test go-test
|
||||
@@ -98,15 +96,10 @@ build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT))
|
||||
clean: ; $(ENV) "$(SHELL)" ./scripts/make/clean.sh
|
||||
init: ; git config core.hooksPath ./scripts/hooks
|
||||
|
||||
js-build:
|
||||
$(NPM) $(NPM_FLAGS) run build-prod
|
||||
js-deps:
|
||||
$(NPM) $(NPM_INSTALL_FLAGS) ci
|
||||
|
||||
# TODO(a.garipov): Remove the legacy client tasks support once the new
|
||||
# client is done and the old one is removed.
|
||||
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
|
||||
js-test: ; $(NPM) $(NPM_FLAGS) run test
|
||||
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
|
||||
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
|
||||
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
|
||||
js-test: ; $(NPM) $(NPM_FLAGS) run test
|
||||
|
||||
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
|
||||
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
|
||||
|
||||
473
README.md
473
README.md
@@ -1,85 +1,75 @@
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="doc/adguard_home_darkmode.svg">
|
||||
<img alt="AdGuard Home" src="doc/adguard_home_lightmode.svg" width="300px">
|
||||
</picture>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="doc/adguard_home_darkmode.svg">
|
||||
<img alt="AdGuard Home" src="doc/adguard_home_lightmode.svg" width="300px">
|
||||
</picture>
|
||||
</p>
|
||||
<h3 align="center">Privacy protection center for you and your devices</h3>
|
||||
<p align="center">
|
||||
Free and open source, powerful network-wide ads & trackers blocking DNS
|
||||
server.
|
||||
Free and open source, powerful network-wide ads & trackers blocking DNS server.
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://adguard.com/">AdGuard.com</a> |
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
|
||||
<a href="https://reddit.com/r/Adguard">Reddit</a> |
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a> |
|
||||
<a href="https://t.me/adguard_en">Telegram</a>
|
||||
<br/><br/>
|
||||
<a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
|
||||
<img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage"/>
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
|
||||
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800"/>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release"/>
|
||||
</a>
|
||||
<a href="https://snapcraft.io/adguard-home">
|
||||
<img alt="adguard-home" src="https://snapcraft.io/adguard-home/badge.svg"/>
|
||||
</a>
|
||||
<a href="https://adguard.com/">AdGuard.com</a> |
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
|
||||
<a href="https://reddit.com/r/Adguard">Reddit</a> |
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a> |
|
||||
<a href="https://t.me/adguard_en">Telegram</a>
|
||||
<br/><br/>
|
||||
<a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
|
||||
<img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage"/>
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
|
||||
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800"/>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release"/>
|
||||
</a>
|
||||
<a href="https://snapcraft.io/adguard-home">
|
||||
<img alt="adguard-home" src="https://snapcraft.io/adguard-home/badge.svg"/>
|
||||
</a>
|
||||
</p>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.gif" width="800"/>
|
||||
<img src="https://cdn.adtidy.org/public/Adguard/Common/adguard_home.gif" width="800"/>
|
||||
</p>
|
||||
<hr/>
|
||||
|
||||
AdGuard Home is a network-wide software for blocking ads and tracking. After you
|
||||
set it up, it'll cover ALL your home devices, and you don't need any client-side
|
||||
software for that.
|
||||
AdGuard Home is a network-wide software for blocking ads and tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that.
|
||||
|
||||
It operates as a DNS server that re-routes tracking domains to a “black hole”,
|
||||
thus preventing your devices from connecting to those servers. It's based on
|
||||
software we use for our public [AdGuard DNS] servers, and both share a lot of
|
||||
code.
|
||||
It operates as a DNS server that re-routes tracking domains to a “black hole”, thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS] servers, and both share a lot of code.
|
||||
|
||||
[AdGuard DNS]: https://adguard-dns.io/
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)](#automated-install-linux-and-mac)
|
||||
- [Alternative methods](#alternative-methods)
|
||||
- [Guides](#guides)
|
||||
- [API](#api)
|
||||
- [Comparing AdGuard Home to other solutions](#comparison)
|
||||
- [How is this different from public AdGuard DNS servers?](#comparison-adguard-dns)
|
||||
- [How does AdGuard Home compare to Pi-Hole](#comparison-pi-hole)
|
||||
- [How does AdGuard Home compare to traditional ad blockers](#comparison-adblock)
|
||||
- [Known limitations](#comparison-limitations)
|
||||
- [How to build from source](#how-to-build)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Building](#building)
|
||||
- [Contributing](#contributing)
|
||||
- [Test unstable versions](#test-unstable-versions)
|
||||
- [Reporting issues](#reporting-issues)
|
||||
- [Help with translations](#translate)
|
||||
- [Other](#help-other)
|
||||
- [Projects that use AdGuard Home](#uses)
|
||||
- [Acknowledgments](#acknowledgments)
|
||||
- [Privacy](#privacy)
|
||||
|
||||
## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a>
|
||||
|
||||
* [Getting Started](#getting-started)
|
||||
* [Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)](#automated-install-linux-and-mac)
|
||||
* [Alternative methods](#alternative-methods)
|
||||
* [Guides](#guides)
|
||||
* [API](#api)
|
||||
* [Comparing AdGuard Home to other solutions](#comparison)
|
||||
* [How is this different from public AdGuard DNS servers?](#comparison-adguard-dns)
|
||||
* [How does AdGuard Home compare to Pi-Hole](#comparison-pi-hole)
|
||||
* [How does AdGuard Home compare to traditional ad blockers](#comparison-adblock)
|
||||
* [Known limitations](#comparison-limitations)
|
||||
* [How to build from source](#how-to-build)
|
||||
* [Prerequisites](#prerequisites)
|
||||
* [Building](#building)
|
||||
* [Contributing](#contributing)
|
||||
* [Test unstable versions](#test-unstable-versions)
|
||||
* [Reporting issues](#reporting-issues)
|
||||
* [Help with translations](#translate)
|
||||
* [Other](#help-other)
|
||||
* [Projects that use AdGuard Home](#uses)
|
||||
* [Acknowledgments](#acknowledgments)
|
||||
* [Privacy](#privacy)
|
||||
|
||||
|
||||
|
||||
## <a href="#getting-started" id="getting-started" name="getting-started">Getting Started</a>
|
||||
|
||||
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)</a>
|
||||
### <a href="#automated-install-linux-and-mac" id="automated-install-linux-and-mac" name="automated-install-linux-and-mac">Automated install (Linux/Unix/MacOS/FreeBSD/OpenBSD)</a>
|
||||
|
||||
To install with `curl` run the following command:
|
||||
|
||||
@@ -101,95 +91,70 @@ fetch -o - https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scri
|
||||
|
||||
The script also accepts some options:
|
||||
|
||||
* `-c <channel>` to use specified channel;
|
||||
* `-r` to reinstall AdGuard Home;
|
||||
* `-u` to uninstall AdGuard Home;
|
||||
* `-v` for verbose output.
|
||||
- `-c <channel>` to use specified channel;
|
||||
- `-r` to reinstall AdGuard Home;
|
||||
- `-u` to uninstall AdGuard Home;
|
||||
- `-v` for verbose output.
|
||||
|
||||
Note that options `-r` and `-u` are mutually exclusive.
|
||||
|
||||
### <a href="#alternative-methods" id="alternative-methods" name="alternative-methods">Alternative methods</a>
|
||||
|
||||
#### <a href="#manual-installation" id="manual-installation" name="manual-installation">Manual installation</a>
|
||||
|
||||
### <a href="#alternative-methods" id="alternative-methods" name="alternative-methods">Alternative methods</a>
|
||||
Please read the **[Getting Started][wiki-start]** article on our Wiki to learn how to install AdGuard Home manually, and how to configure your devices to use it.
|
||||
|
||||
#### <a href="#manual-installation" id="manual-installation" name="manual-installation">Manual installation</a>
|
||||
|
||||
Please read the **[Getting Started][wiki-start]** article on our Wiki to learn
|
||||
how to install AdGuard Home manually, and how to configure your devices to use
|
||||
it.
|
||||
|
||||
#### <a href="#docker" id="docker" name="docker">Docker</a>
|
||||
#### <a href="#docker" id="docker" name="docker">Docker</a>
|
||||
|
||||
You can use our official Docker image on [Docker Hub].
|
||||
|
||||
#### <a href="#snap-store" id="snap-store" name="snap-store">Snap Store</a>
|
||||
#### <a href="#snap-store" id="snap-store" name="snap-store">Snap Store</a>
|
||||
|
||||
If you're running **Linux,** there's a secure and easy way to install AdGuard
|
||||
Home: get it from the [Snap Store].
|
||||
If you're running **Linux,** there's a secure and easy way to install AdGuard Home: get it from the [Snap Store].
|
||||
|
||||
[Docker Hub]: https://hub.docker.com/r/adguard/adguardhome
|
||||
[Snap Store]: https://snapcraft.io/adguard-home
|
||||
[wiki-start]: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started
|
||||
|
||||
|
||||
|
||||
### <a href="#guides" id="guides" name="guides">Guides</a>
|
||||
### <a href="#guides" id="guides" name="guides">Guides</a>
|
||||
|
||||
See our [Wiki][wiki].
|
||||
|
||||
[wiki]: https://github.com/AdguardTeam/AdGuardHome/wiki
|
||||
|
||||
### <a href="#api" id="api" name="api">API</a>
|
||||
|
||||
|
||||
### <a href="#api" id="api" name="api">API</a>
|
||||
|
||||
If you want to integrate with AdGuard Home, you can use our [REST API][openapi].
|
||||
Alternatively, you can use this [python client][pyclient], which is used to
|
||||
build the [AdGuard Home Hass.io Add-on][hassio].
|
||||
If you want to integrate with AdGuard Home, you can use our [REST API][openapi]. Alternatively, you can use this [python client][pyclient], which is used to build the [AdGuard Home Hass.io Add-on][hassio].
|
||||
|
||||
[hassio]: https://www.home-assistant.io/integrations/adguard/
|
||||
[openapi]: https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi
|
||||
[pyclient]: https://pypi.org/project/adguardhome/
|
||||
|
||||
## <a href="#comparison" id="comparison" name="comparison">Comparing AdGuard Home to other solutions</a>
|
||||
|
||||
### <a href="#comparison-adguard-dns" id="comparison-adguard-dns" name="comparison-adguard-dns">How is this different from public AdGuard DNS servers?</a>
|
||||
|
||||
## <a href="#comparison" id="comparison" name="comparison">Comparing AdGuard Home to other solutions</a>
|
||||
Running your own AdGuard Home server allows you to do much more than using a public DNS server. It's a completely different level. See for yourself:
|
||||
|
||||
### <a href="#comparison-adguard-dns" id="comparison-adguard-dns" name="comparison-adguard-dns">How is this different from public AdGuard DNS servers?</a>
|
||||
- Choose what exactly the server blocks and permits.
|
||||
|
||||
Running your own AdGuard Home server allows you to do much more than using a
|
||||
public DNS server. It's a completely different level. See for yourself:
|
||||
- Monitor your network activity.
|
||||
|
||||
* Choose what exactly the server blocks and permits.
|
||||
- Add your own custom filtering rules.
|
||||
|
||||
* Monitor your network activity.
|
||||
- **Most importantly, it's your own server, and you are the only one who's in control.**
|
||||
|
||||
* Add your own custom filtering rules.
|
||||
### <a href="#comparison-pi-hole" id="comparison-pi-hole" name="comparison-pi-hole">How does AdGuard Home compare to Pi-Hole</a>
|
||||
|
||||
* **Most importantly, it's your own server, and you are the only one who's in
|
||||
control.**
|
||||
At this point, AdGuard Home has a lot in common with Pi-Hole. Both block ads and trackers using the so-called “DNS sinkholing” method and both allow customizing what's blocked.
|
||||
|
||||
> [!NOTE]
|
||||
> We're not going to stop here. DNS sinkholing is not a bad starting point, but this is just the beginning.
|
||||
|
||||
AdGuard Home provides a lot of features out-of-the-box with no need to install and configure additional software. We want it to be simple to the point when even casual users can set it up with minimal effort.
|
||||
|
||||
### <a href="#comparison-pi-hole" id="comparison-pi-hole" name="comparison-pi-hole">How does AdGuard Home compare to Pi-Hole</a>
|
||||
|
||||
At this point, AdGuard Home has a lot in common with Pi-Hole. Both block ads
|
||||
and trackers using the so-called “DNS sinkholing” method and both allow
|
||||
customizing what's blocked.
|
||||
|
||||
<aside>
|
||||
We're not going to stop here. DNS sinkholing is not a bad starting point, but
|
||||
this is just the beginning.
|
||||
</aside>
|
||||
|
||||
AdGuard Home provides a lot of features out-of-the-box with no need to install
|
||||
and configure additional software. We want it to be simple to the point when
|
||||
even casual users can set it up with minimal effort.
|
||||
|
||||
**Disclaimer:** some of the listed features can be added to Pi-Hole by
|
||||
installing additional software or by manually using SSH terminal and
|
||||
reconfiguring one of the utilities Pi-Hole consists of. However, in our
|
||||
opinion, this cannot be legitimately counted as a Pi-Hole's feature.
|
||||
> [!NOTE]
|
||||
> Some of the listed features can be added to Pi-Hole by installing additional software or by manually using SSH terminal and reconfiguring one of the utilities Pi-Hole consists of. However, in our opinion, this cannot be legitimately counted as a Pi-Hole's feature.
|
||||
|
||||
| Feature | AdGuard Home | Pi-Hole |
|
||||
|-------------------------------------------------------------------------|-------------------|-----------------------------------------------------------|
|
||||
@@ -207,68 +172,45 @@ opinion, this cannot be legitimately counted as a Pi-Hole's feature.
|
||||
| Access settings (choose who can use AGH DNS) | ✅ | ❌ |
|
||||
| Running [without root privileges][wiki-noroot] | ✅ | ❌ |
|
||||
|
||||
[wiki-noroot]: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser
|
||||
[wiki-noroot]: https://adguard-dns.io/kb/adguard-home/getting-started/#running-without-superuser
|
||||
|
||||
|
||||
|
||||
### <a href="#comparison-adblock" id="comparison-adblock" name="comparison-adblock">How does AdGuard Home compare to traditional ad blockers</a>
|
||||
### <a href="#comparison-adblock" id="comparison-adblock" name="comparison-adblock">How does AdGuard Home compare to traditional ad blockers</a>
|
||||
|
||||
It depends.
|
||||
|
||||
DNS sinkholing is capable of blocking a big percentage of ads, but it lacks
|
||||
the flexibility and the power of traditional ad blockers. You can get a good
|
||||
impression about the difference between these methods by reading [this
|
||||
article][blog-adaway], which compares AdGuard for Android (a traditional ad
|
||||
blocker) to hosts-level ad blockers (which are almost identical to DNS-based
|
||||
blockers in their capabilities). This level of protection is enough for some
|
||||
users.
|
||||
DNS sinkholing is capable of blocking a big percentage of ads, but it lacks the flexibility and the power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article][blog-adaway], which compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities). This level of protection is enough for some users.
|
||||
|
||||
Additionally, using a DNS-based blocker can help to block ads, tracking and
|
||||
analytics requests on other types of devices, such as SmartTVs, smart speakers
|
||||
or other kinds of IoT devices (on which you can't install traditional ad
|
||||
blockers).
|
||||
Additionally, using a DNS-based blocker can help to block ads, tracking and analytics requests on other types of devices, such as SmartTVs, smart speakers or other kinds of IoT devices (on which you can't install traditional ad blockers).
|
||||
|
||||
|
||||
|
||||
### <a href="#comparison-limitations" id="comparison-limitations" name="comparison-limitations">Known limitations</a>
|
||||
### <a href="#comparison-limitations" id="comparison-limitations" name="comparison-limitations">Known limitations</a>
|
||||
|
||||
Here are some examples of what cannot be blocked by a DNS-level blocker:
|
||||
|
||||
* YouTube, Twitch ads;
|
||||
- YouTube, Twitch ads;
|
||||
|
||||
* Facebook, Twitter, Instagram sponsored posts.
|
||||
- Facebook, Twitter, Instagram sponsored posts.
|
||||
|
||||
Essentially, any advertising that shares a domain with content cannot be blocked
|
||||
by a DNS-level blocker.
|
||||
Essentially, any advertising that shares a domain with content cannot be blocked by a DNS-level blocker.
|
||||
|
||||
Is there a chance to handle this in the future? DNS will never be enough to do
|
||||
this. Our only option is to use a content blocking proxy like what we do in the
|
||||
standalone AdGuard applications. We're [going to bring][issue-1228] this
|
||||
feature support to AdGuard Home in the future. Unfortunately, even in this
|
||||
case, there still will be cases when this won't be enough or would require quite
|
||||
a complicated configuration.
|
||||
Is there a chance to handle this in the future? DNS will never be enough to do this. Our only option is to use a content blocking proxy like what we do in the standalone AdGuard applications. We're [going to bring][issue-1228] this feature support to AdGuard Home in the future. Unfortunately, even in this case, there still will be cases when this won't be enough or would require quite a complicated configuration.
|
||||
|
||||
[blog-adaway]: https://adguard.com/blog/adguard-vs-adaway-dns66.html
|
||||
[issue-1228]: https://github.com/AdguardTeam/AdGuardHome/issues/1228
|
||||
|
||||
## <a href="#how-to-build" id="how-to-build" name="how-to-build">How to build from source</a>
|
||||
|
||||
|
||||
## <a href="#how-to-build" id="how-to-build" name="how-to-build">How to build from source</a>
|
||||
|
||||
### <a href="#prerequisites" id="prerequisites" name="prerequisites">Prerequisites</a>
|
||||
### <a href="#prerequisites" id="prerequisites" name="prerequisites">Prerequisites</a>
|
||||
|
||||
Run `make init` to prepare the development environment.
|
||||
|
||||
You will need this to build AdGuard Home:
|
||||
|
||||
* [Go](https://golang.org/dl/) v1.20 or later;
|
||||
* [Node.js](https://nodejs.org/en/download/) v16 or later;
|
||||
* [npm](https://www.npmjs.com/) v8 or later;
|
||||
* [yarn](https://yarnpkg.com/) v1.22.5 or later.
|
||||
- [Go](https://golang.org/dl/) v1.22 or later;
|
||||
- [Node.js](https://nodejs.org/en/download/) v16 or later;
|
||||
- [npm](https://www.npmjs.com/) v8 or later;
|
||||
- [yarn](https://yarnpkg.com/) v1.22.5 or later.
|
||||
|
||||
|
||||
|
||||
### <a href="#building" id="building" name="building">Building</a>
|
||||
### <a href="#building" id="building" name="building">Building</a>
|
||||
|
||||
Open your terminal and execute these commands:
|
||||
|
||||
@@ -278,27 +220,22 @@ cd AdGuardHome
|
||||
make
|
||||
```
|
||||
|
||||
#### <a href="#building-node" id="building-node" name="building-node">Building with Node.js 17 and later</a>
|
||||
#### <a href="#building-node" id="building-node" name="building-node">Building with Node.js 17 and later</a>
|
||||
|
||||
In order to build AdGuard Home with Node.js 17 and later, specify
|
||||
`--openssl-legacy-provider` option.
|
||||
In order to build AdGuard Home with Node.js 17 and later, specify `--openssl-legacy-provider` option.
|
||||
|
||||
```sh
|
||||
export NODE_OPTIONS=--openssl-legacy-provider
|
||||
```
|
||||
|
||||
**NOTE:** The non-standard `-j` flag is currently not supported, so building
|
||||
with `make -j 4` or setting your `MAKEFLAGS` to include, for example, `-j 4` is
|
||||
likely to break the build. If you do have your `MAKEFLAGS` set to that, and you
|
||||
don't want to change it, you can override it by running `make -j 1`.
|
||||
> [!WARNING]
|
||||
> The non-standard `-j` flag is currently not supported, so building with `make -j 4` or setting your `MAKEFLAGS` to include, for example, `-j 4` is likely to break the build. If you do have your `MAKEFLAGS` set to that, and you don't want to change it, you can override it by running `make -j 1`.
|
||||
|
||||
Check the [`Makefile`][src-makefile] to learn about other commands.
|
||||
|
||||
#### <a href="#building-cross" id="building-cross" name="building-cross">Building for a different platform</a>
|
||||
#### <a href="#building-cross" id="building-cross" name="building-cross">Building for a different platform</a>
|
||||
|
||||
You can build AdGuard Home for any OS/ARCH that Go supports. In order to do
|
||||
this, specify `GOOS` and `GOARCH` environment variables as macros when running
|
||||
`make`.
|
||||
You can build AdGuard Home for any OS/ARCH that Go supports. In order to do this, specify `GOOS` and `GOARCH` environment variables as macros when running `make`.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -312,10 +249,9 @@ or:
|
||||
make GOOS='linux' GOARCH='arm64'
|
||||
```
|
||||
|
||||
#### <a href="#preparing-releases" id="preparing-releases" name="preparing-releases">Preparing releases</a>
|
||||
#### <a href="#preparing-releases" id="preparing-releases" name="preparing-releases">Preparing releases</a>
|
||||
|
||||
You'll need [`snapcraft`] to prepare a release build. Once installed, run the
|
||||
following command:
|
||||
You'll need [`snapcraft`] to prepare a release build. Once installed, run the following command:
|
||||
|
||||
```sh
|
||||
make build-release CHANNEL='...' VERSION='...'
|
||||
@@ -323,47 +259,39 @@ make build-release CHANNEL='...' VERSION='...'
|
||||
|
||||
See the [`build-release` target documentation][targ-release].
|
||||
|
||||
#### <a href="#docker-image" id="docker-image" name="docker-image">Docker image</a>
|
||||
#### <a href="#docker-image" id="docker-image" name="docker-image">Docker image</a>
|
||||
|
||||
Run `make build-docker` to build the Docker image locally (the one that we
|
||||
publish to DockerHub). Please note, that we're using [Docker Buildx][buildx] to
|
||||
build our official image.
|
||||
Run `make build-docker` to build the Docker image locally (the one that we publish to DockerHub). Please note, that we're using [Docker Buildx][buildx] to build our official image.
|
||||
|
||||
You may need to prepare before using these builds:
|
||||
|
||||
* (Linux-only) Install Qemu:
|
||||
- (Linux-only) Install Qemu:
|
||||
|
||||
```sh
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes
|
||||
```
|
||||
```sh
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes
|
||||
```
|
||||
|
||||
* Prepare the builder:
|
||||
- Prepare the builder:
|
||||
|
||||
```sh
|
||||
docker buildx create --name buildx-builder --driver docker-container --use
|
||||
```
|
||||
```sh
|
||||
docker buildx create --name buildx-builder --driver docker-container --use
|
||||
```
|
||||
|
||||
See the [`build-docker` target documentation][targ-docker].
|
||||
|
||||
#### <a href="#debugging-the-frontend" id="debugging-the-frontend" name="debugging-the-frontend">Debugging the frontend</a>
|
||||
#### <a href="#debugging-the-frontend" id="debugging-the-frontend" name="debugging-the-frontend">Debugging the frontend</a>
|
||||
|
||||
When you need to debug the frontend without recompiling the production version
|
||||
every time, for example to check how your labels would look on a form, you can
|
||||
run the frontend build a development environment.
|
||||
When you need to debug the frontend without recompiling the production version every time, for example to check how your labels would look on a form, you can run the frontend build a development environment.
|
||||
|
||||
1. In a separate terminal, run:
|
||||
1. In a separate terminal, run:
|
||||
|
||||
```sh
|
||||
( cd ./client/ && env NODE_ENV='development' npm run watch )
|
||||
```
|
||||
```sh
|
||||
( cd ./client/ && env NODE_ENV='development' npm run watch )
|
||||
```
|
||||
|
||||
2. Run your `AdGuardHome` binary with the `--local-frontend` flag, which
|
||||
instructs AdGuard Home to ignore the built-in frontend files and use those
|
||||
from the `./build/` directory.
|
||||
2. Run your `AdGuardHome` binary with the `--local-frontend` flag, which instructs AdGuard Home to ignore the built-in frontend files and use those from the `./build/` directory.
|
||||
|
||||
3. Now any changes you make in the `./client/` directory should be recompiled
|
||||
and become available on the web UI. Make sure that you disable the browser
|
||||
cache to make sure that you actually get the recompiled version.
|
||||
3. Now any changes you make in the `./client/` directory should be recompiled and become available on the web UI. Make sure that you disable the browser cache to make sure that you actually get the recompiled version.
|
||||
|
||||
[`snapcraft`]: https://snapcraft.io/
|
||||
[buildx]: https://docs.docker.com/buildx/working-with-buildx/
|
||||
@@ -371,167 +299,120 @@ run the frontend build a development environment.
|
||||
[targ-docker]: https://github.com/AdguardTeam/AdGuardHome/tree/master/scripts#build-dockersh-build-a-multi-architecture-docker-image
|
||||
[targ-release]: https://github.com/AdguardTeam/AdGuardHome/tree/master/scripts#build-releasesh-build-a-release-for-all-platforms
|
||||
|
||||
|
||||
|
||||
## <a href="#contributing" id="contributing" name="contributing">Contributing</a>
|
||||
|
||||
You are welcome to fork this repository, make your changes and [submit a pull
|
||||
request][pr]. Please make sure you follow our [code guidelines][guide] though.
|
||||
You are welcome to fork this repository, make your changes and [submit a pull request][pr]. Please make sure you follow our [code guidelines][guide] though.
|
||||
|
||||
Please note that we don't expect people to contribute to both UI and backend
|
||||
parts of the program simultaneously. Ideally, the backend part is implemented
|
||||
first, i.e. configuration, API, and the functionality itself. The UI part can
|
||||
be implemented later in a different pull request by a different person.
|
||||
Please note that we don't expect people to contribute to both UI and backend parts of the program simultaneously. Ideally, the backend part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
|
||||
|
||||
[guide]: https://github.com/AdguardTeam/CodeGuidelines/
|
||||
[pr]: https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||
|
||||
|
||||
|
||||
### <a href="#test-unstable-versions" id="test-unstable-versions" name="test-unstable-versions">Test unstable versions</a>
|
||||
### <a href="#test-unstable-versions" id="test-unstable-versions" name="test-unstable-versions">Test unstable versions</a>
|
||||
|
||||
There are two update channels that you can use:
|
||||
|
||||
* `beta`: beta versions of AdGuard Home. More or less stable versions,
|
||||
usually released every two weeks or more often.
|
||||
- `beta`: beta versions of AdGuard Home. More or less stable versions, usually released every two weeks or more often.
|
||||
|
||||
* `edge`: the newest version of AdGuard Home from the development branch. New
|
||||
updates are pushed to this channel daily.
|
||||
- `edge`: the newest version of AdGuard Home from the development branch. New updates are pushed to this channel daily.
|
||||
|
||||
There are three options how you can install an unstable version:
|
||||
|
||||
1. [Snap Store]: look for the `beta` and `edge` channels.
|
||||
1. [Snap Store]: look for the `beta` and `edge` channels.
|
||||
|
||||
2. [Docker Hub]: look for the `beta` and `edge` tags.
|
||||
2. [Docker Hub]: look for the `beta` and `edge` tags.
|
||||
|
||||
3. Standalone builds. Use the automated installation script or look for the
|
||||
available builds [on the Wiki][wiki-platf].
|
||||
3. Standalone builds. Use the automated installation script or look for the available builds [on the Wiki][wiki-platf].
|
||||
|
||||
Script to install a beta version:
|
||||
Script to install a beta version:
|
||||
|
||||
```sh
|
||||
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c beta
|
||||
```
|
||||
```sh
|
||||
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c beta
|
||||
```
|
||||
|
||||
Script to install an edge version:
|
||||
Script to install an edge version:
|
||||
|
||||
```sh
|
||||
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c edge
|
||||
```
|
||||
```sh
|
||||
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -c edge
|
||||
```
|
||||
|
||||
[wiki-platf]: https://github.com/AdguardTeam/AdGuardHome/wiki/Platforms
|
||||
|
||||
### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a>
|
||||
|
||||
|
||||
### <a href="#reporting-issues" id="reporting-issues" name="reporting-issues">Report issues</a>
|
||||
|
||||
If you run into any problem or have a suggestion, head to [this page][iss] and
|
||||
click on the “New issue” button. Please follow the instructions in the issue
|
||||
form carefully and don't forget to start by searching for duplicates.
|
||||
If you run into any problem or have a suggestion, head to [this page][iss] and click on the “New issue” button. Please follow the instructions in the issue form carefully and don't forget to start by searching for duplicates.
|
||||
|
||||
[iss]: https://github.com/AdguardTeam/AdGuardHome/issues
|
||||
|
||||
### <a href="#translate" id="translate" name="translate">Help with translations</a>
|
||||
|
||||
|
||||
### <a href="#translate" id="translate" name="translate">Help with translations</a>
|
||||
|
||||
If you want to help with AdGuard Home translations, please learn more about
|
||||
translating AdGuard products [in our Knowledge Base][kb-trans]. You can
|
||||
contribute to the [AdGuardHome project on CrowdIn][crowdin].
|
||||
If you want to help with AdGuard Home translations, please learn more about translating AdGuard products [in our Knowledge Base][kb-trans]. You can contribute to the [AdGuardHome project on CrowdIn][crowdin].
|
||||
|
||||
[crowdin]: https://crowdin.com/project/adguard-applications/en#/adguard-home
|
||||
[kb-trans]: https://kb.adguard.com/en/general/adguard-translations
|
||||
|
||||
### <a href="#help-other" id="help-other" name="help-other">Other</a>
|
||||
|
||||
|
||||
### <a href="#help-other" id="help-other" name="help-other">Other</a>
|
||||
|
||||
Another way you can contribute is by [looking for issues][iss-help] marked as
|
||||
`help wanted`, asking if the issue is up for grabs, and sending a PR fixing the
|
||||
bug or implementing the feature.
|
||||
Another way you can contribute is by [looking for issues][iss-help] marked as `help wanted`, asking if the issue is up for grabs, and sending a PR fixing the bug or implementing the feature.
|
||||
|
||||
[iss-help]: https://github.com/AdguardTeam/AdGuardHome/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
|
||||
|
||||
|
||||
|
||||
## <a href="#uses" id="uses" name="uses">Projects that use AdGuard Home</a>
|
||||
|
||||
<!--
|
||||
TODO(a.garipov): Use reference links.
|
||||
-->
|
||||
Please note that these projects are not affiliated with AdGuard, but are made by third-party developers and fans.
|
||||
|
||||
* [AdGuard Home Remote](https://apps.apple.com/app/apple-store/id1543143740):
|
||||
iOS app by [Joost](https://rocketscience-it.nl/).
|
||||
- [AdGuard Home Remote](https://apps.apple.com/app/apple-store/id1543143740): iOS app by [Joost](https://rocketscience-it.nl/).
|
||||
|
||||
* [Python library](https://github.com/frenck/python-adguardhome) by
|
||||
[@frenck](https://github.com/frenck).
|
||||
- [Python library](https://github.com/frenck/python-adguardhome) by [@frenck](https://github.com/frenck).
|
||||
|
||||
* [Home Assistant add-on](https://github.com/hassio-addons/addon-adguard-home)
|
||||
by [@frenck](https://github.com/frenck).
|
||||
- [Home Assistant add-on](https://github.com/hassio-addons/addon-adguard-home) by [@frenck](https://github.com/frenck).
|
||||
|
||||
* [OpenWrt LUCI app](https://github.com/kongfl888/luci-app-adguardhome) by
|
||||
[@kongfl888](https://github.com/kongfl888) (originally by
|
||||
[@rufengsuixing](https://github.com/rufengsuixing)).
|
||||
- [OpenWrt LUCI app](https://github.com/kongfl888/luci-app-adguardhome) by [@kongfl888](https://github.com/kongfl888) (originally by [@rufengsuixing](https://github.com/rufengsuixing)).
|
||||
|
||||
* [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)
|
||||
- [AdGuardHome sync](https://github.com/bakito/adguardhome-sync) by [@bakito](https://github.com/bakito).
|
||||
|
||||
* [AdGuard Home on GLInet
|
||||
routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by
|
||||
[Gl-Inet](https://gl-inet.com/).
|
||||
- [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)
|
||||
|
||||
* [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by
|
||||
[@gramakri](https://github.com/gramakri).
|
||||
- [AdGuard Home on GLInet routers](https://forum.gl-inet.com/t/adguardhome-on-gl-routers/10664) by [Gl-Inet](https://gl-inet.com/).
|
||||
|
||||
* [Asuswrt-Merlin-AdGuardHome-Installer](https://github.com/jumpsmm7/Asuswrt-Merlin-AdGuardHome-Installer)
|
||||
by [@jumpsmm7](https://github.com/jumpsmm7) aka
|
||||
[@SomeWhereOverTheRainBow](https://www.snbforums.com/members/somewhereovertherainbow.64179/).
|
||||
- [Cloudron app](https://git.cloudron.io/cloudron/adguard-home-app) by [@gramakri](https://github.com/gramakri).
|
||||
|
||||
* [Node.js library](https://github.com/Andrea055/AdguardHomeAPI) by
|
||||
[@Andrea055](https://github.com/Andrea055/).
|
||||
- [Asuswrt-Merlin-AdGuardHome-Installer](https://github.com/jumpsmm7/Asuswrt-Merlin-AdGuardHome-Installer) by [@jumpsmm7](https://github.com/jumpsmm7) aka [@SomeWhereOverTheRainBow](https://www.snbforums.com/members/somewhereovertherainbow.64179/).
|
||||
|
||||
* [Browser Extension](https://github.com/satheshshiva/Adguard-Home-Browser-Ext) by
|
||||
[@satheshshiva](https://github.com/satheshshiva/).
|
||||
- [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/).
|
||||
|
||||
- [Zabbix Template for AdGuard Home](https://github.com/diasdmhub/AdGuard_Home_Zabbix_Template) by [@diasdmhub](https://github.com/diasdmhub).
|
||||
|
||||
- [Chocolatey package](https://community.chocolatey.org/packages/adguardhome/) by [niks255](https://community.chocolatey.org/profiles/niks255).
|
||||
|
||||
## <a href="#acknowledgments" id="acknowledgments" name="acknowledgments">Acknowledgments</a>
|
||||
|
||||
<!--
|
||||
TODO(a.garipov): Use reference links.
|
||||
-->
|
||||
|
||||
This software wouldn't have been possible without:
|
||||
|
||||
* [Go](https://golang.org/dl/) and its libraries:
|
||||
* [gcache](https://github.com/bluele/gcache)
|
||||
* [miekg's dns](https://github.com/miekg/dns)
|
||||
* [go-yaml](https://github.com/go-yaml/yaml)
|
||||
* [service](https://godoc.org/github.com/kardianos/service)
|
||||
* [dnsproxy](https://github.com/AdguardTeam/dnsproxy)
|
||||
* [urlfilter](https://github.com/AdguardTeam/urlfilter)
|
||||
* [Node.js](https://nodejs.org/) and its libraries:
|
||||
* And many more Node.js packages.
|
||||
* [React.js](https://reactjs.org)
|
||||
* [Tabler](https://github.com/tabler/tabler)
|
||||
* [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
|
||||
- [Go](https://golang.org/dl/) and its libraries:
|
||||
- [gcache](https://github.com/bluele/gcache)
|
||||
- [miekg's dns](https://github.com/miekg/dns)
|
||||
- [go-yaml](https://github.com/go-yaml/yaml)
|
||||
- [service](https://godoc.org/github.com/kardianos/service)
|
||||
- [dnsproxy](https://github.com/AdguardTeam/dnsproxy)
|
||||
- [urlfilter](https://github.com/AdguardTeam/urlfilter)
|
||||
- [Node.js](https://nodejs.org/) and its libraries:
|
||||
- [React.js](https://reactjs.org)
|
||||
- [Tabler](https://github.com/tabler/tabler)
|
||||
- And many more Node.js packages.
|
||||
- [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
|
||||
|
||||
You might have seen that [CoreDNS] was mentioned here before, but we've stopped
|
||||
using it in AdGuard Home.
|
||||
You might have seen that [CoreDNS] was mentioned here before, but we've stopped using it in AdGuard Home.
|
||||
|
||||
For the full list of all Node.js packages in use, please take a look at
|
||||
[`client/package.json`][src-packagejson] file.
|
||||
For the full list of all Node.js packages in use, please take a look at [`client/package.json`][src-packagejson] file.
|
||||
|
||||
[CoreDNS]: https://coredns.io
|
||||
[src-packagejson]: https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json
|
||||
|
||||
## <a href="#privacy" id="privacy" name="privacy">Privacy</a>
|
||||
|
||||
|
||||
## <a href="#privacy" id="privacy" name="privacy">Privacy</a>
|
||||
|
||||
Our main idea is that you are the one, who should be in control of your data.
|
||||
So it is only natural, that AdGuard Home does not collect any usage statistics,
|
||||
and does not use any web services unless you configure it to do so. See also
|
||||
the [full privacy policy][privacy] with every bit that *could in theory be sent*
|
||||
by AdGuard Home is available.
|
||||
Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. See also the [full privacy policy][privacy] with every bit that *could in theory be sent* by AdGuard Home is available.
|
||||
|
||||
[privacy]: https://adguard.com/en/privacy/home.html
|
||||
|
||||
19
SECURITY.md
19
SECURITY.md
@@ -1,18 +1,13 @@
|
||||
# Security Policy
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
## Reporting vulnerabilities
|
||||
|
||||
Please send your vulnerability reports to <security@adguard.com>. To make sure
|
||||
that your report reaches us, please:
|
||||
Please send your vulnerability reports to <security@adguard.com>. To make sure that your report reaches us, please:
|
||||
|
||||
1. Include the words “AdGuard Home” and “vulnerability” to the subject line as
|
||||
well as a short description of the vulnerability. For example:
|
||||
1. Include the words “AdGuard Home” and “vulnerability” to the subject line as well as a short description of the vulnerability. For example:
|
||||
|
||||
> AdGuard Home API vulnerability: possible XSS attack
|
||||
> AdGuard Home API vulnerability: possible XSS attack
|
||||
|
||||
2. Make sure that the message body contains a clear description of the
|
||||
vulnerability.
|
||||
1. Make sure that the message body contains a clear description of the vulnerability.
|
||||
|
||||
If you have not received a reply to your email within 7 days, please make sure
|
||||
to follow up with us again at <security@adguard.com>. Once again, make sure
|
||||
that the word “vulnerability” is in the subject line.
|
||||
If you have not received a reply to your email within 7 days, please make sure to follow up with us again at <security@adguard.com>. Once again, make sure that the word “vulnerability” is in the subject line.
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
|
||||
'stages':
|
||||
- 'Build frontend':
|
||||
@@ -41,8 +42,13 @@
|
||||
- 'Publish to GitHub Releases'
|
||||
|
||||
'Build frontend':
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome frontend'
|
||||
'pattern': 'build/**'
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'image': '${bamboo.dockerFrontend}'
|
||||
'volumes':
|
||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
||||
'key': 'BF'
|
||||
@@ -59,19 +65,21 @@
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
# Explicitly checkout the revision that we need.
|
||||
git checkout "${bamboo.repository.revision.number}"
|
||||
|
||||
make js-deps js-build
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome frontend'
|
||||
'pattern': 'build/**'
|
||||
'shared': true
|
||||
'required': true
|
||||
make\
|
||||
VERBOSE=1\
|
||||
js-deps js-build
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'Make release':
|
||||
'artifact-subscriptions':
|
||||
- 'artifact': 'AdGuardHome frontend'
|
||||
# TODO(a.garipov): Use more fine-grained artifact rules.
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome dists'
|
||||
'pattern': 'dist/**'
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'volumes':
|
||||
@@ -91,9 +99,6 @@
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
# Explicitly checkout the revision that we need.
|
||||
git checkout "${bamboo.repository.revision.number}"
|
||||
|
||||
# Run the build with the specified channel.
|
||||
echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\
|
||||
| awk '{ gsub(/\\n/, "\n"); print; }'\
|
||||
@@ -106,12 +111,6 @@
|
||||
PARALLELISM=1\
|
||||
VERBOSE=2\
|
||||
build-release
|
||||
# TODO(a.garipov): Use more fine-grained artifact rules.
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome dists'
|
||||
'pattern': 'dist/**'
|
||||
'shared': true
|
||||
'required': true
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
@@ -130,13 +129,6 @@
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
COMMIT="${bamboo.repository.revision.number}"
|
||||
export COMMIT
|
||||
readonly COMMIT
|
||||
|
||||
# Explicitly checkout the revision that we need.
|
||||
git checkout "$COMMIT"
|
||||
|
||||
# Install Qemu, create builder.
|
||||
docker version -f '{{ .Server.Experimental }}'
|
||||
docker buildx rm buildx-builder || :
|
||||
@@ -157,6 +149,7 @@
|
||||
# Prepare and push the build.
|
||||
env\
|
||||
CHANNEL="${bamboo.channel}"\
|
||||
COMMIT="${bamboo.repository.revision.number}"\
|
||||
DIST_DIR='dist'\
|
||||
DOCKER_IMAGE_NAME='adguard/adguardhome'\
|
||||
DOCKER_OUTPUT="type=image,name=adguard/adguardhome,push=true"\
|
||||
@@ -256,7 +249,7 @@
|
||||
'recipients':
|
||||
- 'webhook':
|
||||
'name': 'Build webhook'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa-dns-builds'
|
||||
|
||||
'labels': []
|
||||
'other':
|
||||
@@ -272,7 +265,8 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
# 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 +281,5 @@
|
||||
# are the ones that actually get released.
|
||||
'variables':
|
||||
'channel': 'release'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Make sure to sync any changes with the branch overrides below.
|
||||
'variables':
|
||||
'channel': 'edge'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerSnap': 'adguard/snap-builder:1.1'
|
||||
'snapcraftChannel': 'edge'
|
||||
|
||||
'stages':
|
||||
@@ -53,7 +53,7 @@
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'image': '${bamboo.dockerSnap}'
|
||||
'key': 'DR'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
@@ -99,7 +99,7 @@
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'image': '${bamboo.dockerSnap}'
|
||||
'key': 'BP'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
@@ -127,7 +127,7 @@
|
||||
- 'artifact': 'armhf_snap'
|
||||
- 'artifact': 'arm64_snap'
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'image': '${bamboo.dockerSnap}'
|
||||
'key': 'PTS'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
@@ -175,7 +175,7 @@
|
||||
'recipients':
|
||||
- 'webhook':
|
||||
'name': 'Build webhook'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa'
|
||||
'url': 'http://prod.jirahub.service.eu.consul/v1/webhook/bamboo?channel=adguard-qa-dns-builds'
|
||||
|
||||
'labels': []
|
||||
'other':
|
||||
@@ -191,7 +191,7 @@
|
||||
# need to build a few of these.
|
||||
'variables':
|
||||
'channel': 'beta'
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerSnap': 'adguard/snap-builder:1.1'
|
||||
'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:8.1'
|
||||
'dockerSnap': 'adguard/snap-builder:1.1'
|
||||
'snapcraftChannel': 'candidate'
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
'key': 'AHBRTSPECS'
|
||||
'name': 'AdGuard Home - Build and run tests'
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
'channel': 'development'
|
||||
|
||||
'stages':
|
||||
@@ -13,7 +14,14 @@
|
||||
'manual': false
|
||||
'final': false
|
||||
'jobs':
|
||||
- 'Test'
|
||||
- 'Test frontend'
|
||||
- 'Test backend'
|
||||
|
||||
- 'Frontend':
|
||||
manual: false
|
||||
final: false
|
||||
jobs:
|
||||
- 'Build frontend'
|
||||
|
||||
- 'Artifact':
|
||||
manual: false
|
||||
@@ -21,14 +29,12 @@
|
||||
jobs:
|
||||
- 'Artifact'
|
||||
|
||||
'Test':
|
||||
'Test frontend':
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'image': '${bamboo.dockerFrontend}'
|
||||
'volumes':
|
||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
||||
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
|
||||
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
|
||||
'key': 'TEST'
|
||||
'key': 'JSTEST'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
'tasks':
|
||||
@@ -42,13 +48,91 @@
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
make VERBOSE=1 ci go-tools lint
|
||||
make VERBOSE=1 js-deps js-lint js-test
|
||||
'final-tasks':
|
||||
- 'clean'
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'Test backend':
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'volumes':
|
||||
'${system.GO_CACHE_DIR}': '${bamboo.cacheGo}'
|
||||
'${system.GO_PKG_CACHE_DIR}': '${bamboo.cacheGoPkg}'
|
||||
'key': 'GOTEST'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
'tasks':
|
||||
- 'checkout':
|
||||
'force-clean-build': true
|
||||
- 'script':
|
||||
'interpreter': 'SHELL'
|
||||
'scripts':
|
||||
- |
|
||||
#!/bin/sh
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
make\
|
||||
GOMAXPROCS=1\
|
||||
VERBOSE=1\
|
||||
go-deps go-tools go-lint
|
||||
|
||||
make\
|
||||
VERBOSE=1\
|
||||
go-test
|
||||
'final-tasks':
|
||||
- 'clean'
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'Build frontend':
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome frontend'
|
||||
'pattern': 'build/**'
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerFrontend}'
|
||||
'volumes':
|
||||
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
|
||||
'key': 'BF'
|
||||
'other':
|
||||
'clean-working-dir': true
|
||||
'tasks':
|
||||
- 'checkout':
|
||||
'force-clean-build': true
|
||||
- 'script':
|
||||
'interpreter': 'SHELL'
|
||||
'scripts':
|
||||
- |-
|
||||
#!/bin/sh
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
make\
|
||||
VERBOSE=1\
|
||||
js-deps js-build
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
'Artifact':
|
||||
'artifact-subscriptions':
|
||||
- 'artifact': 'AdGuardHome frontend'
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome_windows_amd64'
|
||||
'pattern': 'dist/AdGuardHome_windows_amd64.zip'
|
||||
'shared': true
|
||||
'required': true
|
||||
- 'name': 'AdGuardHome_darwin_amd64'
|
||||
'pattern': 'dist/AdGuardHome_darwin_amd64.zip'
|
||||
'shared': true
|
||||
'required': true
|
||||
- 'name': 'AdGuardHome_linux_amd64'
|
||||
'pattern': 'dist/AdGuardHome_linux_amd64.tar.gz'
|
||||
'shared': true
|
||||
'required': true
|
||||
'docker':
|
||||
'image': '${bamboo.dockerGo}'
|
||||
'volumes':
|
||||
@@ -68,30 +152,15 @@
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
# Explicitly checkout the revision that we need.
|
||||
git checkout "${bamboo.repository.revision.number}"
|
||||
|
||||
make\
|
||||
ARCH="amd64"\
|
||||
OS="windows darwin linux"\
|
||||
CHANNEL=${bamboo.channel}\
|
||||
SIGN=0\
|
||||
FRONTEND_PREBUILT=1\
|
||||
OS="windows darwin linux"\
|
||||
PARALLELISM=1\
|
||||
SIGN=0\
|
||||
VERBOSE=2\
|
||||
build-release
|
||||
'artifacts':
|
||||
- 'name': 'AdGuardHome_windows_amd64'
|
||||
'pattern': 'dist/AdGuardHome_windows_amd64.zip'
|
||||
'shared': true
|
||||
'required': true
|
||||
- 'name': 'AdGuardHome_darwin_amd64'
|
||||
'pattern': 'dist/AdGuardHome_darwin_amd64.zip'
|
||||
'shared': true
|
||||
'required': true
|
||||
- 'name': 'AdGuardHome_linux_amd64'
|
||||
'pattern': 'dist/AdGuardHome_linux_amd64.tar.gz'
|
||||
'shared': true
|
||||
'required': true
|
||||
'requirements':
|
||||
- 'adg-docker': 'true'
|
||||
|
||||
@@ -122,10 +191,9 @@
|
||||
# from the release branch and are used to build the release candidate
|
||||
# images.
|
||||
- '^rc-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||
# Build betas on release branches manually.
|
||||
'triggers': []
|
||||
# Set the default release channel on the release branch to beta, as we
|
||||
# may need to build a few of these.
|
||||
'variables':
|
||||
'dockerGo': 'adguard/golang-ubuntu:8.1'
|
||||
'dockerFrontend': 'adguard/home-js-builder:1.1'
|
||||
'dockerGo': 'adguard/go-builder:1.22.3--1'
|
||||
'channel': 'candidate'
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
{
|
||||
"client_settings": "إعدادات العميل",
|
||||
"example_upstream_reserved": "يمكنك تحديد <0> DNS upstream لنطاق معين (نطاقات) </0>",
|
||||
"example_upstream_comment": "يمكنك تحديد تعليق",
|
||||
"upstream_parallel": "استخدام الاستعلامات المتوازية لتسريع الحل عن طريق الاستعلام في وقت واحد عن جميع خوادم المنبع",
|
||||
"parallel_requests": "طلبات موازية",
|
||||
"load_balancing": "توزيع الحمل",
|
||||
"example_upstream_reserved": "من المنبع <0>لمجالات محددة</0>;",
|
||||
"example_multiple_upstreams_reserved": "منابع متعددة <0>لمجالات محددة</0>;",
|
||||
"example_upstream_comment": "تعليق.",
|
||||
"upstream_parallel": "استخدم الاستعلامات المتوازية لتسريع عملية الحل عن طريق الاستعلام عن جميع الخوادم المنبع في وقت واحد.",
|
||||
"parallel_requests": "الطلبات الموازية",
|
||||
"load_balancing": "موازنة الأحمال",
|
||||
"load_balancing_desc": "الاستعلام عن خادم واحد في كل مرة سيستخدم AdGuard الرئيسية الخوارزمية العشوائية الموزونة لاختيار الخادم بحيث يتم استخدام أسرع خادم في كثير من الأحيان",
|
||||
"bootstrap_dns": "خوادم Bootstrap DNS",
|
||||
"bootstrap_dns_desc": "عناوين IP لخوادم DNS المستخدمة لحل عناوين IP الخاصة بمحللات DoH/DoT التي تحددها كمصدرين رئيسيين. التعليقات غير مسموح بها.",
|
||||
"fallback_dns_title": "خوادم DNS الاحتياطية",
|
||||
"fallback_dns_desc": "قائمة الخوادم الاحتياطية المستخدمة في حالة عدم الاستجابة من خوادم DNS الرئيسية. تمتلك تلك الخوادم والخوادم الرئيسية نفس الأوامر.",
|
||||
"fallback_dns_placeholder": "أدخل خادم DNS احتياطي واحد لكل سطر",
|
||||
"local_ptr_title": "خوادم DNS العكسية الخاصة",
|
||||
"local_ptr_desc": "خوادم DNS التي يستخدمها AdGuard Home لاستعلامات PTR المحلية. تُستخدم هذه الخوادم لحل أسماء المضيفين للعملاء بعناوين IP خاصة ، على سبيل المثال \"192.168.12.34\" ، باستخدام DNS العكسي. في حالة عدم التعيين ، يستخدم AdGuard Home عناوين محللات DNS الافتراضية لنظام التشغيل الخاص بك باستثناء عناوين AdGuard Home نفسها.",
|
||||
"local_ptr_default_resolver": "بشكل افتراضي ، يستخدم AdGuard Home محللات DNS العكسية التالية: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "لم يتمكن AdGuard Home من تحديد محللات DNS العكسية المناسبة لهذا النظام.",
|
||||
"local_ptr_placeholder": "أدخل عنوان خادم واحد لكل سطر",
|
||||
"local_ptr_placeholder": "أدخل عنوان IP واحد لكل سطر",
|
||||
"resolve_clients_title": "تفعيل التحليل العكسي لعناوين IP للعملاء",
|
||||
"resolve_clients_desc": "حل عكسيًا لعناوين IP للعملاء في أسماء مضيفيهم عن طريق إرسال استعلامات PTR إلى أدوات الحل المقابلة (خوادم DNS الخاصة للعملاء المحليين ، والخوادم الأولية للعملاء الذين لديهم عناوين IP عامة).",
|
||||
"use_private_ptr_resolvers_title": "استخدم محللات DNS العكسية الخاصة",
|
||||
@@ -34,7 +38,7 @@
|
||||
"dhcp_leases_not_found": "لم يتم العثور على عقود إيجار DHCP",
|
||||
"dhcp_config_saved": "الإعدادات محفوظة لخادم DHCP",
|
||||
"dhcp_ipv4_settings": "DHCP IPv4 إعدادات",
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 إعدادات",
|
||||
"dhcp_ipv6_settings": "إعدادات DHCP IPv6",
|
||||
"form_error_required": "الحقل مطلوب",
|
||||
"form_error_ip4_format": "عنوان IPv4 غير صالح",
|
||||
"form_error_ip4_gateway_format": "عنوان IPv4 غير صالح للبوابة",
|
||||
@@ -63,14 +67,16 @@
|
||||
"dhcp_ip_addresses": "عناوين الـIP",
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "اسم المضيف",
|
||||
"dhcp_table_expires": "يتنهي في",
|
||||
"dhcp_table_expires": "تنتهي",
|
||||
"dhcp_warning": "إذا كنت تريد تمكين خادم DHCP على أي حال ، فتأكد من عدم وجود خادم DHCP نشط آخر في شبكتك. خلاف ذلك ، يمكن أن يعطل خدمة الإنترنت للأجهزة المتصلة!",
|
||||
"dhcp_error": "لم نتمكن من تحديد ما إذا كان هناك خادم DHCP آخر في الشبكة.",
|
||||
"dhcp_static_ip_error": "من أجل استخدام خادم DHCP ، يجب تعيين عنوان IP ثابت. فشلنا في تحديد ما إذا تم تكوين واجهة الشبكة هذه باستخدام عنوان IP ثابت. يرجى تعيين عنوان IP ثابت يدويًا.",
|
||||
"dhcp_dynamic_ip_found": "يستخدم نظامك عنوان IP الديناميكي للواجهة <0>{{interfaceName}}</0>. من أجل استعمال خادم DHCP ، يجب تعيين عنوان IP ثابت. عنوان IP الحالي الخاص بك هو <0>{{ipAddress}}</0>. إذا ضغطت على زر تفعيل DHCP سنقوم تلقائيًا بتعيين عنوان الIP هذا على أنه ثابت.",
|
||||
"dhcp_lease_added": "تمت أضافة مدة الايجار \"{{key}}\" بنجاح",
|
||||
"dhcp_lease_deleted": "تمت ازالة مدة الايجار \"{{key}}\" بنجاح",
|
||||
"dhcp_lease_updated": "Static lease \"{{key}}\" تمّ التحديث بنجاح",
|
||||
"dhcp_new_static_lease": "عقد إيجار ثابت جديد",
|
||||
"dhcp_edit_static_lease": "تحرير عقد الإيجار الثابت",
|
||||
"dhcp_static_leases_not_found": "لم يتم العثور على عقود إيجار ثابتة DHCP",
|
||||
"dhcp_add_static_lease": "إضافة عقد إيجار ثابت",
|
||||
"dhcp_reset_leases": "إعادة تعيين كافة عقود الإيجار",
|
||||
@@ -83,7 +89,7 @@
|
||||
"form_enter_hostname": "أدخل اسم الhostname",
|
||||
"error_details": "مزيد من التفاصيل حول الخطأ",
|
||||
"response_details": "تفاصيل الاستجابة",
|
||||
"request_details": "تفاصيل الطلب",
|
||||
"request_details": "طلب التفاصيل",
|
||||
"client_details": "تفاصيل العميل",
|
||||
"details": "التفاصيل",
|
||||
"back": "رجوع",
|
||||
@@ -93,13 +99,13 @@
|
||||
"filter": "فلتر",
|
||||
"query_log": "سجل الQuery",
|
||||
"compact": "المدمج",
|
||||
"nothing_found": "لم يتم العثور علي شيء...",
|
||||
"faq": "أسئلة مكررة",
|
||||
"nothing_found": "لم يتم العثور على شيء",
|
||||
"faq": "الأسئلة المتداولة",
|
||||
"version": "الإصدار",
|
||||
"address": "العناوين",
|
||||
"address": "العنوان",
|
||||
"protocol": "البروتوكول",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"on": "قيد التشغيل",
|
||||
"off": "قيد الإيقاف",
|
||||
"copyright": "حقوق النشر",
|
||||
"homepage": "الصفحة الرئيسية",
|
||||
"report_an_issue": "الإبلاغ عن مشكلة",
|
||||
@@ -114,7 +120,8 @@
|
||||
"stats_malware_phishing": "حسر البرامج الضارة / والتصيّد",
|
||||
"stats_adult": "حظر مواقع الويب الخاصة بالبالغين",
|
||||
"stats_query_domain": "اعلى النطاقات التي تم الاستعلام عنها",
|
||||
"for_last_24_hours": "لأخر 24 ساعة",
|
||||
"for_last_hours": "لآخر {{count}} ساعة",
|
||||
"for_last_hours_plural": "لآخر {{count}} ساعة",
|
||||
"for_last_days": "لآخر {{value}} يوم",
|
||||
"for_last_days_plural": "لآخر {{count}} ايام",
|
||||
"stats_disabled": "تم تعطيل الإحصائيات. يمكنك تشغيله من <0> صفحة الإعدادات </0>.",
|
||||
@@ -129,13 +136,16 @@
|
||||
"no_upstreams_data_found": "لم يتم العثور على بيانات خوادم upstream",
|
||||
"number_of_dns_query_days": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} يوم",
|
||||
"number_of_dns_query_days_plural": "عدد استعلامات DNS التي تمت معالجتها لآخر {{count}} أيام",
|
||||
"number_of_dns_query_24_hours": "عدد استعلامات DNS التي تمت معالجتها لآخر 24 ساعة",
|
||||
"number_of_dns_query_hours": "عدد استفسارات DNS التي تمت معالجتها لآخر {{count}} ساعة",
|
||||
"number_of_dns_query_hours_plural": "عدد استعلامات DNS التي تمت معالجتها خلال آخر {{count}} ساعة",
|
||||
"number_of_dns_query_blocked_24_hours": "عدد طلبات DNS المحظورة بواسطة فلاتر adblock وقوائم حظر المضيفين",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "عدد طلبات DNS التي تم حظرها من قبل وحدة أمان التصفح AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "عدد من المواقع (الإباحية) للبالغين تم حجبها",
|
||||
"enforced_save_search": "فرض البحث الآمن",
|
||||
"number_of_dns_query_to_safe_search": "عدد طلبات DNS لمحركات البحث التي تم فرض البحث الآمن عنها",
|
||||
"average_processing_time": "متوسط وقت المعالجة",
|
||||
"average_upstream_response_time": "متوسط وقت استجابة المنبع",
|
||||
"response_time": "وقت الاستجابة",
|
||||
"average_processing_time_hint": "متوسط الوقت بالمللي ثانية عند معالجة طلب DNS",
|
||||
"block_domain_use_filters_and_hosts": "حظر النطاقات باستخدام عوامل التصفية وملفات المضيفين",
|
||||
"filters_block_toggle_hint": "يمكنك إعداد قواعد حظر في <a>المرشحات</a> اعدادات.",
|
||||
@@ -170,8 +180,9 @@
|
||||
"enabled_parental_toast": "تفعيل الرقابة الأبوية",
|
||||
"disabled_safe_search_toast": "تعطيل البحث الآمن",
|
||||
"enabled_save_search_toast": "تفعيل البحث الآمن",
|
||||
"enabled_table_header": "تمكين",
|
||||
"name_table_header": "الاسم",
|
||||
"updated_save_search_toast": "تم تحديث إعدادات البحث الآمن",
|
||||
"enabled_table_header": "قيد التشغيل",
|
||||
"name_table_header": "الاِسْم",
|
||||
"list_url_table_header": "قائمة الروابط",
|
||||
"rules_count_table_header": "عدد القواعد",
|
||||
"last_time_updated_table_header": "آخر تحديث",
|
||||
@@ -225,6 +236,7 @@
|
||||
"updated_upstream_dns_toast": "تم حفظ خوادم Upstream بنجاح",
|
||||
"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": "حظر",
|
||||
@@ -232,7 +244,8 @@
|
||||
"allow_this_client": "السماح لهذا العميل",
|
||||
"block_for_this_client_only": "احجب هذا العميل فقط",
|
||||
"unblock_for_this_client_only": "إلغاء حجب هذا العميل فقط",
|
||||
"time_table_header": "وقت",
|
||||
"add_persistent_client": "إضافة كعميل دائم",
|
||||
"time_table_header": "الوقت",
|
||||
"date": "التاريخ",
|
||||
"domain_name_table_header": "اسم النطاق",
|
||||
"domain_or_client": "الدومين أو العميل",
|
||||
@@ -247,7 +260,7 @@
|
||||
"refresh_btn": "تحديث",
|
||||
"previous_btn": "السابق",
|
||||
"next_btn": "التالي",
|
||||
"loading_table_status": "جار التحميل...",
|
||||
"loading_table_status": "التحميل جارٍ...",
|
||||
"page_table_footer_text": "الصفحة",
|
||||
"rows_table_footer_text": "صفوف",
|
||||
"updated_custom_filtering_toast": "تحديث قواعد الفلترة المخصصة",
|
||||
@@ -259,12 +272,12 @@
|
||||
"query_log_cleared": "تم مسح سجل الاستعلام بنجاح",
|
||||
"query_log_updated": "تم تحديث سجل الاستعلام بنجاح",
|
||||
"query_log_clear": "مسح سجلات الاستعلام",
|
||||
"query_log_retention": "الاحتفاظ بسجلات الاستعلام",
|
||||
"query_log_retention": "تناوب سجلات الاستعلام",
|
||||
"query_log_enable": "تمكين السجل",
|
||||
"query_log_configuration": "تكوين السجلات",
|
||||
"query_log_disabled": "سجل الاستعلام معطل ويمكن تهيئته من<0>الاعدادات</0>",
|
||||
"query_log_strict_search": "استخدم علامات الاقتباس المزدوجة للبحث الدقيق",
|
||||
"query_log_retention_confirm": "هل أنت متأكد من أنك تريد تغيير الاحتفاظ بسجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني سيتم فقدان بعض البيانات",
|
||||
"query_log_retention_confirm": "هل أنت متيقِّن من أنك تريد تغيير دوران سجل الاستعلام؟ إذا قمت بتقليل قيمة الفاصل الزمني، ستفقد بعض البيانات",
|
||||
"anonymize_client_ip": "إخفاء عنوان IP العميل",
|
||||
"anonymize_client_ip_desc": "لا تقم بحفظ كامل عنوان IP العميل في السجلات والإحصائيات",
|
||||
"dns_config": "إعداد خادم DNS",
|
||||
@@ -279,6 +292,8 @@
|
||||
"blocking_ipv4": "حجب عنوان IPv4",
|
||||
"blocking_ipv6": "حجب عنوان IPv6",
|
||||
"blocked_response_ttl": "زمن حظر الاستجابة",
|
||||
"blocked_response_ttl_desc": "تحديد عدد الثواني التي يجب على العملاء تخزين الاستجابة التي تمت تصفيتها مؤقتًا",
|
||||
"form_enter_blocked_response_ttl": "أدخل وقت الاستجابة المحظورة TTL (بالثواني)",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
@@ -294,7 +309,19 @@
|
||||
"rate_limit": "حدود التقييم",
|
||||
"edns_enable": "فعل EDNS client subnet",
|
||||
"edns_cs_desc": "أضف EDNS الشبكة الفرعية للعميل (ECS) إلى الطلبات الأولية وقم بتسجيل القيم المرسلة من قبل العملاء في سجل الاستعلام.",
|
||||
"edns_use_custom_ip": "استخدام IP مخصص لـ EDNS",
|
||||
"edns_use_custom_ip_desc": "السماح باستخدام IP مخصص لـ EDNS",
|
||||
"rate_limit_desc": "عدد الطلبات في الثانية المسموح بها لكل عميل. جعله على 0 يعني عدم وجود حد.",
|
||||
"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_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_placeholder": "أدخل عنوان IP واحد لكل سطر",
|
||||
"blocking_ipv4_desc": "سيتم إرجاع عنوان IP لطلب محظور",
|
||||
"blocking_ipv6_desc": "سيتم إرجاع عنوان IP لطلب AAAA محظور",
|
||||
"blocking_mode_default": "الافتراضي: الرد بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA) عند حظره بواسطة قاعدة نمط Adblock ؛ الرد بعنوان IP المحدد في القاعدة عند حظره بواسطة / etc / hosts-style rule",
|
||||
@@ -302,14 +329,15 @@
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: الرد باستخدام رمز NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP Null: الاستجابة بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA)",
|
||||
"blocking_mode_custom_ip": "استجابة IP مخصصة بعنوان IP تم تعيينه يدويًا",
|
||||
"theme_auto": "تلقائي",
|
||||
"theme_light": "فاتح",
|
||||
"theme_dark": "ليلي",
|
||||
"theme_dark": "داكن",
|
||||
"upstream_dns_client_desc": "إذا احتفظت بهذا الحقل فارغًا ، فسيستخدم AdGuard Home الخوادم التي تم تكوينها في<0>DNS إعدادات</0>.",
|
||||
"tracker_source": "مصدر المتعقب",
|
||||
"source_label": "المصدر",
|
||||
"found_in_known_domain_db": "تم العثور عليه في قاعدة بيانات دومينات معروفة.",
|
||||
"category_label": "الفئة",
|
||||
"rule_label": "قواعد",
|
||||
"rule_label": "قاعدة (قواعد)",
|
||||
"list_label": "قائمه",
|
||||
"unknown_filter": "فلتر غير معروف {{filterId}}",
|
||||
"known_tracker": "متعقب معروف",
|
||||
@@ -320,14 +348,14 @@
|
||||
"install_settings_port": "المنفذ",
|
||||
"install_settings_interface_link": "ستكون واجهة الويب الخاصة بمسؤول AdGuard Home متاحة على العناوين التالية:",
|
||||
"form_error_port": "أدخل رقم منفذ صالح",
|
||||
"install_settings_dns": "خادم DNS",
|
||||
"install_settings_dns": "خَادِم DNS",
|
||||
"install_settings_dns_desc": "ستحتاج إلى ضبط أجهزتك أو جهاز التوجيه الخاص بك لاستخدام خادم DNS على العناوين التالية:",
|
||||
"install_settings_all_interfaces": "جميع الواجهات",
|
||||
"install_auth_title": "المصادقة",
|
||||
"install_auth_desc": "يجب إعداد مصادقة كلمة المرور لواجهة ويب مسؤول AdGuard Home. في حال كان AdGuard Home لا يمكن الوصول إليه إلا في شبكتك المحلية ، فلا يزال من المهم حمايته من الوصول غير المقيد.",
|
||||
"install_auth_username": "اسم المستخدم",
|
||||
"install_auth_password": "الكلمة السرية",
|
||||
"install_auth_confirm": "تاكيد كلمه المرور",
|
||||
"install_auth_confirm": "تأكيد كلمة المرور",
|
||||
"install_auth_username_enter": "أدخل اسم المستخدم",
|
||||
"install_auth_password_enter": "أدخل كلمة المرور",
|
||||
"install_step": "خطوة",
|
||||
@@ -397,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",
|
||||
@@ -436,6 +467,7 @@
|
||||
"form_add_id": "أضافة معّرف",
|
||||
"form_client_name": "ادخل اسم العميل",
|
||||
"name": "اسم",
|
||||
"client_name": "العميل {{id}}",
|
||||
"client_global_settings": "استخدم إعدادات عالمية",
|
||||
"client_deleted": "تم حذف العميل \"{{key}}\" بنجاح",
|
||||
"client_added": "تم اضافة العميل \"{{key}}\" بنجاح",
|
||||
@@ -444,7 +476,7 @@
|
||||
"client_confirm_delete": "هل أنت متأكد من أنك تريد حذف العميل \"{{key}}\"?",
|
||||
"list_confirm_delete": "هل أنت متأكد أنك تريد حذف هذه القائمة؟",
|
||||
"auto_clients_title": "Runtime clients",
|
||||
"auto_clients_desc": "الأجهزة غير المدرجة في قائمة العملاء الدائمين الذين قد لا يزالون يستخدمون AdGuard Home",
|
||||
"auto_clients_desc": "معلومات حول عناوين IP للأجهزة التي تستخدم أو قد تستخدم AdGuard Home. يتم جمع هذه المعلومات من عدة مصادر، بما في ذلك ملفات المضيفين، و DNS العكسي، إلخ.",
|
||||
"access_title": "إعدادات الوصول",
|
||||
"access_desc": "هنا يمكنك ضبط قواعد الوصول لخادم AdGuard Home DNS",
|
||||
"access_allowed_title": "العملاء المسموحين",
|
||||
@@ -457,6 +489,7 @@
|
||||
"updates_checked": "يتوفر إصدار جديد من AdGuard Home",
|
||||
"updates_version_equal": "AdGuard Home محدث",
|
||||
"check_updates_now": "تحقق من وجود تحديثات الآن",
|
||||
"version_request_error": "فشل التحقق من التحديث. يرجى التحقق من اتصالك بالإنترنت.",
|
||||
"dns_privacy": "خصوصية DNS",
|
||||
"setup_dns_privacy_1": "<0> DNS-over-TLS: </0> استخدم سلسلة <1> {{address}} </1>.",
|
||||
"setup_dns_privacy_2": "<0> DNS-over-HTTPS: </0> استخدم سلسلة <1> {{address}} </1>.",
|
||||
@@ -477,7 +510,9 @@
|
||||
"setup_dns_notice": "من أجل استخدام <0> DNS-over-HTTPS </0> أو <1> DNS-over-TLS </1> ، تحتاج إلى <1> تكوين التشفير </1> في إعدادات AdGuard Home.",
|
||||
"rewrite_added": "تمت إضافة إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
|
||||
"rewrite_deleted": "تم حذف إعادة كتابة DNS لـ \"{{key}}\" بنجاح",
|
||||
"rewrite_updated": "تم تحديث إعادة كتابة DNS بنجاح",
|
||||
"rewrite_add": "إضافة إعادة كتابة DNS",
|
||||
"rewrite_edit": "تحرير إعادة كتابة DNS",
|
||||
"rewrite_not_found": "لم يتم العثور على إعادة كتابة DNS",
|
||||
"rewrite_confirm_delete": "هل أنت متأكد من أنك تريد حذف إعادة كتابة DNS لـ \"{{key}}\"؟",
|
||||
"rewrite_desc": "يسمح بتكوين استجابة DNS المخصصة بسهولة لاسم نطاق معين.",
|
||||
@@ -506,7 +541,7 @@
|
||||
"encryption_key_source_content": "الصق محتويات المفتاح الخاص",
|
||||
"stats_params": "ضبط الاحصائيات",
|
||||
"config_successfully_saved": "تم حفظ الاعدادات بنجاح",
|
||||
"interval_6_hour": "ساعات6",
|
||||
"interval_6_hour": "6 ساعات",
|
||||
"interval_24_hour": "24 ساعة",
|
||||
"interval_days": "{{count}} يوم",
|
||||
"interval_days_plural": "{{count}} الأيام",
|
||||
@@ -517,14 +552,18 @@
|
||||
"filter_added_successfully": "تم إضافة القائمة بنجاح",
|
||||
"filter_removed_successfully": "تم ازالته من القائمة بنجاح",
|
||||
"filter_updated": "تم تحديث القائمة بنجاح",
|
||||
"statistics_configuration": "ضبط الاحصائيات",
|
||||
"statistics_configuration": "تكوين الإحصائيات",
|
||||
"statistics_retention": "الاحتفاظ بالإحصاءات",
|
||||
"statistics_retention_desc": "إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
|
||||
"statistics_clear": "إعادة تعيين الإحصائيات",
|
||||
"statistics_clear_confirm": "هل أنت متأكد من أنك تريد مسح الإحصاءات؟",
|
||||
"statistics_clear_confirm": "هل أنت متيقِّن من أنك تريد مسح الإحصائيات؟",
|
||||
"statistics_retention_confirm": "هل أنت متأكد أنك تريد تغيير الاحتفاظ بالإحصاءات؟ إذا قمت بتقليل قيمة الفاصل الزمني ، فستفقد بعض البيانات",
|
||||
"statistics_cleared": "تم مسح الإحصائيات بنجاح",
|
||||
"statistics_enable": "تفعيل الاحصائيات",
|
||||
"ignore_domains": "المجالات التي تم تجاهلها (مفصولة بسطر جديد)",
|
||||
"ignore_domains_title": "نطاقات تم تجاهلها",
|
||||
"ignore_domains_desc_stats": "لا تتم كتابة الاستعلامات المطابقة لهذه القواعد في الإحصائيات",
|
||||
"ignore_domains_desc_query": "لا تتم كتابة الاستعلامات المطابقة لهذه القواعد في سجل الاستعلامات",
|
||||
"interval_hours": "{{count}} ساعة",
|
||||
"interval_hours_plural": "{{count}} ساعات",
|
||||
"filters_configuration": "اضبط الفلاتر",
|
||||
@@ -597,20 +636,20 @@
|
||||
"dnssec_enable_desc": "قم بتعيين علامة DNSSEC في استعلامات DNS الواردة وتحقق من النتيجة (مطلوب محلل يدعم DNSSEC).",
|
||||
"validated_with_dnssec": "تم التحقق من صحتها باستخدام DNSSEC",
|
||||
"all_queries": "كافة الاستفسارات",
|
||||
"show_blocked_responses": "حظر",
|
||||
"show_whitelisted_responses": "القائمة البيضاء",
|
||||
"show_processed_responses": "معالجة",
|
||||
"show_blocked_responses": "ما تمّ حظره",
|
||||
"show_whitelisted_responses": "المسموح به",
|
||||
"show_processed_responses": "تمت معالجتها",
|
||||
"blocked_safebrowsing": "محظور بواسطة التصفح الآمن",
|
||||
"blocked_adult_websites": "محظور بواسطة الرقابة الأبوية",
|
||||
"blocked_adult_websites": "محظور بواسطة الرِّقابة الأبوية",
|
||||
"blocked_threats": "التهديدات المحظورة",
|
||||
"allowed": "القائمة البيضاء",
|
||||
"allowed": "المسموح به",
|
||||
"filtered": "تمت الفلترة",
|
||||
"rewritten": "أعيدت كتابته",
|
||||
"safe_search": "البحث الأمن",
|
||||
"safe_search": "البحث الآمن",
|
||||
"blocklist": "قائمة الحظر",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "حجم ذاكرة التخزين المؤقت",
|
||||
"cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات (بالبايت).",
|
||||
"cache_size_desc": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاق (بالبايت). لتعطيل التخزين المؤقت، اتركه فارغًا.",
|
||||
"cache_ttl_min_override": "تجاوز الحد الأدنى من مدة البقاء TTL",
|
||||
"cache_ttl_max_override": "تجاوز الحد الاقصى من مدة البقاء TTL",
|
||||
"enter_cache_size": "أدخل حجم ذاكرة التخزين المؤقت (بايت)",
|
||||
@@ -621,14 +660,14 @@
|
||||
"ttl_cache_validation": "يجب أن يكون الحد الأدنى لتجاوز TTL لذاكرة التخزين المؤقت أقل من أو يساوي الحد الأقصى",
|
||||
"cache_optimistic": "متفائل التخزين المؤقت",
|
||||
"cache_optimistic_desc": "اجعل AdGuard Home يستجيب من ذاكرة التخزين المؤقت حتى عندما تنتهي صلاحية الإدخالات وحاول أيضًا تحديثها.",
|
||||
"filter_category_general": "General",
|
||||
"filter_category_security": "الامان",
|
||||
"filter_category_general": "عام",
|
||||
"filter_category_security": "الأمان",
|
||||
"filter_category_regional": "إقليمي",
|
||||
"filter_category_other": "أخرى",
|
||||
"filter_category_general_desc": "القوائم التي تمنع التتبع والإعلان على معظم الأجهزة",
|
||||
"filter_category_general_desc": "القوائم التي تحظر التتبع والإعلانات على معظم الأجهزة",
|
||||
"filter_category_security_desc": "القوائم المصممة خصيصًا لحظر النطاقات الخبيثة والتصيد الاحتيالي والخداع",
|
||||
"filter_category_regional_desc": "القوائم التي تركز على الإعلانات الإقليمية وخوادم التتبع",
|
||||
"filter_category_other_desc": "قوائم حظر أخرى",
|
||||
"filter_category_other_desc": "قوائم الحظر الأخرى",
|
||||
"setup_config_to_enable_dhcp_server": "أضبط الاعدادات لتمكين خادم DHCP",
|
||||
"original_response": "الرد الأصلي",
|
||||
"click_to_view_queries": "انقر لعرض الـ queries",
|
||||
@@ -637,16 +676,72 @@
|
||||
"filter_allowlist": "تحذير: سيؤدي هذا الإجراء أيضًا إلى استبعاد القاعدة \"{{disallowed_rule}}\" من قائمة العملاء المسموح لهم.",
|
||||
"last_rule_in_allowlist": "لا يمكن منع هذا العميل لأن استبعاد القاعدة \"{{disallowed_rule}}\" سيؤدي إلى تعطيل قائمة \"العملاء المسموح لهم\".",
|
||||
"use_saved_key": "استخدم المفتاح المحفوظ مسبقًا",
|
||||
"parental_control": "الرقابة الابويه",
|
||||
"parental_control": "الرِّقابة الأبوية",
|
||||
"safe_browsing": "تصفح آمن",
|
||||
"served_from_cache": "{{value}} <i>(يتم تقديمه من ذاكرة التخزين المؤقت)</i>",
|
||||
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{value}} من الأحرف على الأقل",
|
||||
"served_from_cache_label": "يتم تقديمه من ذاكرة التخزين المؤقت",
|
||||
"form_error_password_length": "يجب أن تتكون كلمة المرور من {{min}} إلى {{max}} من الأحرف في الأقل",
|
||||
"anonymizer_notification": "<0>ملاحظة:</0> تم تمكين إخفاء هُوِيَّة IP. يمكنك تعطيله في <1>الإعدادات العامة</1>.",
|
||||
"confirm_dns_cache_clear": "هل تريد بالتأكيد مسح ذاكرة التخزين المؤقت لنظام أسماء النطاقات DNS؟",
|
||||
"cache_cleared": "تم مسح ذاكرة التخزين المؤقت لنظام أسماء النطاق بنجاح",
|
||||
"clear_cache": "مسح ذاكرة التخزين المؤقت",
|
||||
"make_static": "اجعلها ثابتة",
|
||||
"theme_auto_desc": "تلقائي (بناءً على نظام ألوان جهازك)",
|
||||
"theme_dark_desc": "المظهر الداكن",
|
||||
"theme_light_desc": "المظهر فاتح",
|
||||
"disable_for_seconds": "لـ {{count}} ثانية",
|
||||
"disable_for_seconds_plural": "لمدة {{count}} ثانية",
|
||||
"disable_for_minutes": "لمدة {{count}} دقيقة",
|
||||
"disable_for_minutes_plural": "لمدة {{count}} دقيقة",
|
||||
"disable_for_hours": "لمدة {{count}} ساعة",
|
||||
"disable_for_hours_plural": "لمدة {{count}} ساعات",
|
||||
"disable_until_tomorrow": "حتى الغد",
|
||||
"disable_notify_for_seconds": "تعطيل الحماية لـ {{count}} ثانية",
|
||||
"disable_notify_for_seconds_plural": "تعطيل الحماية ل {{count}} ثواني",
|
||||
"disable_notify_for_minutes": "تعطيل الحماية لـ {{count}} دقيقة",
|
||||
"disable_notify_for_minutes_plural": "تعطيل الحماية لـ {{count}} دقائق",
|
||||
"disable_notify_for_hours": "تعطيل الحماية لـ {{count}} ساعة",
|
||||
"disable_notify_for_hours_plural": "تعطيل الحماية لـ {{count}} ساعات",
|
||||
"disable_notify_until_tomorrow": "تعطيل الحماية حتى الغد",
|
||||
"enable_protection_timer": "سيتم تمكين الحماية في {{time}}",
|
||||
"custom_retention_input": "أدخل الاحتفاظ بالساعات",
|
||||
"custom_rotation_input": "أدخل التناوب بالساعات",
|
||||
"protection_section_label": "الحماية",
|
||||
"log_and_stats_section_label": "سجل الاستعلام والإحصائيات",
|
||||
"ignore_query_log": "تجاهل هذا العميل في سجل الاستعلام",
|
||||
"ignore_statistics": "تجاهل هذا العميل في الإحصائيات",
|
||||
"schedule_services": "إيقاف حظر الخدمة مؤقتًا",
|
||||
"schedule_services_desc": "تهيئة جدول إيقاف فلتر خدمة الحظر",
|
||||
"schedule_services_desc_client": "تهيئة جدول إيقاف فلتر خدمة الحظر لهذا العميل",
|
||||
"schedule_desc": "تعيين فترات عدم النشاط للخدمات المحظورة",
|
||||
"schedule_invalid_select": "يجب أن يكون وقت البَدْء قبل وقت الانتهاء",
|
||||
"schedule_select_days": "اختر الأيام",
|
||||
"schedule_timezone": "قم باختيار منطقة زمنية",
|
||||
"schedule_current_timezone": "المنطقة الزمنية الحالية: {{value}}",
|
||||
"schedule_time_all_day": "طوال اليوم",
|
||||
"schedule_modal_description": "سيحل هذا الجدول الزمني محل أي جداول موجودة لنفس اليوم من الأسبوع. يمكن أن يكون لكل يوم من أيام الأسبوع مدّة خمول واحدة فقط.",
|
||||
"schedule_modal_time_off": "لا يوجد حظر للخدمة:",
|
||||
"schedule_new": "جدول زمني جديد",
|
||||
"schedule_edit": "تحرير الجدول الزمني",
|
||||
"schedule_save": "حفظ الجدول الزمني",
|
||||
"schedule_add": "إضافة جدول زمني",
|
||||
"schedule_remove": "إزالة الجدول الزمني",
|
||||
"schedule_from": "من",
|
||||
"schedule_to": "إلى",
|
||||
"sunday": "الأحد",
|
||||
"monday": "الإثنين",
|
||||
"tuesday": "الثلاثاء",
|
||||
"wednesday": "الأربعاء",
|
||||
"thursday": "الخميس",
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت",
|
||||
"sunday_short": "الاحد",
|
||||
"monday_short": "الإثنين",
|
||||
"tuesday_short": "الثلاثاء",
|
||||
"wednesday_short": "الاربعاء",
|
||||
"thursday_short": "الخميس",
|
||||
"friday_short": "الجمعة",
|
||||
"saturday_short": "السبت"
|
||||
"saturday_short": "السبت",
|
||||
"upstream_dns_cache_configuration": "تهيئة ذاكرة التخزين المؤقت لنظام أسماء النطاقات المستقبلي",
|
||||
"enable_upstream_dns_cache": "تمكين التخزين المؤقت لنظام أسماء النطاقات DNS لتكوين المنبع المخصص لهذا العميل",
|
||||
"dns_cache_size": "حجم ذاكرة التخزين المؤقت لنظام أسماء النطاقات، بالبايت"
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Seznam záložních DNS serverů používaných v případě, že odchozí DNS servery neodpovídají. Syntaxe je stejná jako v hlavním poli pro odchozí servery výše.",
|
||||
"fallback_dns_placeholder": "Zadejte jeden záložní DNS server na řádek",
|
||||
"local_ptr_title": "Soukromé reverzní DNS servery",
|
||||
"local_ptr_desc": "Servery DNS, které AdGuard Home používá pro lokální dotazy PTR. Tyto servery se používají k řešení požadavků PTR na adresy v soukromých rozmezích IP, například \"192.168.12.34\", pomocí reverzního DNS. Pokud není nastaveno, AdGuard Home automaticky použije výchozí řešitele vašeho OS s výjimkou adres samotného AdGuard Home.",
|
||||
"local_ptr_desc": "DNS servery používané AdGuard Home pro soukromé požadavky PTR, SOA a NS. Požadavek je považován za soukromý, pokud požaduje doménu ARPA obsahující podsíť v rámci soukromých IP rozsahů (například \"192.168.12.34\") a pochází od klienta se soukromou IP adresou. Pokud není nastaveno, budou použity výchozí DNS řešitele vašeho operačního systému, s výjimkou IP adres AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Ve výchozím nastavení používá AdGuard Home následující reverzní DNS řešitele: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nemohl určit vhodné soukromé reverzní DNS řešitele pro tento systém.",
|
||||
"local_ptr_placeholder": "Zadejte jednu IP adresu na řádek",
|
||||
"resolve_clients_title": "Povolit zpětné řešení IP adres klientů",
|
||||
"resolve_clients_desc": "Obráceně vyřešit IP adresy klientů na jejich názvy hostitelů zasláním dotazů PTR příslušným řešitelům (soukromé DNS servery pro místní klienty, odchozí servery pro klienty s veřejnou IP adresou).",
|
||||
"use_private_ptr_resolvers_title": "Použít soukromé reverzní rDNS řešitele",
|
||||
"use_private_ptr_resolvers_desc": "Realizuje reverzní DNS vyhledávání pro lokální adresy pomocí těchto odchozích serverů. Pokud je funkce vypnuta, Adguard Home reaguje s NXDOMAIN na všechny takové PTR dotazy kromě klientů známých z DHCP, /etc/hosts, atd.",
|
||||
"use_private_ptr_resolvers_desc": "Řešení požadavků PTR, SOA a NS pro domény ARPA obsahující soukromé IP adresy prostřednictvím soukromých odchozích serverů, DHCP, /etc/hosts atd. Pokud je zakázáno, AdGuard Home bude na všechny takové požadavky odpovídat pomocí NXDOMAIN.",
|
||||
"check_dhcp_servers": "Zkontrolovat DHCP servery",
|
||||
"save_config": "Uložit konfiguraci",
|
||||
"enabled_dhcp": "DHCP server zapnutý",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Použít dříve uložený klíče",
|
||||
"parental_control": "Rodičovská ochrana",
|
||||
"safe_browsing": "Bezpečné prohlížení",
|
||||
"served_from_cache": "{{value}} <i>(převzato z mezipaměti)</i>",
|
||||
"served_from_cache_label": "Převzato z mezipaměti",
|
||||
"form_error_password_length": "Heslo musí obsahovat od {{min}} do {{max}} znaků",
|
||||
"anonymizer_notification": "<0>Poznámka:</0> Anonymizace IP je zapnuta. Můžete ji vypnout v <1>Obecných nastaveních</1>.",
|
||||
"confirm_dns_cache_clear": "Opravdu chcete vymazat mezipaměť DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste over reserve (fallback) DNS-servere, som bruges, når upstream DNS-servere ikke reagerer. Samme syntaks som i upstream-hovedfeltet ovenfor.",
|
||||
"fallback_dns_placeholder": "Angiv én reserve DNS-server pr. linje",
|
||||
"local_ptr_title": "Private reverse DNS-servere",
|
||||
"local_ptr_desc": "DNS-servere brugt af AdGuard Home til lokale PTR-forespørgsler. Disse servere bruges til at opløse PTR-forespørgsler fra private IP-adresseområder, f.eks. \"192.168.12.34\", vha. reverse DNS. Hvis ikke opsat, bruger AdGuard Home operativsystems standard DNS-opløsere undtagen for sine egne adresser.",
|
||||
"local_ptr_desc": "DNS-serverne brugt af AdGuard Home til private PTR-, SOA- og NS-forespørgsler. En forespørgsel anses som privat, hvis den omhandler et ARPA-domæne indeholdende et undernet i et privat IP-områder, (såsom \"192.168.12.34\") og kommer fra en klient med en privat adresse. Hvis ikke opsat, bruger AdGuard Home OS'ets adresser på standard DNS-opløserne, bortset fra AdGuard Home-adresserne.",
|
||||
"local_ptr_default_resolver": "AdGuard Home bruger som standard flg. reverse DNS-opløsere: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home kunne ikke fastslå egnede private reverse DNS-opløsere for dette system.",
|
||||
"local_ptr_placeholder": "Angiv én IP-adresse pr. linje",
|
||||
"resolve_clients_title": "Aktivér omvendt løsning af klienters IP-adresser",
|
||||
"resolve_clients_desc": "Opløs klienters IP-adresser reverseret til deres værtsnavne ved at sende PTR-forespørgsler til korresponderende opløsere (private DNS-servere til lokale klienter, upstream-servere til klienter med offentlige IP-adresser).",
|
||||
"use_private_ptr_resolvers_title": "Brug private reverse DNS-opløsere",
|
||||
"use_private_ptr_resolvers_desc": "Udfør reverse DNS-opslag for lokalt leverede adresser vha. disse upstream-servere. Hvis deaktiveret, svarer AdGuard Home med NXDOMAIN på alle sådanne PTR-forespørgsler bortset fra for klienter kendt via DHCP, /etc/hosts mv.",
|
||||
"use_private_ptr_resolvers_desc": "Opløs PTR-, SOA- og NS-forespørgsler til ARPA-domæner indeholdende private adresser vha. private upstream-servere, DHCP, /etc/hosts mv. Hvis deaktiveret, besvarer AdGuard Home sådanne forespørgsler med NXDOMAIN.",
|
||||
"check_dhcp_servers": "Søg efter DHCP-servere",
|
||||
"save_config": "Gem opsætning",
|
||||
"enabled_dhcp": "DHCP-server aktiveret",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Brug den tidligere gemte nøgle",
|
||||
"parental_control": "Forældrekontrol",
|
||||
"safe_browsing": "Sikker Browsing",
|
||||
"served_from_cache": "{{value}} <i>(leveret fra cache)</i>",
|
||||
"served_from_cache_label": "Leveret fra cache",
|
||||
"form_error_password_length": "Adgangskode skal udgøre fra {{min}} til {{max}} tegn",
|
||||
"anonymizer_notification": "<0>Bemærk:</0> IP-anonymisering er aktiveret. Det kan deaktiveres via <1>Generelle indstillinger</1>.",
|
||||
"confirm_dns_cache_clear": "Sikker på, at DNS-cache skal ryddes?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste der Fallback-DNS-Server, die verwendet werden, wenn die Upstream-DNS-Server nicht antworten. Die Syntax ist die gleiche wie im Hauptfeld für Upstream-Server oben.",
|
||||
"fallback_dns_placeholder": "Geben Sie einen Fallback-DNS-Server pro Zeile ein",
|
||||
"local_ptr_title": "Private inverse DNS-Server",
|
||||
"local_ptr_desc": "Die DNS-Server, die AdGuard Home für lokale PTR-Abfragen verwendet. Diese Server werden verwendet, um die Hostnamen von Clients mit privaten IP-Adressen, z. B. „192.168.12.34“, per inverse DNS-Anfragen aufzulösen. Wenn nicht festgelegt, verwendet AdGuard Home die Adressen der Standard-DNS-Auflöser Ihres Betriebssystems mit Ausnahme der Adressen von AdGuard Home selbst.",
|
||||
"local_ptr_desc": "Von AdGuard Home verwendete DNS-Server für private PTR-, SOA- und NS-Anfragen. Eine Anfrage gilt als privat, wenn sie nach einer ARPA-Domain fragt, die ein Subnetz innerhalb privater IP-Bereiche enthält (z. B. „192.168.12.34“) und von einem Client mit privater IP-Adresse stammt. Wenn nicht eingestellt, werden die Standard-DNS-Auflöser Ihres Betriebssystems verwendet, außer für die IP-Adressen von AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Standardmäßig verwendet AdGuard Home die folgenden inversen DNS-Resolver: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home konnte keine geeigneten privaten Invers-DNS-Resolver für dieses System ermitteln.",
|
||||
"local_ptr_placeholder": "Geben Sie eine IP-Adresse pro Zeile ein",
|
||||
"resolve_clients_title": "Hostnamenauflösung der Clients aktivieren",
|
||||
"resolve_clients_desc": "Inverses Auflösen der IP-Adressen der Clients in ihre Hostnamen durch Senden von PTR-Anfragen an die entsprechenden Resolver (private DNS-Server für lokale Kunden, Upstream-Server für Kunden mit öffentlichen IP-Adressen).",
|
||||
"use_private_ptr_resolvers_title": "Private Reverse-DNS-Resolver verwenden",
|
||||
"use_private_ptr_resolvers_desc": "Führt inverse DNS-Abfragen für lokal bereitgestellte Adressen mit diesen Upstream-Servern durch. Wenn deaktiviert, antwortet AdGuard Home mit NXDOMAIN auf alle solchen PTR-Anfragen, außer für Clients, die über DHCP, /etc/hosts usw. bekannt sind.",
|
||||
"use_private_ptr_resolvers_desc": "Löst PTR-, SOA- und NS-Anfragen für ARD-Domains mit privaten IP-Adressen über private Upstream-Server, DHCP, /etc/hosts usw. auf. Ist diese Option deaktiviert, antwortet AdGuard Home auf alle derartigen Anfragen mit NXDOMAIN.",
|
||||
"check_dhcp_servers": "Auf DHCP-Server prüfen",
|
||||
"save_config": "Konfiguration speichern",
|
||||
"enabled_dhcp": "DHCP-Server aktiviert",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Zuvor gespeicherten Schlüssel verwenden",
|
||||
"parental_control": "Kindersicherung",
|
||||
"safe_browsing": "Internetsicherheit",
|
||||
"served_from_cache": "{{value}} <i>(aus dem Cache abgerufen)</i>",
|
||||
"served_from_cache_label": "Aus dem Cache abgerufen",
|
||||
"form_error_password_length": "Das Passwort muss zwischen {{min}} und {{max}} Zeichen enthalten",
|
||||
"anonymizer_notification": "<0>Hinweis:</0> Die IP-Anonymisierung ist aktiviert. Sie können sie in den <1>Allgemeinen Einstellungen</1> deaktivieren.",
|
||||
"confirm_dns_cache_clear": "Möchten Sie den DNS-Cache wirklich leeren?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.",
|
||||
"fallback_dns_placeholder": "Enter one fallback DNS server per line",
|
||||
"local_ptr_title": "Private reverse DNS servers",
|
||||
"local_ptr_desc": "The DNS servers that AdGuard Home uses for local PTR queries. These servers are used to resolve PTR requests for addresses in private IP ranges, for example \"192.168.12.34\", using reverse DNS. If not set, AdGuard Home uses the addresses of the default DNS resolvers of your OS except for the addresses of AdGuard Home itself.",
|
||||
"local_ptr_desc": "DNS servers used by AdGuard Home for private PTR, SOA, and NS requests. A request is considered private if it asks for an ARPA domain containing a subnet within private IP ranges (such as \"192.168.12.34\") and comes from a client with a private IP address. If not set, the default DNS resolvers of your OS will be used, except for the AdGuard Home IP addresses.",
|
||||
"local_ptr_default_resolver": "By default, AdGuard Home uses the following reverse DNS resolvers: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home could not determine suitable private reverse DNS resolvers for this system.",
|
||||
"local_ptr_placeholder": "Enter one IP address per line",
|
||||
"resolve_clients_title": "Enable reverse resolving of clients' IP addresses",
|
||||
"resolve_clients_desc": "Reversely resolve clients' IP addresses into their hostnames by sending PTR queries to corresponding resolvers (private DNS servers for local clients, upstream servers for clients with public IP addresses).",
|
||||
"use_private_ptr_resolvers_title": "Use private reverse DNS resolvers",
|
||||
"use_private_ptr_resolvers_desc": "Perform reverse DNS lookups for locally served addresses using these upstream servers. If disabled, AdGuard Home responds with NXDOMAIN to all such PTR requests except for clients known from DHCP, /etc/hosts, and so on.",
|
||||
"use_private_ptr_resolvers_desc": "Resolve PTR, SOA, and NS requests for ARPA domains containing private IP addresses through private upstream servers, DHCP, /etc/hosts, etc. If disabled, AdGuard Home will respond to all such requests with NXDOMAIN.",
|
||||
"check_dhcp_servers": "Check for DHCP servers",
|
||||
"save_config": "Save configuration",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Use the previously saved key",
|
||||
"parental_control": "Parental Control",
|
||||
"safe_browsing": "Safe Browsing",
|
||||
"served_from_cache": "{{value}} <i>(served from cache)</i>",
|
||||
"served_from_cache_label": "Served from cache",
|
||||
"form_error_password_length": "Password must be {{min}} to {{max}} characters long",
|
||||
"anonymizer_notification": "<0>Note:</0> IP anonymization is enabled. You can disable it in <1>General settings</1>.",
|
||||
"confirm_dns_cache_clear": "Are you sure you want to clear DNS cache?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
|
||||
"fallback_dns_placeholder": "Ingresa un servidor DNS alternativo por línea",
|
||||
"local_ptr_title": "Servidores DNS inversos y privados",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para las consultas PTR locales. Estos servidores se utilizan para resolver las peticiones PTR de direcciones en rangos de IP privadas, por ejemplo \"192.168.12.34\", utilizando DNS inverso. Si no está establecido, AdGuard Home utilizará los resolutores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||
"local_ptr_desc": "Los servidores DNS que AdGuard Home utiliza para consultas PTR, SOA y NS privadas. La petición se considera privada si solicita un dominio ARPA que contiene una subred dentro de rangos IP privados, por ejemplo \"192.168.12.34\", y procede de un cliente con dirección privada. Si no se configura, AdGuard Home utiliza las direcciones de los resolvedores DNS predeterminados de tu sistema operativo, excepto las direcciones del propio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por defecto, AdGuard Home utiliza los siguientes resolutores DNS inversos: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home no pudo determinar los resolutores DNS inversos y privados adecuados para este sistema.",
|
||||
"local_ptr_placeholder": "Ingresa una dirección IP por línea",
|
||||
"resolve_clients_title": "Habilitar la resolución inversa de las direcciones IP de clientes",
|
||||
"resolve_clients_desc": "Resolve de manera inversa las direcciones IP de los clientes a sus nombres de hosts enviando consultas PTR a los resolutores correspondientes (servidores DNS privados para clientes locales, servidores DNS de subida para clientes con direcciones IP públicas).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolutores DNS inversos y privados",
|
||||
"use_private_ptr_resolvers_desc": "Realiza búsquedas DNS inversas para direcciones servidas localmente utilizando estos servidores DNS de subida. Si está deshabilitado, AdGuard Home responderá con NXDOMAIN a todas las peticiones PTR de este tipo, excepto para los clientes conocidos por DHCP, /etc/hosts, etc.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver las peticiones PTR, SOA y NS para dominios ARPA que contienen direcciones privadas utilizando servidores upstream privados, DHCP, /etc/hosts, etc. Si se desactiva, AdGuard Home responde a todas estas consultas con NXDOMAIN.",
|
||||
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
||||
"save_config": "Guardar configuración",
|
||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Usar la clave guardada previamente",
|
||||
"parental_control": "Control parental",
|
||||
"safe_browsing": "Navegación segura",
|
||||
"served_from_cache": "{{value}} <i>(servido desde la caché)</i>",
|
||||
"served_from_cache_label": "Servido desde la caché",
|
||||
"form_error_password_length": "La contraseña debe tener entre {{min}} y {{max}} caracteres",
|
||||
"anonymizer_notification": "<0>Nota:</0> La anonimización de IP está habilitada. Puedes deshabilitarla en <1>Configuración general</1>.",
|
||||
"confirm_dns_cache_clear": "¿Estás seguro de que deseas borrar la caché DNS?",
|
||||
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Käytä aiemmin tallennettua avainta",
|
||||
"parental_control": "Lapsilukko",
|
||||
"safe_browsing": "Turvallinen selaus",
|
||||
"served_from_cache": "{{value}} <i>(jaettu välimuistista)</i>",
|
||||
"served_from_cache_label": "Toimitettu välimuistista",
|
||||
"form_error_password_length": "Salasanan on oltava {{min}} - {{max}} merkkiä pitkä",
|
||||
"anonymizer_notification": "<0>Huomioi:</0> IP-osoitteen anonymisointi on käytössä. Voit poistaa sen käytöstä <1>Yleisistä asetuksista</1>.",
|
||||
"confirm_dns_cache_clear": "Haluatko varmasti tyhjentää DNS-välimuistin?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Liste des serveurs DNS de repli utilisés lorsque les serveurs DNS en amont ne répondent pas. La syntaxe est la même que dans le champ principal en amont ci-dessus.",
|
||||
"fallback_dns_placeholder": "Saisissez un serveur DNS de repli par ligne",
|
||||
"local_ptr_title": "Serveurs DNS privés inverses",
|
||||
"local_ptr_desc": "Les serveurs DNS que AdGuard Home utilise pour les requêtes PTR locales. Ces serveurs sont utilisés pour résoudre les noms d'hôte des clients avec des adresses IP privées, par exemple « 192.168.12.34 », en utilisant le DNS inversé. Si ce paramètre n'est pas défini, AdGuard Home utilise les adresses des résolveurs DNS par défaut de votre système d'exploitation, à l'exception des adresses d'AdGuard Home lui-même.",
|
||||
"local_ptr_desc": "Les serveurs DNS utilisés par AdGuard Home pour les requêtes privées PTR, SOA et NS. Une requête est considérée privée si elle demande un domaine ARPA contenant un sous-réseau entre les plages IP privées (par exemple \"192.168.12.34\") et provient d'un client avec une adresse IP privée. Sans réglages additionnels, les résolveurs DNS par défaut de votre système d'exploitation seront utilisés, sauf pour les adresses IP d'AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Par défaut, AdGuard Home utilise les résolveurs DNS inversés suivants : {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home n'a pas pu déterminer de résolveurs DNS inversés privés appropriés pour ce système.",
|
||||
"local_ptr_placeholder": "Saisissez une adresse IP par ligne",
|
||||
"resolve_clients_title": "Activer la résolution inverse des adresses IP des clients",
|
||||
"resolve_clients_desc": "Résoudre inversement les adresses IP des clients en leurs noms d'hôtes en envoyant des requêtes PTR aux résolveurs correspondants (serveurs DNS privés pour les clients locaux, serveurs en amont pour les clients ayant une adresse IP publique).",
|
||||
"use_private_ptr_resolvers_title": "Utiliser des résolveurs DNS inversés privés",
|
||||
"use_private_ptr_resolvers_desc": "Effectuer des recherches DNS inversées pour les adresses servies localement en utilisant ces serveurs en amont. S'il est désactivé, AdGuard Home répond avec NXDOMAIN à toutes les requêtes PTR de ce type, sauf pour les clients connus par DHCP, /etc/hosts, etc.",
|
||||
"use_private_ptr_resolvers_desc": "Résolvez les requêtes PTR, SOA et NS pour les domaines ARPA contenant des adresses IP privées par aide des serveurs privés en amont, DHCP, /etc/hosts, etc. S'il est désactivé, AdGuard Home répondra à toutes ces requêtes avec NXDOMAIN.",
|
||||
"check_dhcp_servers": "Rechercher les serveurs DHCP",
|
||||
"save_config": "Sauvegarder la configuration",
|
||||
"enabled_dhcp": "Serveur DHCP activé",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Utiliser la clef précédemment enregistrée",
|
||||
"parental_control": "Contrôle parental",
|
||||
"safe_browsing": "Navigation sécurisée",
|
||||
"served_from_cache": "{{value}} <i>(depuis le cache)</i>",
|
||||
"served_from_cache_label": "Servi depuis le cache",
|
||||
"form_error_password_length": "Le mot de passe doit comporter entre {{min}} et {{max}} caractères",
|
||||
"anonymizer_notification": "<0>Note :</0> L'anonymisation IP est activée. Vous pouvez la désactiver dans les <1>paramètres généraux</1>.",
|
||||
"confirm_dns_cache_clear": "Voulez-vous vraiment vider le cache DNS ?",
|
||||
|
||||
@@ -117,8 +117,8 @@
|
||||
"refresh_statics": "Segarkan statistik",
|
||||
"dns_query": "Kueri DNS",
|
||||
"blocked_by": "<0>Diblokir oleh</0>",
|
||||
"stats_malware_phishing": "Malware/phishing diblokir",
|
||||
"stats_adult": "Situs dewasa diblokir",
|
||||
"stats_malware_phishing": "Malware/phishing terblokir",
|
||||
"stats_adult": "Situs dewasa terblokir",
|
||||
"stats_query_domain": "Kueri domain teratas",
|
||||
"for_last_hours": "selama {{count}} jam terakhir",
|
||||
"for_last_hours_plural": "selama {{count}} jam terakhir",
|
||||
@@ -138,9 +138,9 @@
|
||||
"number_of_dns_query_days_plural": "Jumlah kueri DNS yang diproses selama {{count}} hari terakhir",
|
||||
"number_of_dns_query_hours": "Jumlah kueri DNS diproses selama {{{count}} jam terakhir",
|
||||
"number_of_dns_query_hours_plural": "Jumlah kueri DNS diproses selama {{count}} jam terakhir",
|
||||
"number_of_dns_query_blocked_24_hours": "Julah DNS diblokir oleh penyaring adblock dan daftar blokir hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Jumlah perminataan DNS diblokir oleh modul Kemanan Penjelajahan AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Jumlah website dewasa diblokir",
|
||||
"number_of_dns_query_blocked_24_hours": "Jumlah permintaan DNS yang diblokir oleh filter adblock dan daftar hitam host",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Jumlah permintaan DNS yang diblokir oleh modul keamanan penjelajahan AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Jumlah situs web dewasa yang diblokir",
|
||||
"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",
|
||||
@@ -148,7 +148,7 @@
|
||||
"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",
|
||||
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran di pengaturan <a>Penyaringan</a>.",
|
||||
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran dalam pengaturan <a>Filter</a>.",
|
||||
"use_adguard_browsing_sec": "Gunakan layanan web Keamanan Penjelajahan AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home akan memeriksa apakah domain diblokir oleh layanan web keamanan penjelajahan. Ini akan menggunakan API pencarian yang ramah privasi untuk melakukan pemeriksaan: hanya awalan singkat dari hash nama domain SHA256 yang dikirim ke server.",
|
||||
"use_adguard_parental": "Gunakan layanan web kontrol orang tua AdGuard",
|
||||
@@ -291,7 +291,7 @@
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blokiran IPv4",
|
||||
"blocking_ipv6": "Blokiran IPv6",
|
||||
"blocked_response_ttl": "Respons TTL yang diblokir",
|
||||
"blocked_response_ttl": "Respons TTL terblokir",
|
||||
"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",
|
||||
@@ -322,9 +322,9 @@
|
||||
"rate_limit_whitelist": "Daftar pembatasan tarif yang diizinkan",
|
||||
"rate_limit_whitelist_desc": "Alamat IP dikecualikan dari pembatasan tarif",
|
||||
"rate_limit_whitelist_placeholder": "Masukkan satu alamat IP per baris",
|
||||
"blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
|
||||
"blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
|
||||
"blocking_mode_default": "Default: Tanggapi dengan alamat IP nol (0.0.0.0 untuk A; :: untuk AAAA) saat diblokir oleh aturan gaya Adblock; tanggapi dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh aturan gaya host /etc/",
|
||||
"blocking_ipv4_desc": "Alamat IP yang akan dikembalikan untuk permintaan A yang diblokir",
|
||||
"blocking_ipv6_desc": "Alamat IP yang akan dikembalikan untuk permintaan AAAA yang diblokir",
|
||||
"blocking_mode_default": "Standar: Tanggapi dengan alamat IP nol (0.0.0.0 untuk A; :: untuk AAAA) saat diblokir oleh aturan gaya Adblock; tanggapi dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh aturan /etc/hosts-style",
|
||||
"blocking_mode_refused": "DITOLAK: Respon dengan kode DITOLAK",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
|
||||
@@ -621,8 +621,8 @@
|
||||
"check_not_found": "Tidak di temukan di daftar penyaringan anda",
|
||||
"client_confirm_block": "Apa anda yakin ingin mem-blokir klien ini \"{{ip}}\"?",
|
||||
"client_confirm_unblock": "Apa anda yakin ingin meng-unblock klien ini \"{{ip}}\"?",
|
||||
"client_blocked": "Klien \"{{ip}}\" sukses di blokir",
|
||||
"client_unblocked": "Klien \"{{ip}}\" sukses di unblock",
|
||||
"client_blocked": "Klien \"{{ip}}\" berhasil diblokir",
|
||||
"client_unblocked": "Klien \"{{ip}}\" berhasil membuka blokir",
|
||||
"static_ip": "Alamat IP statis",
|
||||
"static_ip_desc": "AdGuard Home adalah server jadi perlu alamat IP statis agar berfungsi dengan benar. Jika tidak, pada titik tertentu, router Anda dapat menetapkan alamat IP yang berbeda untuk perangkat ini.",
|
||||
"set_static_ip": "Atur alamat IP statik",
|
||||
@@ -640,8 +640,8 @@
|
||||
"show_whitelisted_responses": "Dalam Daftar Putih",
|
||||
"show_processed_responses": "Terproses",
|
||||
"blocked_safebrowsing": "Diblokir oleh Penjelajahan Aman",
|
||||
"blocked_adult_websites": "Diblok oleh Kontrol Orang tua",
|
||||
"blocked_threats": "Blokir Ancaman",
|
||||
"blocked_adult_websites": "Diblokir oleh Kontrol Orang Tua",
|
||||
"blocked_threats": "Ancaman terblokir",
|
||||
"allowed": "Dibolehkan",
|
||||
"filtered": "Tersaring",
|
||||
"rewritten": "Tulis ulang",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Elenco dei server DNS fallback utilizzati quando i server DNS upstream non rispondono. La sintassi è la stessa del campo principale upstream sopra.",
|
||||
"fallback_dns_placeholder": "Inserisci un server DNS fallback per riga",
|
||||
"local_ptr_title": "Server DNS privati inversi",
|
||||
"local_ptr_desc": "I server DNS che AdGuard Home utilizza per le richieste PTR locali. Questi server vengono utilizzati per risolvere i nomi host dei client con indirizzi IP privati, ad esempio \"192.168.12.34\", utilizzando il DNS inverso. Se non è impostato, AdGuard Home utilizzerà gli indirizzi dei resolutori DNS predefiniti del tuo sistema operativo ad eccezione degli indirizzi di AdGuard Home stesso.",
|
||||
"local_ptr_desc": "I server DNS usati da AdGuard Home per richieste private PTR, SOA e NS. Una richiesta è considerata privata se richiede un dominio ARPA contenente una sottorete all'interno di intervalli IP privati (come \"192.168.12.34\") e proviene da un client con un indirizzo IP privato. Se non impostato, saranno usati i risolutori DNS predefiniti del tuo sistema operativo, ad eccezione degli indirizzi IP di AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Per impostazione predefinita, AdGuard Home utilizzerà i seguenti risolutori DNS inversi: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home non è stato in grado di determinare i risolutori DNS inversi privati adatti per questo sistema.",
|
||||
"local_ptr_placeholder": "Inserisci un indirizzo IP per riga",
|
||||
"resolve_clients_title": "Attiva la risoluzione inversa degli indirizzi IP dei client",
|
||||
"resolve_clients_desc": "Risolve inversamente gli indirizzi IP dei client nei loro nomi host inviando richieste PTR ai risolutori corrispondenti (server DNS privati per client locali, server upstream per client con indirizzi IP pubblici).",
|
||||
"use_private_ptr_resolvers_title": "Utilizza dei resolver rDNS privati",
|
||||
"use_private_ptr_resolvers_desc": "Esegue ricerche DNS inverse per indirizzi locali utilizzando questi server upstream. Se disattivata, AdGuard Home risponderà con NXDOMAIN a tutte le richieste PTR ad eccezione dei client noti da DHCP, /etc/hosts, e così via.",
|
||||
"use_private_ptr_resolvers_desc": "Risolvi le richieste PTR, SOA e NS per domini ARPA contenenti indirizzi IP privati tramite server upstream privati, DHCP, /etc/hosts, ecc. Se disabilitato, AdGuard Home risponderà a tutte queste richieste con NXDOMAIN.",
|
||||
"check_dhcp_servers": "Controlla la presenza di server DHCP",
|
||||
"save_config": "Salva configurazione",
|
||||
"enabled_dhcp": "Server DHCP attivo",
|
||||
@@ -341,7 +341,7 @@
|
||||
"list_label": "Elenco",
|
||||
"unknown_filter": "Filtro sconosciuto {{filterId}}",
|
||||
"known_tracker": "Tracciatore noto",
|
||||
"install_welcome_title": "Benvenuto nella Home di AdGuard!",
|
||||
"install_welcome_title": "Benvenuto in AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home è un server DNS che blocca annunci e tracciatori a livello di rete. Il suo scopo è quello di permetterti il controllo dell'intera rete e di tutti i dispositivi, e non richiede l'utilizzo di un programma lato client.",
|
||||
"install_settings_title": "Interfaccia Web dell'Admin",
|
||||
"install_settings_listen": "Interfaccia d'ascolto",
|
||||
@@ -446,7 +446,7 @@
|
||||
"update_now": "Aggiorna ora",
|
||||
"update_failed": "Aggiornamento automatico non riuscito. Ti suggeriamo di <a>seguire questi passaggi</a> per aggiornare manualmente.",
|
||||
"manual_update": "Ti invitiamo a <a>seguire questi passaggi</a> per aggiornare manualmente.",
|
||||
"processing_update": "Perfavore aspetta, AdGuard Home si sta aggiornando",
|
||||
"processing_update": "Attendi per favore, AdGuard Home si sta aggiornando",
|
||||
"clients_title": "Client persistenti",
|
||||
"clients_desc": "Configura le registrazioni dei client persistenti per i dispositivi connessi ad AdGuard Home",
|
||||
"settings_global": "Globale",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Utilizza la chiave salvata in precedenza",
|
||||
"parental_control": "Controllo Parentale",
|
||||
"safe_browsing": "Navigazione Sicura",
|
||||
"served_from_cache": "{{value}} <i>(fornito dalla cache)</i>",
|
||||
"served_from_cache_label": "Servito dalla cache",
|
||||
"form_error_password_length": "La password deve contenere da {{min}} a {{max}} caratteri",
|
||||
"anonymizer_notification": "<0>Attenzione:</0> L'anonimizzazione dell'IP è abilitata. Puoi disabilitarla in <1>Impostazioni generali</1>.",
|
||||
"confirm_dns_cache_clear": "Sei sicuro di voler cancellare la cache DNS?",
|
||||
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "以前に保存したキーを使用する",
|
||||
"parental_control": "ペアレンタルコントロール",
|
||||
"safe_browsing": "セーフブラウジング",
|
||||
"served_from_cache": "{{value}} <i>(キャッシュから応答)</i>",
|
||||
"served_from_cache_label": "キャッシュからの配信:",
|
||||
"form_error_password_length": "パスワードの長さは{{min}}〜{{max}}文字にしてください。",
|
||||
"anonymizer_notification": "【<0>注意</0>】IPの匿名化が有効になっています。 <1>一般設定</1>で無効にできます。",
|
||||
"confirm_dns_cache_clear": "DNS キャッシュをクリアしてもよろしいですか?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "업스트림 DNS 서버가 응답하지 않을 때 사용되는 폴백 DNS 서버 목록입니다. 구문은 위의 기본 업스트림 필드와 동일합니다.",
|
||||
"fallback_dns_placeholder": "한 줄에 하나의 폴백 DNS 서버를 입력하세요.",
|
||||
"local_ptr_title": "프라이빗 역방향 DNS 서버",
|
||||
"local_ptr_desc": "AdGuard Home이 로컬 PTR 쿼리에 사용하는 DNS 서버입니다. 이러한 서버는 역방향 DNS를 사용하여 개인 IP 주소(예: '192.168.12.34')가 있는 클라이언트의 호스트 이름을 확인하는 데 사용됩니다. 설정되지 않은 경우, AdGuard Home은 AdGuard Home의 주소를 제외하고 운영 체제의 기본 DNS 리졸버의 주소를 사용합니다.",
|
||||
"local_ptr_desc": "AdGuard Home에서 비공개 PTR, SOA 및 NS 요청에 사용하는 DNS 서버입니다. 요청이 비공개 IP 범위 내의 서브넷(예: \"192.168.12.34\")을 포함하는 ARPA 도메인을 요청하고 비공개 IP 주소를 가진 클라이언트로부터 오는 경우 비공개로 간주됩니다. 설정하지 않으면 AdGuard Home IP 주소를 제외한 OS의 기본 DNS 리졸버가 사용됩니다.",
|
||||
"local_ptr_default_resolver": "기본적으로 AdGuard Home에서는 {{ip}} 역방향 DNS 서버를 이용합니다.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home에서 이 시스템에 적합한 사설 역방향 프라이빗 DNS 서버를 결정할 수 없습니다.",
|
||||
"local_ptr_placeholder": "한 줄에 하나씩 IP 주소를 입력하세요.",
|
||||
"resolve_clients_title": "클라이언트 IP 주소에 대한 호스트명 확인 활성화",
|
||||
"resolve_clients_desc": "해당 서버에 대한 PTR 쿼리를 통해 클라이언트의 도메인 이름을 정의합니다. (로컬 클라이언트의 경우 프라이빗 DNS 서버, 공용 IP 주소가 있는 클라이언트의 경우 업스트림 서버).",
|
||||
"use_private_ptr_resolvers_title": "프라이빗 역방향 DNS 리졸버 사용",
|
||||
"use_private_ptr_resolvers_desc": "업스트림 서버를 사용해 로컬로 제공되는 주소의 역방향 DNS를 조회합니다. 끄는 경우, AdGuard Home은 DHCP, /etc/hosts 등에서 알려진 클라이언트를 제외한 모든 PTR 요청에 NXDOMAIN으로 응답합니다.",
|
||||
"use_private_ptr_resolvers_desc": "사설 업스트림 서버, DHCP, / etc/hosts 등을 통해 사설 IP 주소가 포함된 ARPA 도메인에 대한 PTR, SOA 및 NS 요청을 처리합니다. 비활성화하면 AdGuard Home은 NXDOMAIN을 사용하여 이러한 모든 요청에 응답합니다.",
|
||||
"check_dhcp_servers": "DHCP 서버 체크",
|
||||
"save_config": "구성 저장",
|
||||
"enabled_dhcp": "DHCP 서버 활성화됨",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "이전에 저장했던 키 사용하기",
|
||||
"parental_control": "자녀 보호",
|
||||
"safe_browsing": "세이프 브라우징",
|
||||
"served_from_cache": "{{value}} <i>(캐시에서 제공)</i>",
|
||||
"served_from_cache_label": "캐시에서 가져옴",
|
||||
"form_error_password_length": "비밀번호는 {{min}}~{{max}}자 길이여야 합니다.",
|
||||
"anonymizer_notification": "<0>참고:</0> IP 익명화가 활성화되었습니다. <1>일반 설정</1>에서 비활성화할 수 있습니다.",
|
||||
"confirm_dns_cache_clear": "정말로 DNS 캐시를 지우시겠습니까?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lijst met DNS-back-up-noodservers die worden gebruikt wanneer upstream DNS-servers niet reageren. De syntaxis is hetzelfde als in het veld hoofdstroomopwaarts hierboven.",
|
||||
"fallback_dns_placeholder": "Voer één DNS-back-upserver per regel in",
|
||||
"local_ptr_title": "Private omgekeerde DNS-servers",
|
||||
"local_ptr_desc": "De DNS-servers die AdGuard Home gebruikt voor lokale PTR-zoekopdrachten. Deze servers worden gebruikt om PTR-verzoeken voor adressen in privé-IP-bereiken op te lossen, bijvoorbeeld \"192.168.12.34\", met behulp van reverse DNS. Indien niet ingesteld, gebruikt AdGuard Home de adressen van de standaard DNS-resolvers van uw besturingssysteem, behalve de adressen van AdGuard Home zelf.",
|
||||
"local_ptr_desc": "DNS-servers die door AdGuard Home worden gebruikt voor privé PTR-, SOA- en NS-verzoeken. Een verzoek wordt als privé beschouwd als het vraagt om een ARPA-domein dat een subnet binnen privé-IP-bereiken bevat (zoals \"192.168.12.34\") en afkomstig is van een client met een privé-IP-adres. Indien niet ingesteld, zullen de standaard DNS-resolvers van je besturingssysteem worden gebruikt, behalve de AdGuard Home IP-adressen.",
|
||||
"local_ptr_default_resolver": "Standaard gebruikt AdGuard Home de volgende omgekeerde DNS-resolvers: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home kon voor dit systeem geen geschikte private omgekeerde DNS-resolvers bepalen.",
|
||||
"local_ptr_placeholder": "Voer één IP-adres per regel in",
|
||||
"resolve_clients_title": "Omzetten van hostnamen van clients inschakelen",
|
||||
"resolve_clients_desc": "Indien ingeschakeld, zal AdGuard Home proberen om IP-adressen van apparaten te converteren in hun hostnamen door PTR-verzoeken te sturen naar overeenkomstige resolvers (privé-DNS-servers voor lokale apparaten, upstream-server voor apparaten met een openbaar IP-adres).",
|
||||
"use_private_ptr_resolvers_title": "Private omgekeerde DNS-resolvers gebruiken",
|
||||
"use_private_ptr_resolvers_desc": "Omgekeerde DNS opzoekingen uitvoeren voor locale adressen door deze upstream servers te gebruiken. Indien uitgeschakeld, reageert AdGuard Home met NXDOMAIN op al dergelijke PTR-verzoeken, uitgezonderd voor apparaten gekend van DHCP, /etc/hosts, enz.",
|
||||
"use_private_ptr_resolvers_desc": "PTR-, SOA- en NS-verzoeken voor ARPA-domeinen die privé-IP-adressen bevatten oplossen via privé-upstreamservers, DHCP, /etc/hosts, enz. Indien uitgeschakeld, zal AdGuard Home op al dergelijke verzoeken reageren met NXDOMAIN.",
|
||||
"check_dhcp_servers": "Zoek achter DHCP servers",
|
||||
"save_config": "Configuratie opslaan",
|
||||
"enabled_dhcp": "DHCP server inschakelen",
|
||||
@@ -254,8 +254,8 @@
|
||||
"response_code": "Reactiecode",
|
||||
"client_table_header": "Gebruiker",
|
||||
"empty_response_status": "Leeg",
|
||||
"show_all_filter_type": "Toon alles",
|
||||
"show_filtered_type": "Toon gefilterde",
|
||||
"show_all_filter_type": "Alles weergeven",
|
||||
"show_filtered_type": "Gefilterde weergeven",
|
||||
"no_logs_found": "Geen logboeken gevonden",
|
||||
"refresh_btn": "Verversen",
|
||||
"previous_btn": "Vorige",
|
||||
@@ -532,7 +532,7 @@
|
||||
"blocked_services_global": "Gebruik algemeen geblokkeerde services",
|
||||
"blocked_service": "Geblokkeerde service",
|
||||
"block_all": "Blokkeer alles",
|
||||
"unblock_all": "Deblokkeer alles",
|
||||
"unblock_all": "Alles deblokkeren",
|
||||
"encryption_certificate_path": "Certificaat pad",
|
||||
"encryption_private_key_path": "Privé sleutel pad",
|
||||
"encryption_certificates_source_path": "Certificaten bestandspad instellen",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "De eerder opgeslagen sleutel gebruiken",
|
||||
"parental_control": "Ouderlijk toezicht",
|
||||
"safe_browsing": "Veilig browsen",
|
||||
"served_from_cache": "{{value}} <i>(geleverd vanuit cache)</i>",
|
||||
"served_from_cache_label": "Geleverd vanuit cache",
|
||||
"form_error_password_length": "Wachtwoord moet {{min}} tot {{max}} tekens lang zijn",
|
||||
"anonymizer_notification": "<0>Opmerking:</0> IP-anonimisering is ingeschakeld. Je kunt het uitschakelen in <1>Algemene instellingen</1>.",
|
||||
"confirm_dns_cache_clear": "Weet je zeker dat je de DNS-cache wilt wissen?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista rezerwowych serwerów DNS używanych, gdy nadrzędne serwery DNS nie odpowiadają. Składnia jest taka sama jak w głównym polu powyżej.",
|
||||
"fallback_dns_placeholder": "Wprowadź jeden rezerwowy serwer DNS w każdym wierszu",
|
||||
"local_ptr_title": "Prywatne odwrotne serwery DNS",
|
||||
"local_ptr_desc": "Serwery DNS, których AdGuard Home używa do lokalnych zapytań PTR. Serwery te są używane do rozpoznawania nazw hostów klientów z prywatnymi adresami IP, na przykład „192.168.12.34”, przy użyciu odwrotnego DNS. Jeśli nie jest ustawiona, AdGuard Home używa adresów domyślnych resolwerów DNS systemu operacyjnego, z wyjątkiem adresów samego AdGuard Home.",
|
||||
"local_ptr_desc": "Serwery DNS używane przez AdGuard Home do prywatnych żądań PTR, SOA i NS. Żądanie jest uważane za prywatne, jeśli prosi o domenę ARPA zawierającą podsieć w prywatnym zakresie adresów IP (np. „192.168.12.34”) i pochodzi od klienta z prywatnym adresem IP. Jeśli nie zostanie ustawione, zostaną użyte domyślne programy rozpoznawania nazw DNS Twojego systemu operacyjnego, z wyjątkiem domowych adresów IP AdGuard.",
|
||||
"local_ptr_default_resolver": "Domyślnie AdGuard Home używa następujących odwrotnych resolwerów DNS: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nie mógł określić odpowiednich prywatnych resolwerów DNS dla tego systemu.",
|
||||
"local_ptr_placeholder": "Wprowadź po jednym adresie IP w każdym wierszu",
|
||||
"resolve_clients_title": "Włącz odwrotne rozpoznawanie adresów IP klientów",
|
||||
"resolve_clients_desc": "Odwróć adresy IP klientów na ich nazwy hostów, wysyłając zapytania PTR do odpowiednich programów tłumaczących (prywatne serwery DNS dla klientów lokalnych, serwery nadrzędne dla klientów z publicznymi adresami IP).",
|
||||
"use_private_ptr_resolvers_title": "Użyj prywatnych odwrotnych resolwerów DNS",
|
||||
"use_private_ptr_resolvers_desc": "Wykonuj odwrotne wyszukiwania DNS dla adresów obsługiwanych lokalnie przy użyciu tych serwerów nadrzędnych. Po wyłączeniu AdGuard Home odpowiada za pomocą NXDOMAIN na wszystkie takie żądania PTR, z wyjątkiem klientów znanych z DHCP, /etc/hosts i tak dalej.",
|
||||
"use_private_ptr_resolvers_desc": "Rozwiązuj żądania PTR, SOA i NS dla domen ARPA zawierających prywatne adresy IP za pośrednictwem prywatnych serwerów nadrzędnych, DHCP, /etc/hosts itp. Jeśli ta opcja jest wyłączona, AdGuard Home będzie odpowiadać na wszystkie takie żądania za pomocą NXDOMAIN.",
|
||||
"check_dhcp_servers": "Sprawdź serwery DHCP",
|
||||
"save_config": "Zapisz konfigurację",
|
||||
"enabled_dhcp": "Serwer DHCP włączony",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Użyj wcześniej zapisanego klucza",
|
||||
"parental_control": "Kontrola rodzicielska",
|
||||
"safe_browsing": "Bezpieczne przeglądanie",
|
||||
"served_from_cache": "{{value}} <i>(podawane z pamięci podręcznej)</i>",
|
||||
"served_from_cache_label": "Podano z pamięci podręcznej",
|
||||
"form_error_password_length": "Hasło musi zawierać od {{min}} do {{max}} znaków",
|
||||
"anonymizer_notification": "<0>Uwaga:</0> Anonimizacja IP jest włączona. Możesz ją wyłączyć w <1>Ustawieniach ogólnych</1>.",
|
||||
"confirm_dns_cache_clear": "Czy na pewno chcesz wyczyścić pamięć podręczną DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS Fallback usados quando os servidores DNS primários não estão respondendo. A sintaxe é a mesma dos campos de servidores principais na seção acima.",
|
||||
"fallback_dns_placeholder": "Insira um servidor DNS fallback por linha",
|
||||
"local_ptr_title": "Servidores DNS reversos privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home utiliza para consultas privadas de PTR, SOA e NS. A solicitação é considerada privada se solicitar um domínio ARPA contendo uma sub-rede dentro de intervalos de IP privados, por exemplo \"192.168.12.34\", e vier de um cliente com endereço privado. Se não for definido, o AdGuard Home usará os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por padrão, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
|
||||
"local_ptr_placeholder": "Insira um endereço IP por linha",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados",
|
||||
"use_private_ptr_resolvers_desc": "Execute pesquisas reversas de DNS para endereços servidos localmente usando esses servidores DNS primário. Se desativado, o AdGuard Home responde com NXDOMAIN a todas essas solicitações PTR, exceto para clientes conhecidos de DHCP, /etc/hosts e assim por diante.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver solicitações PTR, SOA e NS para domínios ARPA contendo endereços privados usando servidores upstream privados, DHCP, /etc/hosts e assim por diante. Se desativado, o AdGuard Home responde a todas essas consultas com NXDOMAIN.",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Salvar configuração",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Use a chave salva anteriormente",
|
||||
"parental_control": "Controle parental",
|
||||
"safe_browsing": "Navegação segura",
|
||||
"served_from_cache": "{{value}} <i>(servido do cache)</i>",
|
||||
"served_from_cache_label": "Servido a partir do cache",
|
||||
"form_error_password_length": "A senha deve ter entre {{min}} e {{max}} caracteres",
|
||||
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-lo em <1>Configurações gerais</1>.",
|
||||
"confirm_dns_cache_clear": "Tem certeza de que deseja limpar o cache DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Lista de servidores DNS de fallback usados quando os servidores DNS upstream não estão respondendo. A sintaxe é a mesma do campo principal de upstreams acima.",
|
||||
"fallback_dns_placeholder": "Insira um servidor DNS de fallback por linha",
|
||||
"local_ptr_title": "Servidores DNS reversos privados",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home usa para consultas PTR locais. Esses servidores são usados para resolver solicitações de PTR para endereços em intervalos de IP privados, por exemplo \"192.168.12.34\", usando DNS reverso. Se não estiver definido, o AdGuard Home usa os endereços dos resolvedores de DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_desc": "Os servidores DNS que o AdGuard Home utiliza para consultas privadas de PTR, SOA e NS. A solicitação é considerada privada se solicitar um domínio ARPA contendo uma sub-rede dentro de intervalos de IP privados, por exemplo \"192.168.12.34\", e vier de um cliente com endereço privado. Se não for definido, o AdGuard Home usará os endereços dos resolvedores DNS padrão do seu sistema operacional, exceto os endereços do próprio AdGuard Home.",
|
||||
"local_ptr_default_resolver": "Por predefinição, o AdGuard Home usa os seguintes resolvedores de DNS reverso: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "A página inicial do AdGuard não conseguiu determinar resolvedores DNS reversos privados adequados para este sistema.",
|
||||
"local_ptr_placeholder": "Insira um endereço IP por linha",
|
||||
"resolve_clients_title": "Ativar resolução reversa de endereços IP de clientes",
|
||||
"resolve_clients_desc": "Resolva reversamente os endereços IP dos clientes em seus nomes de host, enviando consultas PTR aos resolvedores correspondentes (servidores DNS privados para clientes locais, servidores upstream para clientes com endereços IP públicos).",
|
||||
"use_private_ptr_resolvers_title": "Usar resolvedores DNS reversos privados",
|
||||
"use_private_ptr_resolvers_desc": "Execute pesquisas reversas de DNS para endereços servidos localmente usando esses servidores DNS primário. Se desativado, o AdGuard Home responde com NXDOMAIN a todas essas solicitações PTR, exceto para clientes conhecidos de DHCP, /etc/hosts e assim por diante.",
|
||||
"use_private_ptr_resolvers_desc": "Resolver solicitações PTR, SOA e NS para domínios ARPA contendo endereços privados usando servidores upstream privados, DHCP, /etc/hosts e assim por diante. Se desativado, o AdGuard Home responde a todas essas consultas com NXDOMAIN.",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Guardar definição",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Use a chave guardada anteriormente",
|
||||
"parental_control": "Controlo parental",
|
||||
"safe_browsing": "Navegação segura",
|
||||
"served_from_cache": "{{value}} <i>(servido do cache)</i>",
|
||||
"served_from_cache_label": "Servido a partir do cache",
|
||||
"form_error_password_length": "A palavra-passe deve ter {{min}} a {{max}} caracteres",
|
||||
"anonymizer_notification": "<0>Observação:</0> A anonimização de IP está ativada. Você pode desativá-la em <1>Definições gerais</1>.",
|
||||
"confirm_dns_cache_clear": "Tem certeza de que quer limpar a cache DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Список резервных DNS-серверов, используемых в тех случаях, когда вышестоящие DNS-серверы недоступны. Синтаксис такой же, как и в поле Upstream DNS-серверы выше.",
|
||||
"fallback_dns_placeholder": "Введите один резервный DNS-сервер в каждой строке",
|
||||
"local_ptr_title": "Приватные серверы для обратного DNS",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью обратного DNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.",
|
||||
"local_ptr_desc": "DNS-серверы, которые AdGuard Home использует для локальных PTR, SOA и NS-запросов. Запрос считается локальным, если он запрашивает информацию об ARPA-домене, подсеть которого в локальном IP-диапазоне (например, «192.168.12.34»), и если при этом запрос пришел от клиента с локальным адресом. Если значение не установлено, AdGuard Home использует адреса DNS-серверы по умолчанию в вашей ОС, за исключением адресов самого AdGuard Home.",
|
||||
"local_ptr_default_resolver": "По умолчанию AdGuard Home использует следующие обратные DNS-резолверы: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home не смог определить подходящие приватные обратные DNS-резолверы для этой системы.",
|
||||
"local_ptr_placeholder": "Введите по одному адресу на строчку",
|
||||
"resolve_clients_title": "Включить запрашивание доменных имён для IP-адресов клиентов",
|
||||
"resolve_clients_desc": "Определять доменные имена клиентов через PTR-запросы к соответствующим серверам (приватные DNS-серверы для локальных клиентов, upstream-серверы для клиентов с публичным IP-адресом).",
|
||||
"use_private_ptr_resolvers_title": "Использовать приватные обратные DNS-резолверы",
|
||||
"use_private_ptr_resolvers_desc": "Посылать обратные DNS-запросы для локально обслуживаемых адресов на указанные серверы. Если отключено, AdGuard Home будет отвечать NXDOMAIN на все подобные PTR-запросы, кроме запросов о клиентах, уже известных по DHCP, /etc/hosts и так далее.",
|
||||
"use_private_ptr_resolvers_desc": "Посылать PTR, SOA и NS-запросы для ARPA-доменов, содержащих локальные адреса, с помощью указанных upstream-серверов, DHCP, /etc/hosts и так далее. Если отключено, AdGuard Home отвечает NXDOMAIN на все подобные запросы.",
|
||||
"check_dhcp_servers": "Проверить DHCP-серверы",
|
||||
"save_config": "Сохранить конфигурацию",
|
||||
"enabled_dhcp": "DHCP-сервер включён",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Использовать сохранённый ранее ключ",
|
||||
"parental_control": "Родительский контроль",
|
||||
"safe_browsing": "Безопасный интернет",
|
||||
"served_from_cache": "{{value}} <i>(получено из кеша)</i>",
|
||||
"served_from_cache_label": "Получено из кеша",
|
||||
"form_error_password_length": "Пароль должен содержать от {{min}} до {{max}} символов",
|
||||
"anonymizer_notification": "<0>Внимание:</0> включена анонимизация IP-адресов. Вы можете отключить её в разделе <1>Основные настройки</1>.",
|
||||
"confirm_dns_cache_clear": "Вы уверены, что хотите очистить кеш DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Zoznam záložných serverov DNS, ktoré sa používajú, keď nadradený servery DNS neodpovedajú. Syntax je rovnaká ako v hlavnom poli vyššie.",
|
||||
"fallback_dns_placeholder": "Zadajte jeden záložný server DNS na riadok",
|
||||
"local_ptr_title": "Súkromné reverzné DNS servery",
|
||||
"local_ptr_desc": "DNS servery, ktoré AdGuard Home používa pre miestne PTR dopyty. Tieto servery sa používajú na rozlíšenie názvov hostiteľov klientov so súkromnými adresami IP, napríklad \"192.168.12.34\", pomocou reverzného DNS. Ak nie je nastavené inak, AdGuard Home použije adresy predvolených prekladačov DNS Vášho operačného systému okrem adries samotného AdGuard Home.",
|
||||
"local_ptr_desc": "Servery DNS, ktoré používa AdGuard Home na súkromné dopyty PTR, SOA a NS. Dopyt sa považuje za súkromný, ak požaduje doménu ARPA obsahujúcu podsieť v rozsahu súkromnej IP adresy (napríklad „192.168.12.34“) a pochádza od klienta so súkromnou IP adresou. Ak nie je nastavené, použijú sa predvolené DNS resolvery Vášho operačného systému, okrem AdGuard Home IP adries.",
|
||||
"local_ptr_default_resolver": "V predvolenom nastavení používa AdGuard Home nasledujúce reverzné DNS prekladače: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home nemohol určiť vhodné súkromné reverzné DNS prekladače pre tento systém.",
|
||||
"local_ptr_placeholder": "Na každý riadok zadajte IP adresu jedného servera",
|
||||
"resolve_clients_title": "Povoliť spätný preklad IP adries klientov",
|
||||
"resolve_clients_desc": "Reverzne rozlišuje adresy IP klientov na ich názvy hostiteľov odosielaním PTR dopytov príslušným prekladačom (súkromné DNS servery pre miestnych klientov, servery typu upstream pre klientov s verejnými IP adresami).",
|
||||
"use_private_ptr_resolvers_title": "Použiť súkromné reverzné DNS resolvery",
|
||||
"use_private_ptr_resolvers_desc": "Realizuje reverzné vyhľadávanie DNS pre lokálne adresy pomocou týchto upstream serverov. Ak je funkcia vypnutá, Adguard Home reaguje s NXDOMAIN na všetky takéto PTR dopyty okrem klientov známych z DHCP, /etc/hosts, a tak ďalej.",
|
||||
"use_private_ptr_resolvers_desc": "Riešenie dopytov PTR, SOA a NS pre domény ARPA obsahujúce súkromné IP adresy prostredníctvom súkromných upstream serverov, DHCP, /etc/hosts atď. Ak je vypnuté, AdGuard Home bude na všetky takéto dopyty odpovedať pomocou NXDOMAIN.",
|
||||
"check_dhcp_servers": "Skontrolovať DHCP servery",
|
||||
"save_config": "Uložiť konfiguráciu",
|
||||
"enabled_dhcp": "DHCP server zapnutý",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Použiť predtým uložený kľúč",
|
||||
"parental_control": "Rodičovská kontrola",
|
||||
"safe_browsing": "Bezpečné prehliadanie",
|
||||
"served_from_cache": "{{value}} <i>(prevzatá z cache pamäte)</i>",
|
||||
"served_from_cache_label": "Prevzaté z cache pamäte",
|
||||
"form_error_password_length": "Heslo musí mať od {{min}} do {{max}} znakov",
|
||||
"anonymizer_notification": "<0>Poznámka:</0> Anonymizácia IP je zapnutá. Môžete ju vypnúť vo <1>Všeobecných nastaveniach</1>.",
|
||||
"confirm_dns_cache_clear": "Naozaj chcete vymazať vyrovnávaciu pamäť DNS?",
|
||||
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Uporabi prej shranjeni ključ",
|
||||
"parental_control": "Starševski nadzor",
|
||||
"safe_browsing": "Varno brskanje",
|
||||
"served_from_cache": "{{value}} <i>(postreženo iz predpomnilnika)</i>",
|
||||
"served_from_cache_label": "Dostavljeno iz predpomnilnika",
|
||||
"form_error_password_length": "Geslo mora vsebovati od {{min}} do {{max}} znakov",
|
||||
"anonymizer_notification": "<0>Opomba:</0> Anonimizacija IP je omogočena. Onemogočite ga lahko v <1>Splošnih nastavitvah</1>.",
|
||||
"confirm_dns_cache_clear": "Ali ste prepričani, da želite počistiti predpomnilnik DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "Yukarı akış DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Söz dizimi yukarıdaki ana üst kaynak alanıyla aynıdır.",
|
||||
"fallback_dns_placeholder": "Her satıra bir yedek DNS sunucusu girin",
|
||||
"local_ptr_title": "Özel ters DNS sunucuları",
|
||||
"local_ptr_desc": "AdGuard Home'un yerel PTR sorguları için kullandığı DNS sunucuları. Bu sunucular, rDNS kullanarak, örneğin \"192.168.12.34\" gibi özel IP aralıklarındaki adresler için PTR isteklerini çözmek için kullanılır. Ayarlanmadığı durumda AdGuard Home, işletim sisteminizin varsayılan DNS çözümleme adreslerini kullanır.",
|
||||
"local_ptr_desc": "AdGuard Home tarafından özel PTR, SOA ve NS istekleri için kullanılan DNS sunucuları. Bir istek, özel IP aralıkları (\"192.168.12.34\" gibi) içinde bir alt ağ içeren bir ARPA alan adı ister ve özel IP adresine sahip bir istemciden gelirse özel olarak kabul edilir. Ayarlanmadığı durumda AdGuard Home, IP adresleri dışında işletim sisteminizin varsayılan DNS çözümleyicileri kullanılır.",
|
||||
"local_ptr_default_resolver": "AdGuard Home, varsayılan olarak aşağıdaki ters DNS çözümleyicilerini kullanır: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home, bu sistem için uygun olan özel ters DNS çözümleyicilerini belirleyemedi.",
|
||||
"local_ptr_placeholder": "Her satıra bir IP adresi girin",
|
||||
"resolve_clients_title": "İstemcilerin IP adreslerinin ters çözümlenmesini etkinleştir",
|
||||
"resolve_clients_desc": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunucuları) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözün.",
|
||||
"use_private_ptr_resolvers_title": "Özel ters DNS çözümleyicileri kullan",
|
||||
"use_private_ptr_resolvers_desc": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS aramaları yapın. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts, vb. tarafından bilinen istemciler dışında bu tür tüm PTR isteklerine NXDOMAIN ile yanıt verir.",
|
||||
"use_private_ptr_resolvers_desc": "Özel üst kaynak sunucuları, DHCP, /etc/hosts, vb. aracılığıyla özel IP adresleri içeren ARPA alan adları için PTR, SOA ve NS isteklerini çözümleyin. Devre dışı bırakılırsa, AdGuard Home bu tür tüm isteklere NXDOMAIN ile yanıt verir.",
|
||||
"check_dhcp_servers": "DHCP sunucularını denetle",
|
||||
"save_config": "Yapılandırmayı kaydet",
|
||||
"enabled_dhcp": "DHCP sunucusu etkinleştirildi",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Önceden kaydedilmiş anahtarı kullan",
|
||||
"parental_control": "Ebeveyn Denetimi",
|
||||
"safe_browsing": "Güvenli Gezinti",
|
||||
"served_from_cache": "{{value}} <i>(önbellekten kullanıldı)</i>",
|
||||
"served_from_cache_label": "Önbellekten kullanıldı",
|
||||
"form_error_password_length": "Parola {{min}} ila {{max}} karakter uzunluğunda olmalıdır",
|
||||
"anonymizer_notification": "<0>Not:</0> IP anonimleştirme etkinleştirildi. Bunu <1>Genel ayarlardan</1> devre dışı bırakabilirsiniz.",
|
||||
"confirm_dns_cache_clear": "DNS önbelleğini temizlemek istediğinizden emin misiniz?",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Назва вузла",
|
||||
"dhcp_table_expires": "Закінчується",
|
||||
"dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на під'єднаних пристроях!",
|
||||
"dhcp_warning": "Якщо ви однаково хочете увімкнути DHCP-сервер, переконайтеся, що у вашій мережі немає інших активних DHCP-серверів. Інакше, це може порушити роботу інтернету на підʼєднаних пристроях!",
|
||||
"dhcp_error": "AdGuard Home не зміг визначити, чи є в мережі інший DHCP-сервер",
|
||||
"dhcp_static_ip_error": "Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Нам не вдалося визначити, чи цей мережевий інтерфейс налаштовано для використання статичної IP-адреси. Встановіть статичну IP-адресу вручну.",
|
||||
"dhcp_dynamic_ip_found": "Ваша система використовує конфігурацію з динамічною IP-адресою для інтерфейсу <0>{{interfaceName}}</0>. Для використання DHCP-сервера необхідно встановити статичну IP-адресу. Ваша поточна IP-адреса <0>{{ipAddress}}</0>. Ми автоматично встановимо цю IP-адресу як статичну, якщо ви натиснете кнопку «Увімкнути DHCP-сервер».",
|
||||
@@ -364,7 +364,7 @@
|
||||
"install_submit_title": "Вітаємо!",
|
||||
"install_submit_desc": "Процедура налаштування завершена і тепер все готово, аби почати користуватися AdGuard Home.",
|
||||
"install_devices_router": "Роутер",
|
||||
"install_devices_router_desc": "Це налаштування буде автоматично охоплювати всі пристрої, що під'єднано до домашнього маршрутизатора. Вам не потрібно буде налаштовувати кожен з них вручну.",
|
||||
"install_devices_router_desc": "Це налаштування буде автоматично охоплювати всі пристрої, що підʼєднано до домашнього маршрутизатора. Вам не потрібно буде налаштовувати кожен з них вручну.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home прослуховує наступні адреси",
|
||||
"install_devices_router_list_1": "Відкрийте налаштування маршрутизатора. Зазвичай ви можете отримати до нього доступ із браузера за допомогою URL-адреси, наприклад, http://192.168.0.1/ або http://192.168.1.1/. Можливо, треба буде ввести пароль. Якщо ви його не знаєте, часто можна скинути пароль, натиснувши кнопку на самому маршрутизаторі. Для деяких маршрутизаторів потрібна спеціальна програма, яка в такому випадку повинна бути вже встановлена на вашому комп’ютері чи телефоні.",
|
||||
"install_devices_router_list_2": "Знайдіть налаштування DHCP/DNS. Шукайте літери DNS поруч із полем, в яке можна ввести два або три набори чисел, кожен з яких розбитий на чотири групи від однієї до трьох цифр.",
|
||||
@@ -448,7 +448,7 @@
|
||||
"manual_update": "Щоб оновити самостійно, <a>виконайте ці кроки</a>.",
|
||||
"processing_update": "Зачекайте будь ласка, AdGuard Home оновлюється",
|
||||
"clients_title": "Постійні клієнти",
|
||||
"clients_desc": "Налаштуйте пристрої, під'єднані до AdGuard Home",
|
||||
"clients_desc": "Налаштуйте пристрої, які підʼєднано до AdGuard Home",
|
||||
"settings_global": "Загальні",
|
||||
"settings_custom": "Власні",
|
||||
"table_client": "Клієнт",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "Використати раніше збережений ключ",
|
||||
"parental_control": "Батьківський контроль",
|
||||
"safe_browsing": "Безпечний перегляд",
|
||||
"served_from_cache": "{{value}} <i>(отримано з кешу)</i>",
|
||||
"served_from_cache_label": "Отримано з кешу",
|
||||
"form_error_password_length": "Пароль має містити від {{min}} до {{max}} символів",
|
||||
"anonymizer_notification": "<0>Примітка:</0> IP-анонімізацію ввімкнено. Ви можете вимкнути його в <1>Загальні налаштування</1> .",
|
||||
"confirm_dns_cache_clear": "Ви впевнені, що бажаєте очистити кеш DNS?",
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "当上游 DNS 服务器没有响应时使用的后备 DNS 服务器列表。语法与上面的「主要上游」字段相同。",
|
||||
"fallback_dns_placeholder": "每行输入一个后备 DNS 服务器",
|
||||
"local_ptr_title": "私人反向 DNS 服务器",
|
||||
"local_ptr_desc": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器。这些服务器将使用反向 DNS 解析具有私人 IP 地址的客户机的主机名,比如 \"192.168.12.34\"。如果没有设置,除非是 AdGuard Home 里设置的地址,AdGuard Home 都将自动使用您的操作系统的默认 DNS 解析器。",
|
||||
"local_ptr_desc": "AdGuard Home 用于私人 PTR、SOA 和 NS 请求的 DNS 服务器。如果请求的 ARPA 域名包含私有 IP 范围内的子网(如 \"192.168.12.34\"),且请求来自具有私有 IP 地址的客户端,该请求被视为私有请求。如果未设置,将使用操作系统的默认 DNS 解析器,AdGuard Home IP 地址除外。",
|
||||
"local_ptr_default_resolver": "AdGuard Home 默认使用下列反向 DNS 解析器: {{ip}}",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home 无法为这个系统确定合适的私人反向 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行输入一个 IP 地址",
|
||||
"resolve_clients_title": "启用客户端的 IP 地址的反向解析",
|
||||
"resolve_clients_desc": "通过发送 PTR 查询到对应的解析器 (本地客户端的私人 DNS 服务器,公共 IP 客户端的上游服务器) 将 IP 地址反向解析成其客户端主机名。",
|
||||
"use_private_ptr_resolvers_title": "使用私人反向 DNS 解析器",
|
||||
"use_private_ptr_resolvers_desc": "使用这些上游服务器对本地服务的地址执行反向 DNS 查找。 如果禁用,则 AdGuard Home 会以 NXDOMAIN 响应所有此类 PTR 请求,从 DHCP、/etc/hosts 等获知的客户端除外。",
|
||||
"use_private_ptr_resolvers_desc": "使用私有上游服务器、DHCP、/etc/hosts 等解决包含私有 IP 地址的 ARPA 域名的 PTR、SOA 和 NS 请求。如果禁用,AdGuard Home 将以 NXDOMAIN 回应所有此类请求。",
|
||||
"check_dhcp_servers": "检查 DHCP 服务器",
|
||||
"save_config": "保存配置",
|
||||
"enabled_dhcp": "DHCP 服务器已启用",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "使用之前保存的密钥",
|
||||
"parental_control": "家长控制",
|
||||
"safe_browsing": "安全浏览",
|
||||
"served_from_cache": "{{value}}<i>(由缓存提供)</i>",
|
||||
"served_from_cache_label": "从缓存中",
|
||||
"form_error_password_length": "密码长度必须为 {{min}} 到 {{max}} 个字符",
|
||||
"anonymizer_notification": "<0>注意:</0> IP 匿名化已启用。您可以在<1>常规设置</1>中禁用它。",
|
||||
"confirm_dns_cache_clear": "您确定要清除 DNS 缓存吗?",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"client_settings": "用戶端設定",
|
||||
"example_upstream_reserved": "您可以<0>指定網域</0>使用特定 DNS 查詢",
|
||||
"example_multiple_upstreams_reserved": "多個上游 <0>for 特定網域</0>;",
|
||||
"example_upstream_comment": "您可以指定註解",
|
||||
"upstream_parallel": "使用平行查詢,同時查詢所有上游伺服器來加速解析結果",
|
||||
"parallel_requests": "平行處理",
|
||||
@@ -8,6 +9,9 @@
|
||||
"load_balancing_desc": "一次只查詢一個伺服器。AdGuard Home 會使用加權隨機取樣來選擇使用的查詢結果,以確保速度最快的伺服器能被充分運用。",
|
||||
"bootstrap_dns": "引導(Boostrap) DNS 伺服器",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS 伺服器用於解析您所設定的上游 DoH/DoT 解析器的 IP 地址",
|
||||
"fallback_dns_title": "備用 DNS 伺服器",
|
||||
"fallback_dns_desc": "備用 DNS 伺服器列表:於主要 DNS 伺服器沒有回應時使用。語法與主要 DNS 伺服器設定欄位相同。",
|
||||
"fallback_dns_placeholder": "每行輸入一個備用 DNS 伺服器",
|
||||
"local_ptr_title": "私人 DNS 伺服器",
|
||||
"local_ptr_desc": "AdGuard Home 用於區域 PTR 查詢的 DNS 伺服器。這些伺服器將用於解析具有私人 IP 位址的用戶端的主機名稱,例如 \"192.168.12.34\",使用 rDNS。如果沒有設定,AdGuard Home 將自動使用您的系統預設 DNS 解析。",
|
||||
"local_ptr_default_resolver": "AdGuard Home 預設使用以下作為 DNS 反解器:{{ip}}",
|
||||
@@ -37,17 +41,19 @@
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 設定",
|
||||
"form_error_required": "必要欄位",
|
||||
"form_error_ip4_format": "無效的 IPv4 格式",
|
||||
"form_error_ip6_format": "無效的 IPv6 格式",
|
||||
"form_error_ip4_gateway_format": "閘道的 IPv4 位址無效",
|
||||
"form_error_ip_format": "無效的 IP 位址",
|
||||
"form_error_ip6_format": "無效的 IPv6 格式",
|
||||
"form_error_ip_format": "無效的 IP 格式",
|
||||
"form_error_mac_format": "無效的 「MAC 位址」格式",
|
||||
"form_error_client_id_format": "無效的「客戶端 ID」格式",
|
||||
"form_error_server_name": "無效伺服器名稱",
|
||||
"form_error_subnet": "子網路 \"{{cidr}}\" 不包含 IP 位址 \"{{ip}}\"",
|
||||
"form_error_positive": "數值必須大於 0",
|
||||
"form_error_gateway_ip": "租約不能使用閘道器的 IP 位址",
|
||||
"out_of_range_error": "必須介於 \"{{start}}\" - \"{{end}}\" 範圍之外",
|
||||
"lower_range_start_error": "必須小於起始值",
|
||||
"greater_range_start_error": "必須大於起始值",
|
||||
"subnet_error": "地址必須在同一個子網路中",
|
||||
"gateway_or_subnet_invalid": "無效子網路",
|
||||
"dhcp_form_gateway_input": "閘道 IP 位址",
|
||||
"dhcp_form_subnet_input": "子網路遮罩",
|
||||
@@ -68,7 +74,9 @@
|
||||
"dhcp_dynamic_ip_found": "您的網路介面 <0>{{interfaceName}}</0> 正在使用動態 IP,要使用 DHCP 伺服器必須指定靜態 IP 給 AdGuard。\n目前您的 IP 位址 <0>{{ipAddress}}</0>,啟用 DHCP 後此 IP 將自動設定為靜態 IP 位址。",
|
||||
"dhcp_lease_added": "靜態租用 \"{{key}}\" 已新增成功",
|
||||
"dhcp_lease_deleted": "靜態租用 \"{{key}}\" 已刪除成功",
|
||||
"dhcp_lease_updated": "靜態租約 \"{{key}}\" 已成功更新",
|
||||
"dhcp_new_static_lease": "新增靜態租用",
|
||||
"dhcp_edit_static_lease": "編輯靜態租約",
|
||||
"dhcp_static_leases_not_found": "找不到 DHCP 靜態租用",
|
||||
"dhcp_add_static_lease": "新增靜態租用",
|
||||
"dhcp_reset_leases": "重置所有 DHCP 租約",
|
||||
@@ -112,7 +120,8 @@
|
||||
"stats_malware_phishing": "已封鎖惡意軟體/網路釣魚",
|
||||
"stats_adult": "已封鎖成人網站",
|
||||
"stats_query_domain": "熱門查詢網域排行",
|
||||
"for_last_24_hours": "過去 24 小時",
|
||||
"for_last_hours": "在過去 {{count}} 小時",
|
||||
"for_last_hours_plural": "在過去 {{count}} 小時裡",
|
||||
"for_last_days": "最近 {{count}} 天內",
|
||||
"for_last_days_plural": "最近 {{count}} 天內",
|
||||
"stats_disabled": "已禁用統計資料。您可以從<0>設定頁面</0>打開它。",
|
||||
@@ -123,15 +132,20 @@
|
||||
"top_clients": "熱門用戶端排行",
|
||||
"no_clients_found": "找不到用戶端",
|
||||
"general_statistics": "一般統計資料",
|
||||
"top_upstreams": "熱門上游伺服器",
|
||||
"no_upstreams_data_found": "找不到上游數據",
|
||||
"number_of_dns_query_days": "過去 {{count}} 天內 DNS 查詢總數",
|
||||
"number_of_dns_query_days_plural": "過去 {{count}} 天內 DNS 查詢總數",
|
||||
"number_of_dns_query_24_hours": "過去 24小時內 DNS 查詢總數",
|
||||
"number_of_dns_query_hours": "過去 {{count}} 小時處理的 DNS 查詢數量",
|
||||
"number_of_dns_query_hours_plural": "過去 {{count}} 小時處理的 DNS 查詢數量",
|
||||
"number_of_dns_query_blocked_24_hours": "已被廣告過濾器與主機黑名單封鎖 DNS 查詢總數",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "已被 AdGuard 瀏覽安全模組封鎖的 DNS 查詢總數",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "已封鎖成人網站總數",
|
||||
"enforced_save_search": "強制使用安全搜尋",
|
||||
"number_of_dns_query_to_safe_search": "已強制使用安全搜尋總數",
|
||||
"average_processing_time": "平均的處理時間",
|
||||
"average_upstream_response_time": "平均上游伺服器回應時間",
|
||||
"response_time": "回應時間",
|
||||
"average_processing_time_hint": "處理 DNS 請求的平均時間(毫秒)",
|
||||
"block_domain_use_filters_and_hosts": "使用過濾器與 hosts 檔案阻擋網域查詢",
|
||||
"filters_block_toggle_hint": "您可在<a>過濾器</a>設定中設定封鎖規則。",
|
||||
@@ -156,6 +170,7 @@
|
||||
"upstream_dns_configured_in_file": "設定在 {{path}}",
|
||||
"test_upstream_btn": "測試上游 DNS",
|
||||
"upstreams": "上游",
|
||||
"upstream": "上游伺服器",
|
||||
"apply_btn": "套用",
|
||||
"disabled_filtering_toast": "已停用過濾",
|
||||
"enabled_filtering_toast": "已啟用過濾",
|
||||
@@ -164,6 +179,7 @@
|
||||
"disabled_parental_toast": "已停用家長監護",
|
||||
"enabled_parental_toast": "已啟用家長監護",
|
||||
"disabled_safe_search_toast": "已停用安全搜尋",
|
||||
"enabled_save_search_toast": "已啟用安全搜尋",
|
||||
"updated_save_search_toast": "已更新安全搜尋設定",
|
||||
"enabled_table_header": "啟用",
|
||||
"name_table_header": "名稱",
|
||||
@@ -206,24 +222,29 @@
|
||||
"example_comment_hash": "# Also a comment",
|
||||
"example_regex_meaning": "使用正規表示式(Regular Expression)來阻止對應的網域查詢",
|
||||
"example_upstream_regular": "一般 DNS(透過 UDP)",
|
||||
"example_upstream_regular_port": "一般 DNS(透過 UDP,連接埠)",
|
||||
"example_upstream_udp": "一般 DNS(透過 UDP,主機名稱)",
|
||||
"example_upstream_dot": "<0>DNS-over-TLS</0>(流量加密)",
|
||||
"example_upstream_doh": "<0>DNS-over-HTTPS</0>(流量加密)",
|
||||
"example_upstream_doh3": "使 DNS-over-HTTPS 強制使用 <0>HTTP/3</0> ,並禁止使用後備 HTTP/2 或更低版本;",
|
||||
"example_upstream_doq": "加密 <0>DNS-over-QUIC</0>",
|
||||
"example_upstream_sdns": "您可以使透過 <0>DNS Stamps</0> 來解析 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2>",
|
||||
"example_upstream_tcp": "一般 DNS(透過 TCP)",
|
||||
"example_upstream_regular_port": "一般 DNS(透過 UDP,連接埠)",
|
||||
"example_upstream_udp": "一般 DNS(透過 UDP,主機名稱)",
|
||||
"example_upstream_tcp_port": "一般 DNS(透過 TCP,連接埠)",
|
||||
"example_upstream_tcp_hostname": "一般 DNS(透過 TCP,主機名稱)",
|
||||
"all_lists_up_to_date_toast": "所有清單已更新至最新",
|
||||
"updated_upstream_dns_toast": "已更新上游 DNS 伺服器",
|
||||
"dns_test_ok_toast": "設定中的 DNS 上游運作正常",
|
||||
"dns_test_not_ok_toast": "DNS 設定中的 \"{{key}}\" 出現錯誤,請確認是否正確輸入",
|
||||
"dns_test_parsing_error_toast": "在 {{section}} 部分中:第 {{line}} 行:無法使用,請檢查您是否有正確地填寫",
|
||||
"dns_test_warning_toast": "上游伺服器 \"{{key}}\" 沒有回應測試請求,可能無法正常運作",
|
||||
"unblock": "解除封鎖",
|
||||
"block": "封鎖",
|
||||
"disallow_this_client": "不允許此用戶端",
|
||||
"allow_this_client": "允許此用戶端",
|
||||
"block_for_this_client_only": "僅封鎖此用戶端",
|
||||
"unblock_for_this_client_only": "僅解除封鎖此用戶端",
|
||||
"add_persistent_client": "加入到用戶端",
|
||||
"time_table_header": "時間",
|
||||
"date": "日期",
|
||||
"domain_name_table_header": "域名",
|
||||
@@ -270,6 +291,9 @@
|
||||
"custom_ip": "自訂 IP 位址",
|
||||
"blocking_ipv4": "封鎖 IPv4",
|
||||
"blocking_ipv6": "封鎖 IPv6",
|
||||
"blocked_response_ttl": "阻塞響應 TTL",
|
||||
"blocked_response_ttl_desc": "指定客戶端應快取過濾回應的秒數",
|
||||
"form_enter_blocked_response_ttl": "輸入已封鎖的回應 TTL(秒)",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
@@ -288,6 +312,16 @@
|
||||
"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_desc": "用於速率限制的 IPv4 位址的子網路前綴長度。 預設為 24",
|
||||
"rate_limit_subnet_len_ipv4_error": "IPv4 子網路前綴長度應介於 0 到 32 之間",
|
||||
"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_placeholder": "每行輸入一個 IP 地址",
|
||||
"blocking_ipv4_desc": "回覆指定 IPv4 位址給被封鎖的網域的 A 紀錄查詢",
|
||||
"blocking_ipv6_desc": "回覆指定 IPv6 位址給被封鎖的網域的 AAAA 紀錄查詢",
|
||||
"blocking_mode_default": "預設:被 Adblock 規則封鎖時回應零值的 IP 位址(A 紀錄回應 0.0.0.0 ,AAAA 紀錄回應 ::);被 /etc/hosts 規則封鎖時回應規則中指定 IP 位址",
|
||||
@@ -391,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 中的值",
|
||||
@@ -430,6 +467,7 @@
|
||||
"form_add_id": "新增識別碼",
|
||||
"form_client_name": "輸入用戶端名稱",
|
||||
"name": "名稱",
|
||||
"client_name": "客戶端 {{id}}",
|
||||
"client_global_settings": "使用全域設定",
|
||||
"client_deleted": "已刪除「{{key}}」",
|
||||
"client_added": "已新增「{{key}}」",
|
||||
@@ -451,6 +489,7 @@
|
||||
"updates_checked": "檢查更新成功",
|
||||
"updates_version_equal": "AdGuard Home 是最新的版本",
|
||||
"check_updates_now": "立即檢查更新",
|
||||
"version_request_error": "更新檢查失敗。請檢查您的網絡連線。",
|
||||
"dns_privacy": "DNS 隱私",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0>使用 <1>{{address}}</1>。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0>使用 <1>{{address}}</1>。",
|
||||
@@ -471,6 +510,7 @@
|
||||
"setup_dns_notice": "要使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您必須先在 AdGuard Home 完成 <0>加密設定</0>。",
|
||||
"rewrite_added": "「{{key}}」的 DNS 覆寫新增成功",
|
||||
"rewrite_deleted": "「{{key}}」的 DNS 覆寫刪除成功",
|
||||
"rewrite_updated": "已更新 DNS 覆寫",
|
||||
"rewrite_add": "新增 DNS 覆寫",
|
||||
"rewrite_edit": "編輯 DNS 覆寫",
|
||||
"rewrite_not_found": "找不到 DNS 覆寫",
|
||||
@@ -522,6 +562,8 @@
|
||||
"statistics_enable": "啟用統計數據",
|
||||
"ignore_domains": "已忽略網域(每行一個)",
|
||||
"ignore_domains_title": "已忽略網域",
|
||||
"ignore_domains_desc_stats": "符合這些規則的查詢不會被計入統計資料中",
|
||||
"ignore_domains_desc_query": "符合這些規則的查詢不會被寫入查詢記錄中",
|
||||
"interval_hours": "{{count}} 小時",
|
||||
"interval_hours_plural": "{{count}} 小時",
|
||||
"filters_configuration": "過濾器設定",
|
||||
@@ -631,10 +673,19 @@
|
||||
"click_to_view_queries": "按一下以檢視查詢結果",
|
||||
"port_53_faq_link": "連接埠 53 經常被「DNSStubListener」或「systemd-resolved」服務佔用。請閱讀下列有關解決<0>這個問題</0>的說明",
|
||||
"adg_will_drop_dns_queries": "AdGuard Home 將停止回應此用戶端的所有 DNS 查詢。",
|
||||
"filter_allowlist": "警告:此操作同時會將規則 \"{{disallowed_rule}}\" 從允許的客戶端清單中排除。",
|
||||
"last_rule_in_allowlist": "無法停用此客戶端,因為排除規則「{{disallowed_rule}}」會導致「允許的用戶端」清單停用。",
|
||||
"use_saved_key": "使用先前儲存的鍵",
|
||||
"parental_control": "家長監護",
|
||||
"safe_browsing": "安全瀏覽",
|
||||
"served_from_cache": "{{value}} <i>(由快取回應)</i>",
|
||||
"served_from_cache_label": "由快取回應",
|
||||
"form_error_password_length": "密碼必須至少 {{value}} 個字元長度",
|
||||
"anonymizer_notification": "<0>注意</0>: 已啟用 IP 去識別化。您可以在<1>一般設定</1>中停用它。",
|
||||
"confirm_dns_cache_clear": "您確定要清除 DNS 快取嗎?",
|
||||
"cache_cleared": "DNS 快取成功清除",
|
||||
"clear_cache": "清除快取",
|
||||
"make_static": "新增為靜態",
|
||||
"theme_auto_desc": "自動(根據裝置調整)",
|
||||
"theme_dark_desc": "深色主題",
|
||||
"theme_light_desc": "淺色主題",
|
||||
"disable_for_seconds": "{{count}} 秒",
|
||||
@@ -649,11 +700,48 @@
|
||||
"disable_notify_for_minutes": "暫停防護 {{count}} 分鐘",
|
||||
"disable_notify_for_minutes_plural": "暫停防護 {{count}} 分鐘",
|
||||
"disable_notify_for_hours": "暫停防護 {{count}} 小時",
|
||||
"disable_notify_for_hours_plural": "停用保護 {{count}} 小時",
|
||||
"disable_notify_until_tomorrow": "停用保護直至明天",
|
||||
"enable_protection_timer": "保護功能將在 {{time}} 啟用",
|
||||
"custom_retention_input": "輸入保存時長(單位:小時)",
|
||||
"custom_rotation_input": "請輸入輪替週期(單位:小時)",
|
||||
"protection_section_label": "保護",
|
||||
"log_and_stats_section_label": "查詢日誌與統計資料",
|
||||
"ignore_query_log": "在查詢日誌中忽略此客戶端",
|
||||
"ignore_statistics": "在統計資料中忽略此客戶端",
|
||||
"schedule_services": "暫停服務封鎖",
|
||||
"schedule_services_desc": "設定服務封鎖過濾器的暫停排程",
|
||||
"schedule_services_desc_client": "針對此用戶端,設定服務阻擋的暫停排程",
|
||||
"schedule_desc": "設定已封鎖服務的閒置時段",
|
||||
"schedule_invalid_select": "開始時間必須在結束時間之前",
|
||||
"schedule_select_days": "選擇天數",
|
||||
"schedule_timezone": "選擇時區",
|
||||
"schedule_current_timezone": "目前時區:{{value}}",
|
||||
"schedule_time_all_day": "全天",
|
||||
"schedule_modal_description": "這個排程將會取代同一星期中所有現有的排程。每一天只能有一個閒置時段。",
|
||||
"schedule_modal_time_off": "沒有封鎖服務:",
|
||||
"schedule_new": "新排程",
|
||||
"schedule_edit": "編輯排程",
|
||||
"schedule_save": "儲存排程",
|
||||
"schedule_add": "新增排程",
|
||||
"schedule_remove": "移除排程",
|
||||
"schedule_from": "從",
|
||||
"schedule_to": "至",
|
||||
"sunday": "星期日",
|
||||
"monday": "星期一",
|
||||
"tuesday": "星期二",
|
||||
"wednesday": "星期三",
|
||||
"thursday": "星期四",
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"sunday_short": "週日",
|
||||
"monday_short": "週一",
|
||||
"tuesday_short": "週二",
|
||||
"wednesday_short": "週三",
|
||||
"thursday_short": "週四",
|
||||
"friday_short": "週五",
|
||||
"saturday_short": "週六"
|
||||
"saturday_short": "週六",
|
||||
"upstream_dns_cache_configuration": "上游 DNS 快取設定",
|
||||
"enable_upstream_dns_cache": "為此客戶端的自訂上游設定啟用 DNS 快取",
|
||||
"dns_cache_size": "DNS 快取大小(bytes)"
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"fallback_dns_desc": "當上游 DNS 伺服器未回覆時被使用的應變 DNS 伺服器之清單。此語法與在上面主要上游欄位中的相同。",
|
||||
"fallback_dns_placeholder": "每行輸入一個應變 DNS 伺服器",
|
||||
"local_ptr_title": "私人反向的 DNS 伺服器",
|
||||
"local_ptr_desc": "AdGuard Home 用於區域指標(PTR)查詢之 DNS 伺服器。這些伺服器被用於解析有關在私人 IP 範圍的位址之區域指標查詢,例如,\"192.168.12.34\",使用反向的 DNS。如果未被設定,AdGuard Home 使用您的作業系統之預設 DNS 解析器的位址。",
|
||||
"local_ptr_desc": "AdGuard Home 使用的 DNS 伺服器用於私人 PTR、SOA 和 NS 請求。如果請求要求包含私人 IP 範圍內的子網域的 ARPA 網域(例如 \"192.168.12.34\"),並來自具有私人 IP 位址的用戶端,該請求被視為私人。如果未設定,將使用您的作業系統的預設 DNS 解析器,但不包括 AdGuard Home 的 IP 位址。",
|
||||
"local_ptr_default_resolver": "預設下,AdGuard Home 使用以下反向的 DNS 解析器:{{ip}}。",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home 無法為此系統決定合適的私人反向的 DNS 解析器。",
|
||||
"local_ptr_placeholder": "每行輸入一個 IP 位址",
|
||||
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
|
||||
"resolve_clients_desc": "透過傳送指標(PTR)查詢到對應的解析器(私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
|
||||
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
|
||||
"use_private_ptr_resolvers_desc": "對於正使用這些上游伺服器之區域服務的位址執行反向的 DNS 查找。如果被禁用,除已知來自 DHCP、/etc/hosts 等等的用戶端之外,AdGuard Home 對於所有此類的區域指標(PTR)請求以 NXDOMAIN 回覆。",
|
||||
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果禁用,AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
|
||||
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
||||
"save_config": "儲存配置",
|
||||
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
||||
@@ -678,7 +678,7 @@
|
||||
"use_saved_key": "使用該先前已儲存的金鑰",
|
||||
"parental_control": "家長控制",
|
||||
"safe_browsing": "安全瀏覽",
|
||||
"served_from_cache": "{{value}} <i>(由快取提供)</i>",
|
||||
"served_from_cache_label": "從快取中",
|
||||
"form_error_password_length": "密碼長度必須為 {{min}} 到 {{max}} 個字符",
|
||||
"anonymizer_notification": "<0>注意:</0>IP 匿名化被啟用。您可在<1>一般設定</1>中禁用它。",
|
||||
"confirm_dns_cache_clear": "您確定您想要清除 DNS 快取嗎?",
|
||||
|
||||
@@ -55,6 +55,12 @@ const Dashboard = ({
|
||||
return t('stats_disabled_short');
|
||||
}
|
||||
|
||||
const msIn7Days = 604800000;
|
||||
|
||||
if (stats.timeUnits === TIME_UNITS.HOURS && stats.interval === msIn7Days) {
|
||||
return t('for_last_days', { count: msToDays(stats.interval) });
|
||||
}
|
||||
|
||||
return stats.timeUnits === TIME_UNITS.HOURS
|
||||
? t('for_last_hours', { count: msToHours(stats.interval) })
|
||||
: t('for_last_days', { count: msToDays(stats.interval) });
|
||||
|
||||
@@ -38,9 +38,6 @@ const ResponseCell = ({
|
||||
|
||||
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason);
|
||||
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
||||
const upstreamString = cached
|
||||
? t('served_from_cache', { value: upstream, i: <i /> })
|
||||
: upstream;
|
||||
|
||||
const renderResponses = (responseArr) => {
|
||||
if (!responseArr || responseArr.length === 0) {
|
||||
@@ -58,7 +55,16 @@ const ResponseCell = ({
|
||||
|
||||
const COMMON_CONTENT = {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstreamString,
|
||||
install_settings_dns: upstream,
|
||||
...(cached
|
||||
&& {
|
||||
served_from_cache_label: (
|
||||
<svg className="icons icon--20 icon--green mb-1">
|
||||
<use xlinkHref="#check" />
|
||||
</svg>
|
||||
),
|
||||
}
|
||||
),
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
...(service_name && services.allServices
|
||||
|
||||
@@ -118,9 +118,6 @@ const Row = memo(({
|
||||
|
||||
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
||||
const clientNameBlockingFor = getBlockingClientName(clients, client);
|
||||
const upstreamString = cached
|
||||
? t('served_from_cache', { value: upstream, i: <i /> })
|
||||
: upstream;
|
||||
|
||||
const onBlockingForClientClick = () => {
|
||||
dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor));
|
||||
@@ -192,7 +189,16 @@ const Row = memo(({
|
||||
className="link--green">{sourceData.name}
|
||||
</a>,
|
||||
response_details: 'title',
|
||||
install_settings_dns: upstreamString,
|
||||
install_settings_dns: upstream,
|
||||
...(cached
|
||||
&& {
|
||||
served_from_cache_label: (
|
||||
<svg className="icons icon--20 icon--green">
|
||||
<use xlinkHref="#check" />
|
||||
</svg>
|
||||
),
|
||||
}
|
||||
),
|
||||
elapsed: formattedElapsedMs,
|
||||
...(rules.length > 0
|
||||
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
|
||||
|
||||
@@ -245,6 +245,10 @@ const Icons = () => (
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12 13.5C11.1716 13.5 10.5 12.8284 10.5 12C10.5 11.1716 11.1716 10.5 12 10.5C12.8284 10.5 13.5 11.1716 13.5 12C13.5 12.8284 12.8284 13.5 12 13.5Z" fill="currentColor" />
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12 20C11.1716 20 10.5 19.3284 10.5 18.5C10.5 17.6716 11.1716 17 12 17C12.8284 17 13.5 17.6716 13.5 18.5C13.5 19.3284 12.8284 20 12 20Z" fill="currentColor" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="check" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M5 11.7665L10.5878 17L19 8" />
|
||||
</symbol>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
||||
@@ -142,6 +142,12 @@ export default {
|
||||
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"
|
||||
},
|
||||
"adguard_popup_filter": {
|
||||
"name": "AdGuard DNS Popup Hosts filter",
|
||||
"categoryId": "general",
|
||||
"homepage": "https://github.com/AdguardTeam/AdGuardSDNSFilter",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_59.txt"
|
||||
},
|
||||
"awavenue_ads_rule": {
|
||||
"name": "AWAvenue Ads Rule",
|
||||
"categoryId": "general",
|
||||
@@ -190,6 +196,12 @@ export default {
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists#piracy",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_46.txt"
|
||||
},
|
||||
"hagezi_badware_hoster_blocklist": {
|
||||
"name": "HaGeZi's Badware Hoster Blocklist",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_55.txt"
|
||||
},
|
||||
"hagezi_dyndns_blocklist": {
|
||||
"name": "HaGeZi's DynDNS Blocklist",
|
||||
"categoryId": "security",
|
||||
@@ -226,6 +238,12 @@ export default {
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt"
|
||||
},
|
||||
"hagezi_the_worlds_most_abused_tlds": {
|
||||
"name": "HaGeZi's The World's Most Abused TLDs",
|
||||
"categoryId": "security",
|
||||
"homepage": "https://github.com/hagezi/dns-blocklists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_56.txt"
|
||||
},
|
||||
"hagezi_threat_intelligence_feeds": {
|
||||
"name": "HaGeZi's Threat Intelligence Feeds",
|
||||
"categoryId": "security",
|
||||
@@ -286,6 +304,12 @@ export default {
|
||||
"homepage": "https://github.com/durablenapkin/scamblocklist",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_10.txt"
|
||||
},
|
||||
"shadowwhisperers_dating_list": {
|
||||
"name": "ShadowWhisperer's Dating List",
|
||||
"categoryId": "other",
|
||||
"homepage": "https://github.com/ShadowWhisperer/BlockLists",
|
||||
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_57.txt"
|
||||
},
|
||||
"shadowwhisperers_malware_list": {
|
||||
"name": "ShadowWhisperer's Malware List",
|
||||
"categoryId": "security",
|
||||
|
||||
48
go.mod
48
go.mod
@@ -1,13 +1,13 @@
|
||||
module github.com/AdguardTeam/AdGuardHome
|
||||
|
||||
go 1.21.8
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.65.2
|
||||
github.com/AdguardTeam/golibs v0.20.1
|
||||
github.com/AdguardTeam/dnsproxy v0.71.1
|
||||
github.com/AdguardTeam/golibs v0.23.2
|
||||
github.com/AdguardTeam/urlfilter v0.18.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||
github.com/digineo/go-ipset/v2 v2.2.1
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/renameio/v2 v2.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240204152450-ca2dc33955c1
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49
|
||||
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
|
||||
@@ -27,15 +27,15 @@ require (
|
||||
// TODO(a.garipov): This package is deprecated; find a new one or use our
|
||||
// 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.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.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
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/quic-go/quic-go v0.43.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/ti-mo/netfilter v0.5.2
|
||||
go.etcd.io/bbolt v1.3.10
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.1
|
||||
@@ -47,20 +47,20 @@ require (
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
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-20240207164012-fb44976bdcd5 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240521024322-9665fa269a30 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.3 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
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/u-root/uio v0.0.0-20240207234124-abbebccef0fd // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
go.uber.org/mock v0.4.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.18.0 // indirect
|
||||
gonum.org/v1/gonum v0.14.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
gonum.org/v1/gonum v0.15.0 // indirect
|
||||
)
|
||||
|
||||
114
go.sum
114
go.sum
@@ -1,7 +1,7 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.65.2 h1:D+BMw0Vu2lbQrYpoPctG2Xr+24KdfhgkzZb6QgPZheM=
|
||||
github.com/AdguardTeam/dnsproxy v0.65.2/go.mod h1:8NQTTNZY+qR9O1Fzgz3WQv30knfSgms68SRlzSnX74A=
|
||||
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/dnsproxy v0.71.1 h1:R8jKmoE9HwqdTt7bm8irpvrQEOSmD+iGdNXbOg/uM8Y=
|
||||
github.com/AdguardTeam/dnsproxy v0.71.1/go.mod h1:rCaCL4m4n63sgwTOyUVdc7MC42PlUYBt11Fz/UjD+kM=
|
||||
github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
|
||||
github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
|
||||
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=
|
||||
@@ -10,8 +10,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7 h1:aEitLIR8HcxVodZ79mgRcCiC0A0I5kZPBuWGFwwulAw=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.2.7/go.mod h1:qPWhwz6FdSmuK7W4sMyvogrez4MWdtzosdqlr0Rg3ow=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP2zNLs=
|
||||
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
|
||||
@@ -29,16 +29,14 @@ github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2
|
||||
github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
|
||||
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-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/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/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
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=
|
||||
@@ -46,8 +44,8 @@ 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-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/pprof v0.0.0-20240521024322-9665fa269a30 h1:r6YdmbD41tGHeCWDyHF691LWtL7D1iSTyJaKejTWwVU=
|
||||
github.com/google/pprof v0.0.0-20240521024322-9665fa269a30/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
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=
|
||||
@@ -55,8 +53,8 @@ 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/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/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
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.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||
@@ -78,16 +76,16 @@ github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU
|
||||
github.com/mdlayher/raw v0.1.0 h1:K4PFMVy+AFsp0Zdlrts7yNhxc/uXoPVHi9RzRvtZF2Y=
|
||||
github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5s9Sg=
|
||||
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
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/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
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/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
|
||||
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
||||
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
||||
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.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
@@ -101,56 +99,55 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
||||
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/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k=
|
||||
github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA=
|
||||
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
|
||||
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.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=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
|
||||
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/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
|
||||
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
|
||||
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/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/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/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.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
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.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/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
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=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -161,30 +158,31 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.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=
|
||||
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/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
|
||||
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
|
||||
@@ -5,34 +5,13 @@ 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
|
||||
// COALESCE in SQL. If values or all its elements are empty, it returns a zero
|
||||
// value.
|
||||
//
|
||||
// T is comparable, because Go currently doesn't have a comparableWithZeroValue
|
||||
// constraint.
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [CoalesceSlice].
|
||||
func Coalesce[T comparable](values ...T) (res T) {
|
||||
var zero T
|
||||
for _, v := range values {
|
||||
if v != zero {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
// CoalesceSlice returns the first non-zero value. It is named after function
|
||||
// COALESCE in SQL. If values or all its elements are empty, it returns nil.
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [Coalesce].
|
||||
func CoalesceSlice[E any, S []E](values ...S) (res S) {
|
||||
for _, v := range values {
|
||||
if v != nil {
|
||||
|
||||
@@ -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.
|
||||
@@ -33,7 +33,7 @@ func elements(b *aghalg.RingBuffer[int], n uint, reverse bool) (es []int) {
|
||||
func TestNewRingBuffer(t *testing.T) {
|
||||
t.Run("success_and_clear", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](5)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
}
|
||||
assert.Equal(t, []int{5, 6, 7, 8, 9}, elements(b, b.Len(), false))
|
||||
@@ -44,7 +44,7 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](0)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 0, bufLen)
|
||||
@@ -55,7 +55,7 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
|
||||
t.Run("single", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](1)
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Append(i)
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 1, bufLen)
|
||||
@@ -94,7 +94,7 @@ func TestRingBuffer_Range(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for i := 0; i < tc.count; i++ {
|
||||
for i := range tc.count {
|
||||
b.Append(i)
|
||||
}
|
||||
|
||||
|
||||
86
internal/aghalg/sortedmap.go
Normal file
86
internal/aghalg/sortedmap.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package aghalg
|
||||
|
||||
import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
// SortedMap is a map that keeps elements in order with internal sorting
|
||||
// function. Must be initialised by the [NewSortedMap].
|
||||
type SortedMap[K comparable, V any] struct {
|
||||
vals map[K]V
|
||||
cmp func(a, b K) (res int)
|
||||
keys []K
|
||||
}
|
||||
|
||||
// NewSortedMap initializes the new instance of sorted map. cmp is a sort
|
||||
// function to keep elements in order.
|
||||
//
|
||||
// TODO(s.chzhen): Use cmp.Compare in Go 1.21.
|
||||
func NewSortedMap[K comparable, V any](cmp func(a, b K) (res int)) SortedMap[K, V] {
|
||||
return SortedMap[K, V]{
|
||||
vals: map[K]V{},
|
||||
cmp: cmp,
|
||||
}
|
||||
}
|
||||
|
||||
// Set adds val with key to the sorted map. It panics if the m is nil.
|
||||
func (m *SortedMap[K, V]) Set(key K, val V) {
|
||||
m.vals[key] = val
|
||||
|
||||
i, has := slices.BinarySearchFunc(m.keys, key, m.cmp)
|
||||
if has {
|
||||
m.keys[i] = key
|
||||
} else {
|
||||
m.keys = slices.Insert(m.keys, i, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns val by key from the sorted map.
|
||||
func (m *SortedMap[K, V]) Get(key K) (val V, ok bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
val, ok = m.vals[key]
|
||||
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Del removes the value by key from the sorted map.
|
||||
func (m *SortedMap[K, V]) Del(key K) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, has := m.vals[key]; !has {
|
||||
return
|
||||
}
|
||||
|
||||
delete(m.vals, key)
|
||||
i, _ := slices.BinarySearchFunc(m.keys, key, m.cmp)
|
||||
m.keys = slices.Delete(m.keys, i, i+1)
|
||||
}
|
||||
|
||||
// Clear removes all elements from the sorted map.
|
||||
func (m *SortedMap[K, V]) Clear() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.keys = nil
|
||||
clear(m.vals)
|
||||
}
|
||||
|
||||
// Range calls cb for each element of the map, sorted by m.cmp. If cb returns
|
||||
// false it stops.
|
||||
func (m *SortedMap[K, V]) Range(cb func(K, V) (cont bool)) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, k := range m.keys {
|
||||
if !cb(k, m.vals[k]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
95
internal/aghalg/sortedmap_test.go
Normal file
95
internal/aghalg/sortedmap_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package aghalg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSortedMap(t *testing.T) {
|
||||
var m SortedMap[string, int]
|
||||
|
||||
letters := []string{}
|
||||
for i := range 10 {
|
||||
r := string('a' + rune(i))
|
||||
letters = append(letters, r)
|
||||
}
|
||||
|
||||
t.Run("create_and_fill", func(t *testing.T) {
|
||||
m = NewSortedMap[string, int](strings.Compare)
|
||||
|
||||
nums := []int{}
|
||||
for i, r := range letters {
|
||||
m.Set(r, i)
|
||||
nums = append(nums, i)
|
||||
}
|
||||
|
||||
gotLetters := []string{}
|
||||
gotNums := []int{}
|
||||
m.Range(func(k string, v int) bool {
|
||||
gotLetters = append(gotLetters, k)
|
||||
gotNums = append(gotNums, v)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, letters, gotLetters)
|
||||
assert.Equal(t, nums, gotNums)
|
||||
|
||||
n, ok := m.Get(letters[0])
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, nums[0], n)
|
||||
})
|
||||
|
||||
t.Run("clear", func(t *testing.T) {
|
||||
lastLetter := letters[len(letters)-1]
|
||||
m.Del(lastLetter)
|
||||
|
||||
_, ok := m.Get(lastLetter)
|
||||
assert.False(t, ok)
|
||||
|
||||
m.Clear()
|
||||
|
||||
gotLetters := []string{}
|
||||
m.Range(func(k string, _ int) bool {
|
||||
gotLetters = append(gotLetters, k)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Len(t, gotLetters, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSortedMap_nil(t *testing.T) {
|
||||
const (
|
||||
key = "key"
|
||||
val = "val"
|
||||
)
|
||||
|
||||
var m SortedMap[string, string]
|
||||
|
||||
assert.Panics(t, func() {
|
||||
m.Set(key, val)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
_, ok := m.Get(key)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.Range(func(_, _ string) (cont bool) {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.Del(key)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
m.Clear()
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
// NormalizeDomain returns a lowercased version of host without the final dot,
|
||||
@@ -19,25 +16,3 @@ func NormalizeDomain(host string) (norm string) {
|
||||
|
||||
return strings.ToLower(strings.TrimSuffix(host, "."))
|
||||
}
|
||||
|
||||
// NewDomainNameSet returns nil and error, if list has duplicate or empty domain
|
||||
// name. Otherwise returns a set, which contains domain names normalized using
|
||||
// [NormalizeDomain].
|
||||
func NewDomainNameSet(list []string) (set *stringutil.Set, err error) {
|
||||
set = stringutil.NewSet()
|
||||
|
||||
for i, host := range list {
|
||||
if host == "" {
|
||||
return nil, fmt.Errorf("at index %d: hostname is empty", i)
|
||||
}
|
||||
|
||||
host = NormalizeDomain(host)
|
||||
if set.Has(host) {
|
||||
return nil, fmt.Errorf("duplicate hostname %q at index %d", host, i)
|
||||
}
|
||||
|
||||
set.Add(host)
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package aghnet_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewDomainNameSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
in []string
|
||||
}{{
|
||||
name: "nil",
|
||||
wantErrMsg: "",
|
||||
in: nil,
|
||||
}, {
|
||||
name: "success",
|
||||
wantErrMsg: "",
|
||||
in: []string{
|
||||
"Domain.Example",
|
||||
".",
|
||||
},
|
||||
}, {
|
||||
name: "dups",
|
||||
wantErrMsg: `duplicate hostname "domain.example" at index 1`,
|
||||
in: []string{
|
||||
"Domain.Example",
|
||||
"domain.example",
|
||||
},
|
||||
}, {
|
||||
name: "bad_domain",
|
||||
wantErrMsg: "at index 0: hostname is empty",
|
||||
in: []string{
|
||||
"",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
set, err := aghnet.NewDomainNameSet(tc.in)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, host := range tc.in {
|
||||
assert.Truef(t, set.Has(aghnet.NormalizeDomain(host)), "%q not matched", host)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -154,8 +154,8 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
|
||||
}
|
||||
|
||||
// handleEvents concurrently handles the file system events. It closes the
|
||||
// update channel of HostsContainer when finishes. It's used to be called
|
||||
// within a separate goroutine.
|
||||
// update channel of HostsContainer when finishes. It is intended to be used as
|
||||
// a goroutine.
|
||||
func (hc *HostsContainer) handleEvents() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPrefix))
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: onEvents,
|
||||
OnAdd: onAdd,
|
||||
OnClose: func() (err error) { return nil },
|
||||
@@ -93,6 +94,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
t.Run("nil_fs", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
_, _ = aghnet.NewHostsContainer(nil, &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
// Those shouldn't panic.
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(name string) (err error) { return nil },
|
||||
@@ -111,6 +113,7 @@ func TestNewHostsContainer(t *testing.T) {
|
||||
const errOnAdd errors.Error = "error"
|
||||
|
||||
errWatcher := &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan struct{}) { panic("not implemented") },
|
||||
OnAdd: func(name string) (err error) { return errOnAdd },
|
||||
OnClose: func() (err error) { return nil },
|
||||
@@ -155,6 +158,7 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
t.Cleanup(func() { close(eventsCh) })
|
||||
|
||||
w := &aghtest.FSWatcher{
|
||||
OnStart: func() (_ error) { panic("not implemented") },
|
||||
OnEvents: func() (e <-chan event) { return eventsCh },
|
||||
OnAdd: func(name string) (err error) {
|
||||
assert.Equal(t, "dir", name)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IgnoreEngine contains the list of rules for ignoring hostnames and matches
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
)
|
||||
|
||||
// DialContextFunc is the semantic alias for dialing functions, such as
|
||||
@@ -32,7 +33,7 @@ var (
|
||||
netInterfaceAddrs = net.InterfaceAddrs
|
||||
|
||||
// rootDirFS is the filesystem pointing to the root directory.
|
||||
rootDirFS = aghos.RootDirFS()
|
||||
rootDirFS = osutil.RootDirFS()
|
||||
)
|
||||
|
||||
// ErrNoStaticIPInfo is returned by IfaceHasStaticIP when no information about
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
// FileWalker is the signature of a function called for files in the file tree.
|
||||
@@ -56,7 +56,7 @@ func checkFile(
|
||||
// srcSet. srcSet must be non-nil.
|
||||
func handlePatterns(
|
||||
fsys fs.FS,
|
||||
srcSet *stringutil.Set,
|
||||
srcSet *container.MapSet[string],
|
||||
patterns ...string,
|
||||
) (sub []string, err error) {
|
||||
sub = make([]string, 0, len(patterns))
|
||||
@@ -87,7 +87,7 @@ func handlePatterns(
|
||||
func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
||||
// The slice of sources keeps the order in which the files are walked since
|
||||
// srcSet.Values() returns strings in undefined order.
|
||||
srcSet := stringutil.NewSet()
|
||||
srcSet := container.NewMapSet[string]()
|
||||
var src []string
|
||||
src, err = handlePatterns(fsys, srcSet, initial...)
|
||||
if err != nil {
|
||||
@@ -97,6 +97,8 @@ func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
||||
var filename string
|
||||
defer func() { err = errors.Annotate(err, "checking %q: %w", filename) }()
|
||||
|
||||
// TODO(e.burkov): Redo this loop, as it modifies the very same slice it
|
||||
// iterates over.
|
||||
for i := 0; i < len(src); i++ {
|
||||
var patterns []string
|
||||
var cont bool
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"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 *container.MapSet[string]
|
||||
}
|
||||
|
||||
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: container.NewMapSet[string](),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleErrors handles accompanying errors. It used to be called in a separate
|
||||
// goroutine.
|
||||
func (w *osWatcher) handleErrors() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
|
||||
// type check
|
||||
var _ FSWatcher = (*osWatcher)(nil)
|
||||
|
||||
for err := range w.w.Errors {
|
||||
log.Error("%s: %s", osWatcherPref, err)
|
||||
}
|
||||
// Start implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Start() (err error) {
|
||||
go w.handleErrors()
|
||||
go w.handleEvents()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Close() (err error) {
|
||||
return w.watcher.Close()
|
||||
}
|
||||
|
||||
// Events implements the FSWatcher interface for *osWatcher.
|
||||
@@ -81,34 +92,42 @@ func (w *osWatcher) Events() (e <-chan event) {
|
||||
return w.events
|
||||
}
|
||||
|
||||
// Add implements the FSWatcher interface for *osWatcher.
|
||||
// Add implements the [FSWatcher] interface for *osWatcher.
|
||||
//
|
||||
// TODO(e.burkov): Make it accept non-existing files to detect it's creating.
|
||||
func (w *osWatcher) Add(name string) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "%s: %w", osWatcherPref) }()
|
||||
|
||||
if _, err = fs.Stat(RootDirFS(), name); err != nil {
|
||||
fi, err := fs.Stat(osutil.RootDirFS(), name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking file %q: %w", name, err)
|
||||
}
|
||||
|
||||
return w.w.Add(filepath.Join("/", name))
|
||||
}
|
||||
name = filepath.Join("/", name)
|
||||
w.files.Add(name)
|
||||
|
||||
// Close implements the FSWatcher interface for *osWatcher.
|
||||
func (w *osWatcher) Close() (err error) {
|
||||
return w.w.Close()
|
||||
// Watch the directory and filter the events by the file name, since the
|
||||
// common recomendation to the fsnotify package is to watch the directory
|
||||
// instead of the file itself.
|
||||
//
|
||||
// See https://pkg.go.dev/github.com/fsnotify/fsnotify@v1.7.0#readme-watching-a-file-doesn-t-work-well.
|
||||
if !fi.IsDir() {
|
||||
name = filepath.Dir(name)
|
||||
}
|
||||
|
||||
return w.watcher.Add(name)
|
||||
}
|
||||
|
||||
// handleEvents notifies about the received file system's event if needed. It
|
||||
// used to be called in a separate goroutine.
|
||||
// is intended to be used as a goroutine.
|
||||
func (w *osWatcher) handleEvents() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", osWatcherPref))
|
||||
|
||||
defer close(w.events)
|
||||
|
||||
ch := w.w.Events
|
||||
ch := w.watcher.Events
|
||||
for e := range ch {
|
||||
if e.Op&fsnotify.Write == 0 {
|
||||
if e.Op&fsnotify.Write == 0 || !w.files.Has(e.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -131,3 +150,13 @@ func (w *osWatcher) handleEvents() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleErrors handles accompanying errors. It used to be called in a separate
|
||||
// goroutine.
|
||||
func (w *osWatcher) handleErrors() {
|
||||
defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref))
|
||||
|
||||
for err := range w.watcher.Errors {
|
||||
log.Error("%s: %s", osWatcherPref, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +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"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// UnsupportedError is returned by functions and methods when a particular
|
||||
@@ -155,33 +154,16 @@ 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)
|
||||
}
|
||||
|
||||
// NotifyShutdownSignal notifies c on receiving shutdown signals.
|
||||
func NotifyShutdownSignal(c chan<- os.Signal) {
|
||||
notifyShutdownSignal(c)
|
||||
}
|
||||
|
||||
// IsReconfigureSignal returns true if sig is a reconfigure signal.
|
||||
func IsReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return isReconfigureSignal(sig)
|
||||
}
|
||||
|
||||
// IsShutdownSignal returns true if sig is a shutdown signal.
|
||||
func IsShutdownSignal(sig os.Signal) (ok bool) {
|
||||
return isShutdownSignal(sig)
|
||||
}
|
||||
|
||||
// SendShutdownSignal sends the shutdown signal to the channel.
|
||||
func SendShutdownSignal(c chan<- os.Signal) {
|
||||
sendShutdownSignal(c)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/osutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func isOpenWrt() (ok bool) {
|
||||
}
|
||||
|
||||
return nil, !stringutil.ContainsFold(string(data), osNameData), nil
|
||||
}).Walk(RootDirFS(), etcReleasePattern)
|
||||
}).Walk(osutil.RootDirFS(), etcReleasePattern)
|
||||
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
@@ -3,41 +3,20 @@
|
||||
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)
|
||||
}
|
||||
|
||||
func notifyShutdownSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == unix.SIGHUP
|
||||
}
|
||||
|
||||
func isShutdownSignal(sig os.Signal) (ok bool) {
|
||||
switch sig {
|
||||
case
|
||||
unix.SIGINT,
|
||||
unix.SIGQUIT,
|
||||
unix.SIGTERM:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func sendShutdownSignal(_ chan<- os.Signal) {
|
||||
// On Unix we are already notified by the system.
|
||||
}
|
||||
|
||||
@@ -3,29 +3,12 @@
|
||||
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")
|
||||
}
|
||||
@@ -59,25 +42,10 @@ func notifyReconfigureSignal(c chan<- os.Signal) {
|
||||
signal.Notify(c, windows.SIGHUP)
|
||||
}
|
||||
|
||||
func notifyShutdownSignal(c chan<- os.Signal) {
|
||||
// syscall.SIGTERM is processed automatically. See go doc os/signal,
|
||||
// section Windows.
|
||||
signal.Notify(c, os.Interrupt)
|
||||
}
|
||||
|
||||
func isReconfigureSignal(sig os.Signal) (ok bool) {
|
||||
return sig == windows.SIGHUP
|
||||
}
|
||||
|
||||
func isShutdownSignal(sig os.Signal) (ok bool) {
|
||||
switch sig {
|
||||
case os.Interrupt, syscall.SIGTERM:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func sendShutdownSignal(c chan<- os.Signal) {
|
||||
c <- os.Interrupt
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ func TestWithDeferredCleanup(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -9,8 +9,13 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -71,3 +76,49 @@ func StartHTTPServer(t testing.TB, data []byte) (c *http.Client, u *url.URL) {
|
||||
|
||||
return srv.Client(), u
|
||||
}
|
||||
|
||||
// testTimeout is a timeout for tests.
|
||||
//
|
||||
// TODO(e.burkov): Move into agdctest.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// StartLocalhostUpstream is a test helper that starts a DNS server on
|
||||
// localhost.
|
||||
func StartLocalhostUpstream(t *testing.T, h dns.Handler) (addr *url.URL) {
|
||||
t.Helper()
|
||||
|
||||
startCh := make(chan netip.AddrPort)
|
||||
defer close(startCh)
|
||||
errCh := make(chan error)
|
||||
|
||||
srv := &dns.Server{
|
||||
Addr: "127.0.0.1:0",
|
||||
Net: string(proxy.ProtoTCP),
|
||||
Handler: h,
|
||||
ReadTimeout: testTimeout,
|
||||
WriteTimeout: testTimeout,
|
||||
}
|
||||
srv.NotifyStartedFunc = func() {
|
||||
addrPort := srv.Listener.Addr()
|
||||
startCh <- netutil.NetAddrToAddrPort(addrPort)
|
||||
}
|
||||
|
||||
go func() { errCh <- srv.ListenAndServe() }()
|
||||
|
||||
select {
|
||||
case addrPort := <-startCh:
|
||||
addr = &url.URL{
|
||||
Scheme: string(proxy.ProtoTCP),
|
||||
Host: addrPort.String(),
|
||||
}
|
||||
|
||||
testutil.CleanupAndRequireSuccess(t, func() (err error) { return <-errCh })
|
||||
testutil.CleanupAndRequireSuccess(t, srv.Shutdown)
|
||||
case err := <-errCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(testTimeout):
|
||||
require.FailNow(t, "timeout exceeded")
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
@@ -26,14 +25,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 +54,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.
|
||||
@@ -88,9 +93,6 @@ type AddressProcessor struct {
|
||||
OnClose func() (err error)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ client.AddressProcessor = (*AddressProcessor)(nil)
|
||||
|
||||
// Process implements the [client.AddressProcessor] interface for
|
||||
// *AddressProcessor.
|
||||
func (p *AddressProcessor) Process(ip netip.Addr) {
|
||||
@@ -108,9 +110,6 @@ type AddressUpdater struct {
|
||||
OnUpdateAddress func(ip netip.Addr, host string, info *whois.Info)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ client.AddressUpdater = (*AddressUpdater)(nil)
|
||||
|
||||
// UpdateAddress implements the [client.AddressUpdater] interface for
|
||||
// *AddressUpdater.
|
||||
func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package aghtest_test
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
)
|
||||
@@ -13,3 +14,13 @@ var _ filtering.Resolver = (*aghtest.Resolver)(nil)
|
||||
|
||||
// type check
|
||||
var _ dnsforward.ClientsContainer = (*aghtest.ClientsContainer)(nil)
|
||||
|
||||
// type check
|
||||
//
|
||||
// TODO(s.chzhen): It's here to avoid the import cycle. Remove it.
|
||||
var _ client.AddressProcessor = (*aghtest.AddressProcessor)(nil)
|
||||
|
||||
// type check
|
||||
//
|
||||
// TODO(s.chzhen): It's here to avoid the import cycle. Remove it.
|
||||
var _ client.AddressUpdater = (*aghtest.AddressUpdater)(nil)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -91,8 +91,6 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -186,8 +184,6 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package client
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
)
|
||||
@@ -56,6 +57,9 @@ func (cs Source) MarshalText() (text []byte, err error) {
|
||||
|
||||
// Runtime is a client information from different sources.
|
||||
type Runtime struct {
|
||||
// ip is an IP address of a client.
|
||||
ip netip.Addr
|
||||
|
||||
// whois is the filtered WHOIS information of a client.
|
||||
whois *whois.Info
|
||||
|
||||
@@ -80,6 +84,15 @@ type Runtime struct {
|
||||
hostsFile []string
|
||||
}
|
||||
|
||||
// NewRuntime constructs a new runtime client. ip must be valid IP address.
|
||||
//
|
||||
// TODO(s.chzhen): Validate IP address.
|
||||
func NewRuntime(ip netip.Addr) (r *Runtime) {
|
||||
return &Runtime{
|
||||
ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns a client information from the highest-priority source.
|
||||
func (r *Runtime) Info() (cs Source, host string) {
|
||||
info := []string{}
|
||||
@@ -133,8 +146,8 @@ func (r *Runtime) SetWHOIS(info *whois.Info) {
|
||||
r.whois = info
|
||||
}
|
||||
|
||||
// Unset clears a cs information.
|
||||
func (r *Runtime) Unset(cs Source) {
|
||||
// unset clears a cs information.
|
||||
func (r *Runtime) unset(cs Source) {
|
||||
switch cs {
|
||||
case SourceWHOIS:
|
||||
r.whois = nil
|
||||
@@ -149,11 +162,16 @@ func (r *Runtime) Unset(cs Source) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if there is no information from any source.
|
||||
func (r *Runtime) IsEmpty() (ok bool) {
|
||||
// isEmpty returns true if there is no information from any source.
|
||||
func (r *Runtime) isEmpty() (ok bool) {
|
||||
return r.whois == nil &&
|
||||
r.arp == nil &&
|
||||
r.rdns == nil &&
|
||||
r.dhcp == nil &&
|
||||
r.hostsFile == nil
|
||||
}
|
||||
|
||||
// Addr returns an IP address of the client.
|
||||
func (r *Runtime) Addr() (ip netip.Addr) {
|
||||
return r.ip
|
||||
}
|
||||
|
||||
368
internal/client/index.go
Normal file
368
internal/client/index.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
|
||||
type macKey any
|
||||
|
||||
// macToKey converts mac into key of type macKey, which is used as the key of
|
||||
// the [clientIndex.macToUID]. mac must be valid MAC address.
|
||||
func macToKey(mac net.HardwareAddr) (key macKey) {
|
||||
switch len(mac) {
|
||||
case 6:
|
||||
return [6]byte(mac)
|
||||
case 8:
|
||||
return [8]byte(mac)
|
||||
case 20:
|
||||
return [20]byte(mac)
|
||||
default:
|
||||
panic(fmt.Errorf("invalid mac address %#v", mac))
|
||||
}
|
||||
}
|
||||
|
||||
// Index stores all information about persistent clients.
|
||||
type Index struct {
|
||||
// nameToUID maps client name to UID.
|
||||
nameToUID map[string]UID
|
||||
|
||||
// clientIDToUID maps client ID to UID.
|
||||
clientIDToUID map[string]UID
|
||||
|
||||
// ipToUID maps IP address to UID.
|
||||
ipToUID map[netip.Addr]UID
|
||||
|
||||
// macToUID maps MAC address to UID.
|
||||
macToUID map[macKey]UID
|
||||
|
||||
// uidToClient maps UID to the persistent client.
|
||||
uidToClient map[UID]*Persistent
|
||||
|
||||
// subnetToUID maps subnet to UID.
|
||||
subnetToUID aghalg.SortedMap[netip.Prefix, UID]
|
||||
}
|
||||
|
||||
// NewIndex initializes the new instance of client index.
|
||||
func NewIndex() (ci *Index) {
|
||||
return &Index{
|
||||
nameToUID: map[string]UID{},
|
||||
clientIDToUID: map[string]UID{},
|
||||
ipToUID: map[netip.Addr]UID{},
|
||||
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
|
||||
macToUID: map[macKey]UID{},
|
||||
uidToClient: map[UID]*Persistent{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add stores information about a persistent client in the index. c must be
|
||||
// non-nil and contain UID.
|
||||
func (ci *Index) Add(c *Persistent) {
|
||||
if (c.UID == UID{}) {
|
||||
panic("client must contain uid")
|
||||
}
|
||||
|
||||
ci.nameToUID[c.Name] = c.UID
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
ci.clientIDToUID[id] = c.UID
|
||||
}
|
||||
|
||||
for _, ip := range c.IPs {
|
||||
ci.ipToUID[ip] = c.UID
|
||||
}
|
||||
|
||||
for _, pref := range c.Subnets {
|
||||
ci.subnetToUID.Set(pref, c.UID)
|
||||
}
|
||||
|
||||
for _, mac := range c.MACs {
|
||||
k := macToKey(mac)
|
||||
ci.macToUID[k] = c.UID
|
||||
}
|
||||
|
||||
ci.uidToClient[c.UID] = c
|
||||
}
|
||||
|
||||
// ClashesUID returns existing persistent client with the same UID as c. Note
|
||||
// that this is only possible when configuration contains duplicate fields.
|
||||
func (ci *Index) ClashesUID(c *Persistent) (err error) {
|
||||
p, ok := ci.uidToClient[c.UID]
|
||||
if ok {
|
||||
return fmt.Errorf("another client %q uses the same uid", p.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clashes returns an error if the index contains a different persistent client
|
||||
// with at least a single identifier contained by c. c must be non-nil.
|
||||
func (ci *Index) Clashes(c *Persistent) (err error) {
|
||||
if p := ci.clashesName(c); p != nil {
|
||||
return fmt.Errorf("another client uses the same name %q", p.Name)
|
||||
}
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
existing, ok := ci.clientIDToUID[id]
|
||||
if ok && existing != c.UID {
|
||||
p := ci.uidToClient[existing]
|
||||
|
||||
return fmt.Errorf("another client %q uses the same ClientID %q", p.Name, id)
|
||||
}
|
||||
}
|
||||
|
||||
p, ip := ci.clashesIP(c)
|
||||
if p != nil {
|
||||
return fmt.Errorf("another client %q uses the same IP %q", p.Name, ip)
|
||||
}
|
||||
|
||||
p, s := ci.clashesSubnet(c)
|
||||
if p != nil {
|
||||
return fmt.Errorf("another client %q uses the same subnet %q", p.Name, s)
|
||||
}
|
||||
|
||||
p, mac := ci.clashesMAC(c)
|
||||
if p != nil {
|
||||
return fmt.Errorf("another client %q uses the same MAC %q", p.Name, mac)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clashesName returns existing persistent client with the same name as c or
|
||||
// nil. c must be non-nil.
|
||||
func (ci *Index) clashesName(c *Persistent) (existing *Persistent) {
|
||||
existing, ok := ci.FindByName(c.Name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if existing.UID != c.UID {
|
||||
return existing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clashesIP returns a previous client with the same IP address as c. c must be
|
||||
// non-nil.
|
||||
func (ci *Index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
|
||||
for _, ip := range c.IPs {
|
||||
existing, ok := ci.ipToUID[ip]
|
||||
if ok && existing != c.UID {
|
||||
return ci.uidToClient[existing], ip
|
||||
}
|
||||
}
|
||||
|
||||
return nil, netip.Addr{}
|
||||
}
|
||||
|
||||
// clashesSubnet returns a previous client with the same subnet as c. c must be
|
||||
// non-nil.
|
||||
func (ci *Index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
|
||||
for _, s = range c.Subnets {
|
||||
var existing UID
|
||||
var ok bool
|
||||
|
||||
ci.subnetToUID.Range(func(p netip.Prefix, uid UID) (cont bool) {
|
||||
if s == p {
|
||||
existing = uid
|
||||
ok = true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if ok && existing != c.UID {
|
||||
return ci.uidToClient[existing], s
|
||||
}
|
||||
}
|
||||
|
||||
return nil, netip.Prefix{}
|
||||
}
|
||||
|
||||
// clashesMAC returns a previous client with the same MAC address as c. c must
|
||||
// be non-nil.
|
||||
func (ci *Index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) {
|
||||
for _, mac = range c.MACs {
|
||||
k := macToKey(mac)
|
||||
existing, ok := ci.macToUID[k]
|
||||
if ok && existing != c.UID {
|
||||
return ci.uidToClient[existing], mac
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find finds persistent client by string representation of the client ID, IP
|
||||
// address, or MAC.
|
||||
func (ci *Index) Find(id string) (c *Persistent, ok bool) {
|
||||
uid, found := ci.clientIDToUID[id]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(id)
|
||||
if err == nil {
|
||||
// MAC addresses can be successfully parsed as IP addresses.
|
||||
c, found = ci.findByIP(ip)
|
||||
if found {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
|
||||
mac, err := net.ParseMAC(id)
|
||||
if err == nil {
|
||||
return ci.FindByMAC(mac)
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindByName finds persistent client by name.
|
||||
func (ci *Index) FindByName(name string) (c *Persistent, found bool) {
|
||||
uid, found := ci.nameToUID[name]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// findByIP finds persistent client by IP address.
|
||||
func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
|
||||
uid, found := ci.ipToUID[ip]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
ipWithoutZone := ip.WithZone("")
|
||||
ci.subnetToUID.Range(func(pref netip.Prefix, id UID) (cont bool) {
|
||||
// Remove zone before checking because prefixes strip zones.
|
||||
if pref.Contains(ipWithoutZone) {
|
||||
uid, found = id, true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindByMAC finds persistent client by MAC.
|
||||
func (ci *Index) FindByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
||||
k := macToKey(mac)
|
||||
uid, found := ci.macToUID[k]
|
||||
if found {
|
||||
return ci.uidToClient[uid], true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// FindByIPWithoutZone finds a persistent client by IP address without zone. It
|
||||
// strips the IPv6 zone index from the stored IP addresses before comparing,
|
||||
// because querylog entries don't have it. See TODO on [querylog.logEntry.IP].
|
||||
//
|
||||
// Note that multiple clients can have the same IP address with different zones.
|
||||
// Therefore, the result of this method is indeterminate.
|
||||
func (ci *Index) FindByIPWithoutZone(ip netip.Addr) (c *Persistent) {
|
||||
if (ip == netip.Addr{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for addr, uid := range ci.ipToUID {
|
||||
if addr.WithZone("") == ip {
|
||||
return ci.uidToClient[uid]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes information about persistent client from the index. c must be
|
||||
// non-nil.
|
||||
func (ci *Index) Delete(c *Persistent) {
|
||||
delete(ci.nameToUID, c.Name)
|
||||
|
||||
for _, id := range c.ClientIDs {
|
||||
delete(ci.clientIDToUID, id)
|
||||
}
|
||||
|
||||
for _, ip := range c.IPs {
|
||||
delete(ci.ipToUID, ip)
|
||||
}
|
||||
|
||||
for _, pref := range c.Subnets {
|
||||
ci.subnetToUID.Del(pref)
|
||||
}
|
||||
|
||||
for _, mac := range c.MACs {
|
||||
k := macToKey(mac)
|
||||
delete(ci.macToUID, k)
|
||||
}
|
||||
|
||||
delete(ci.uidToClient, c.UID)
|
||||
}
|
||||
|
||||
// Size returns the number of persistent clients.
|
||||
func (ci *Index) Size() (n int) {
|
||||
return len(ci.uidToClient)
|
||||
}
|
||||
|
||||
// Range calls f for each persistent client, unless cont is false. The order is
|
||||
// undefined.
|
||||
func (ci *Index) Range(f func(c *Persistent) (cont bool)) {
|
||||
for _, c := range ci.uidToClient {
|
||||
if !f(c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RangeByName is like [Index.Range] but sorts the persistent clients by name
|
||||
// before iterating ensuring a predictable order.
|
||||
func (ci *Index) RangeByName(f func(c *Persistent) (cont bool)) {
|
||||
cs := maps.Values(ci.uidToClient)
|
||||
slices.SortFunc(cs, func(a, b *Persistent) (n int) {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
for _, c := range cs {
|
||||
if !f(c) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CloseUpstreams closes upstream configurations of persistent clients.
|
||||
func (ci *Index) CloseUpstreams() (err error) {
|
||||
var errs []error
|
||||
ci.RangeByName(func(c *Persistent) (cont bool) {
|
||||
err = c.CloseUpstreams()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
350
internal/client/index_internal_test.go
Normal file
350
internal/client/index_internal_test.go
Normal file
@@ -0,0 +1,350 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// newIDIndex is a helper function that returns a client index filled with
|
||||
// persistent clients from the m. It also generates a UID for each client.
|
||||
func newIDIndex(m []*Persistent) (ci *Index) {
|
||||
ci = NewIndex()
|
||||
|
||||
for _, c := range m {
|
||||
c.UID = MustNewUID()
|
||||
ci.Add(c)
|
||||
}
|
||||
|
||||
return ci
|
||||
}
|
||||
|
||||
func TestClientIndex_Find(t *testing.T) {
|
||||
const (
|
||||
cliIPNone = "1.2.3.4"
|
||||
cliIP1 = "1.1.1.1"
|
||||
cliIP2 = "2.2.2.2"
|
||||
|
||||
cliIPv6 = "1:2:3::4"
|
||||
|
||||
cliSubnet = "2.2.2.0/24"
|
||||
cliSubnetIP = "2.2.2.222"
|
||||
|
||||
cliID = "client-id"
|
||||
cliMAC = "11:11:11:11:11:11"
|
||||
|
||||
linkLocalIP = "fe80::abcd:abcd:abcd:ab%eth0"
|
||||
linkLocalSubnet = "fe80::/16"
|
||||
)
|
||||
|
||||
var (
|
||||
clientWithBothFams = &Persistent{
|
||||
Name: "client1",
|
||||
IPs: []netip.Addr{
|
||||
netip.MustParseAddr(cliIP1),
|
||||
netip.MustParseAddr(cliIPv6),
|
||||
},
|
||||
}
|
||||
|
||||
clientWithSubnet = &Persistent{
|
||||
Name: "client2",
|
||||
IPs: []netip.Addr{netip.MustParseAddr(cliIP2)},
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
||||
}
|
||||
|
||||
clientWithMAC = &Persistent{
|
||||
Name: "client_with_mac",
|
||||
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||
}
|
||||
|
||||
clientWithID = &Persistent{
|
||||
Name: "client_with_id",
|
||||
ClientIDs: []string{cliID},
|
||||
}
|
||||
|
||||
clientLinkLocal = &Persistent{
|
||||
Name: "client_link_local",
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(linkLocalSubnet)},
|
||||
}
|
||||
)
|
||||
|
||||
clients := []*Persistent{
|
||||
clientWithBothFams,
|
||||
clientWithSubnet,
|
||||
clientWithMAC,
|
||||
clientWithID,
|
||||
clientLinkLocal,
|
||||
}
|
||||
ci := newIDIndex(clients)
|
||||
|
||||
testCases := []struct {
|
||||
want *Persistent
|
||||
name string
|
||||
ids []string
|
||||
}{{
|
||||
name: "ipv4_ipv6",
|
||||
ids: []string{cliIP1, cliIPv6},
|
||||
want: clientWithBothFams,
|
||||
}, {
|
||||
name: "ipv4_subnet",
|
||||
ids: []string{cliIP2, cliSubnetIP},
|
||||
want: clientWithSubnet,
|
||||
}, {
|
||||
name: "mac",
|
||||
ids: []string{cliMAC},
|
||||
want: clientWithMAC,
|
||||
}, {
|
||||
name: "client_id",
|
||||
ids: []string{cliID},
|
||||
want: clientWithID,
|
||||
}, {
|
||||
name: "client_link_local_subnet",
|
||||
ids: []string{linkLocalIP},
|
||||
want: clientLinkLocal,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for _, id := range tc.ids {
|
||||
c, ok := ci.Find(id)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, tc.want, c)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("not_found", func(t *testing.T) {
|
||||
_, ok := ci.Find(cliIPNone)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientIndex_Clashes(t *testing.T) {
|
||||
const (
|
||||
cliIP1 = "1.1.1.1"
|
||||
cliSubnet = "2.2.2.0/24"
|
||||
cliSubnetIP = "2.2.2.222"
|
||||
cliID = "client-id"
|
||||
cliMAC = "11:11:11:11:11:11"
|
||||
)
|
||||
|
||||
clients := []*Persistent{{
|
||||
Name: "client_with_ip",
|
||||
IPs: []netip.Addr{netip.MustParseAddr(cliIP1)},
|
||||
}, {
|
||||
Name: "client_with_subnet",
|
||||
Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)},
|
||||
}, {
|
||||
Name: "client_with_mac",
|
||||
MACs: []net.HardwareAddr{mustParseMAC(cliMAC)},
|
||||
}, {
|
||||
Name: "client_with_id",
|
||||
ClientIDs: []string{cliID},
|
||||
}}
|
||||
|
||||
ci := newIDIndex(clients)
|
||||
|
||||
testCases := []struct {
|
||||
client *Persistent
|
||||
name string
|
||||
}{{
|
||||
name: "ipv4",
|
||||
client: clients[0],
|
||||
}, {
|
||||
name: "subnet",
|
||||
client: clients[1],
|
||||
}, {
|
||||
name: "mac",
|
||||
client: clients[2],
|
||||
}, {
|
||||
name: "client_id",
|
||||
client: clients[3],
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
clone := tc.client.ShallowClone()
|
||||
clone.UID = MustNewUID()
|
||||
|
||||
err := ci.Clashes(clone)
|
||||
require.Error(t, err)
|
||||
|
||||
ci.Delete(tc.client)
|
||||
err = ci.Clashes(clone)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mustParseMAC is wrapper around [net.ParseMAC] that panics if there is an
|
||||
// error.
|
||||
func mustParseMAC(s string) (mac net.HardwareAddr) {
|
||||
mac, err := net.ParseMAC(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return mac
|
||||
}
|
||||
|
||||
func TestMACToKey(t *testing.T) {
|
||||
testCases := []struct {
|
||||
want any
|
||||
name string
|
||||
in string
|
||||
}{{
|
||||
name: "column6",
|
||||
in: "00:00:5e:00:53:01",
|
||||
want: [6]byte(mustParseMAC("00:00:5e:00:53:01")),
|
||||
}, {
|
||||
name: "column8",
|
||||
in: "02:00:5e:10:00:00:00:01",
|
||||
want: [8]byte(mustParseMAC("02:00:5e:10:00:00:00:01")),
|
||||
}, {
|
||||
name: "column20",
|
||||
in: "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01",
|
||||
want: [20]byte(mustParseMAC("00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01")),
|
||||
}, {
|
||||
name: "hyphen6",
|
||||
in: "00-00-5e-00-53-01",
|
||||
want: [6]byte(mustParseMAC("00-00-5e-00-53-01")),
|
||||
}, {
|
||||
name: "hyphen8",
|
||||
in: "02-00-5e-10-00-00-00-01",
|
||||
want: [8]byte(mustParseMAC("02-00-5e-10-00-00-00-01")),
|
||||
}, {
|
||||
name: "hyphen20",
|
||||
in: "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01",
|
||||
want: [20]byte(mustParseMAC("00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01")),
|
||||
}, {
|
||||
name: "dot6",
|
||||
in: "0000.5e00.5301",
|
||||
want: [6]byte(mustParseMAC("0000.5e00.5301")),
|
||||
}, {
|
||||
name: "dot8",
|
||||
in: "0200.5e10.0000.0001",
|
||||
want: [8]byte(mustParseMAC("0200.5e10.0000.0001")),
|
||||
}, {
|
||||
name: "dot20",
|
||||
in: "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001",
|
||||
want: [20]byte(mustParseMAC("0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001")),
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mac := mustParseMAC(tc.in)
|
||||
|
||||
key := macToKey(mac)
|
||||
assert.Equal(t, tc.want, key)
|
||||
})
|
||||
}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
mac := net.HardwareAddr([]byte{1, 2, 3})
|
||||
_ = macToKey(mac)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIndex_FindByIPWithoutZone(t *testing.T) {
|
||||
var (
|
||||
ip = netip.MustParseAddr("fe80::a098:7654:32ef:ff1")
|
||||
ipWithZone = netip.MustParseAddr("fe80::1ff:fe23:4567:890a%eth2")
|
||||
)
|
||||
|
||||
var (
|
||||
clientNoZone = &Persistent{
|
||||
Name: "client",
|
||||
IPs: []netip.Addr{ip},
|
||||
}
|
||||
|
||||
clientWithZone = &Persistent{
|
||||
Name: "client_with_zone",
|
||||
IPs: []netip.Addr{ipWithZone},
|
||||
}
|
||||
)
|
||||
|
||||
ci := newIDIndex([]*Persistent{
|
||||
clientNoZone,
|
||||
clientWithZone,
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
ip netip.Addr
|
||||
want *Persistent
|
||||
name string
|
||||
}{{
|
||||
name: "without_zone",
|
||||
ip: ip,
|
||||
want: clientNoZone,
|
||||
}, {
|
||||
name: "with_zone",
|
||||
ip: ipWithZone,
|
||||
want: clientWithZone,
|
||||
}, {
|
||||
name: "zero_address",
|
||||
ip: netip.Addr{},
|
||||
want: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := ci.FindByIPWithoutZone(tc.ip.WithZone(""))
|
||||
require.Equal(t, tc.want, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientIndex_RangeByName(t *testing.T) {
|
||||
sortedClients := []*Persistent{{
|
||||
Name: "clientA",
|
||||
ClientIDs: []string{"A"},
|
||||
}, {
|
||||
Name: "clientB",
|
||||
ClientIDs: []string{"B"},
|
||||
}, {
|
||||
Name: "clientC",
|
||||
ClientIDs: []string{"C"},
|
||||
}, {
|
||||
Name: "clientD",
|
||||
ClientIDs: []string{"D"},
|
||||
}, {
|
||||
Name: "clientE",
|
||||
ClientIDs: []string{"E"},
|
||||
}}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
want []*Persistent
|
||||
}{{
|
||||
name: "basic",
|
||||
want: sortedClients,
|
||||
}, {
|
||||
name: "nil",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "one_element",
|
||||
want: sortedClients[:1],
|
||||
}, {
|
||||
name: "two_elements",
|
||||
want: sortedClients[:2],
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ci := newIDIndex(tc.want)
|
||||
|
||||
var got []*Persistent
|
||||
ci.RangeByName(func(c *Persistent) (cont bool) {
|
||||
got = append(got, c)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
package home
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// UID is the type for the unique IDs of persistent clients.
|
||||
@@ -30,6 +30,16 @@ func NewUID() (uid UID, err error) {
|
||||
return UID(uuidv7), err
|
||||
}
|
||||
|
||||
// MustNewUID is a wrapper around [NewUID] that panics if there is an error.
|
||||
func MustNewUID() (uid UID) {
|
||||
uid, err := NewUID()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unexpected uuidv7 error: %w", err))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ encoding.TextMarshaler = UID{}
|
||||
|
||||
@@ -46,17 +56,15 @@ func (uid *UID) UnmarshalText(data []byte) error {
|
||||
return (*uuid.UUID)(uid).UnmarshalText(data)
|
||||
}
|
||||
|
||||
// persistentClient contains information about persistent clients.
|
||||
type persistentClient struct {
|
||||
// upstreamConfig is the custom upstream configuration for this client. If
|
||||
// Persistent contains information about persistent clients.
|
||||
type Persistent struct {
|
||||
// UpstreamConfig is the custom upstream configuration for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and empty,
|
||||
// there are no valid upstreams. If it's non-nil and non-empty, these
|
||||
// upstream must be used.
|
||||
upstreamConfig *proxy.CustomUpstreamConfig
|
||||
UpstreamConfig *proxy.CustomUpstreamConfig
|
||||
|
||||
// TODO(d.kolyshev): Make safeSearchConf a pointer.
|
||||
safeSearchConf filtering.SafeSearchConfig
|
||||
SafeSearch filtering.SafeSearch
|
||||
SafeSearch filtering.SafeSearch
|
||||
|
||||
// BlockedServices is the configuration of blocked services of a client.
|
||||
BlockedServices *filtering.BlockedServices
|
||||
@@ -85,10 +93,13 @@ type persistentClient struct {
|
||||
UseOwnBlockedServices bool
|
||||
IgnoreQueryLog bool
|
||||
IgnoreStatistics bool
|
||||
|
||||
// TODO(d.kolyshev): Make SafeSearchConf a pointer.
|
||||
SafeSearchConf filtering.SafeSearchConfig
|
||||
}
|
||||
|
||||
// setTags sets the tags if they are known, otherwise logs an unknown tag.
|
||||
func (c *persistentClient) setTags(tags []string, known *stringutil.Set) {
|
||||
// SetTags sets the tags if they are known, otherwise logs an unknown tag.
|
||||
func (c *Persistent) SetTags(tags []string, known *container.MapSet[string]) {
|
||||
for _, t := range tags {
|
||||
if !known.Has(t) {
|
||||
log.Info("skipping unknown tag %q", t)
|
||||
@@ -102,9 +113,9 @@ func (c *persistentClient) setTags(tags []string, known *stringutil.Set) {
|
||||
slices.Sort(c.Tags)
|
||||
}
|
||||
|
||||
// setIDs parses a list of strings into typed fields and returns an error if
|
||||
// SetIDs parses a list of strings into typed fields and returns an error if
|
||||
// there is one.
|
||||
func (c *persistentClient) setIDs(ids []string) (err error) {
|
||||
func (c *Persistent) SetIDs(ids []string) (err error) {
|
||||
for _, id := range ids {
|
||||
err = c.setID(id)
|
||||
if err != nil {
|
||||
@@ -144,7 +155,7 @@ func subnetCompare(x, y netip.Prefix) (cmp int) {
|
||||
}
|
||||
|
||||
// setID parses id into typed field if there is no error.
|
||||
func (c *persistentClient) setID(id string) (err error) {
|
||||
func (c *Persistent) setID(id string) (err error) {
|
||||
if id == "" {
|
||||
return errors.Error("clientid is empty")
|
||||
}
|
||||
@@ -170,7 +181,7 @@ func (c *persistentClient) setID(id string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = dnsforward.ValidateClientID(id)
|
||||
err = ValidateClientID(id)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
@@ -181,9 +192,23 @@ func (c *persistentClient) setID(id string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ids returns a list of client ids containing at least one element.
|
||||
func (c *persistentClient) ids() (ids []string) {
|
||||
ids = make([]string, 0, c.idsLen())
|
||||
// ValidateClientID returns an error if id is not a valid ClientID.
|
||||
//
|
||||
// TODO(s.chzhen): It's an exact copy of the [dnsforward.ValidateClientID] to
|
||||
// avoid the import cycle. Remove it.
|
||||
func ValidateClientID(id string) (err error) {
|
||||
err = netutil.ValidateHostnameLabel(id)
|
||||
if err != nil {
|
||||
// Replace the domain name label wrapper with our own.
|
||||
return fmt.Errorf("invalid clientid %q: %w", id, errors.Unwrap(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDs returns a list of client IDs containing at least one element.
|
||||
func (c *Persistent) IDs() (ids []string) {
|
||||
ids = make([]string, 0, c.IDsLen())
|
||||
|
||||
for _, ip := range c.IPs {
|
||||
ids = append(ids, ip.String())
|
||||
@@ -200,24 +225,24 @@ func (c *persistentClient) ids() (ids []string) {
|
||||
return append(ids, c.ClientIDs...)
|
||||
}
|
||||
|
||||
// idsLen returns a length of client ids.
|
||||
func (c *persistentClient) idsLen() (n int) {
|
||||
// IDsLen returns a length of client ids.
|
||||
func (c *Persistent) IDsLen() (n int) {
|
||||
return len(c.IPs) + len(c.Subnets) + len(c.MACs) + len(c.ClientIDs)
|
||||
}
|
||||
|
||||
// equalIDs returns true if the ids of the current and previous clients are the
|
||||
// EqualIDs returns true if the ids of the current and previous clients are the
|
||||
// same.
|
||||
func (c *persistentClient) equalIDs(prev *persistentClient) (equal bool) {
|
||||
func (c *Persistent) EqualIDs(prev *Persistent) (equal bool) {
|
||||
return slices.Equal(c.IPs, prev.IPs) &&
|
||||
slices.Equal(c.Subnets, prev.Subnets) &&
|
||||
slices.EqualFunc(c.MACs, prev.MACs, slices.Equal[net.HardwareAddr]) &&
|
||||
slices.Equal(c.ClientIDs, prev.ClientIDs)
|
||||
}
|
||||
|
||||
// shallowClone returns a deep copy of the client, except upstreamConfig,
|
||||
// ShallowClone returns a deep copy of the client, except upstreamConfig,
|
||||
// safeSearchConf, SafeSearch fields, because it's difficult to copy them.
|
||||
func (c *persistentClient) shallowClone() (clone *persistentClient) {
|
||||
clone = &persistentClient{}
|
||||
func (c *Persistent) ShallowClone() (clone *Persistent) {
|
||||
clone = &Persistent{}
|
||||
*clone = *c
|
||||
|
||||
clone.BlockedServices = c.BlockedServices.Clone()
|
||||
@@ -232,10 +257,10 @@ func (c *persistentClient) shallowClone() (clone *persistentClient) {
|
||||
return clone
|
||||
}
|
||||
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *persistentClient) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
if err = c.upstreamConfig.Close(); err != nil {
|
||||
// CloseUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Persistent) CloseUpstreams() (err error) {
|
||||
if c.UpstreamConfig != nil {
|
||||
if err = c.UpstreamConfig.Close(); err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -243,8 +268,8 @@ func (c *persistentClient) closeUpstreams() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setSafeSearch initializes and sets the safe search filter for this client.
|
||||
func (c *persistentClient) setSafeSearch(
|
||||
// SetSafeSearch initializes and sets the safe search filter for this client.
|
||||
func (c *Persistent) SetSafeSearch(
|
||||
conf filtering.SafeSearchConfig,
|
||||
cacheSize uint,
|
||||
cacheTTL time.Duration,
|
||||
@@ -1,4 +1,4 @@
|
||||
package home
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -27,10 +27,10 @@ func TestPersistentClient_EqualIDs(t *testing.T) {
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
want assert.BoolAssertionFunc
|
||||
name string
|
||||
ids []string
|
||||
prevIDs []string
|
||||
want assert.BoolAssertionFunc
|
||||
}{{
|
||||
name: "single_ip",
|
||||
ids: []string{ip1},
|
||||
@@ -110,15 +110,15 @@ func TestPersistentClient_EqualIDs(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := &persistentClient{}
|
||||
err := c.setIDs(tc.ids)
|
||||
c := &Persistent{}
|
||||
err := c.SetIDs(tc.ids)
|
||||
require.NoError(t, err)
|
||||
|
||||
prev := &persistentClient{}
|
||||
err = prev.setIDs(tc.prevIDs)
|
||||
prev := &Persistent{}
|
||||
err = prev.SetIDs(tc.prevIDs)
|
||||
require.NoError(t, err)
|
||||
|
||||
tc.want(t, c.equalIDs(prev))
|
||||
tc.want(t, c.EqualIDs(prev))
|
||||
})
|
||||
}
|
||||
}
|
||||
63
internal/client/runtimeindex.go
Normal file
63
internal/client/runtimeindex.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package client
|
||||
|
||||
import "net/netip"
|
||||
|
||||
// RuntimeIndex stores information about runtime clients.
|
||||
type RuntimeIndex struct {
|
||||
// index maps IP address to runtime client.
|
||||
index map[netip.Addr]*Runtime
|
||||
}
|
||||
|
||||
// NewRuntimeIndex returns initialized runtime index.
|
||||
func NewRuntimeIndex() (ri *RuntimeIndex) {
|
||||
return &RuntimeIndex{
|
||||
index: map[netip.Addr]*Runtime{},
|
||||
}
|
||||
}
|
||||
|
||||
// Client returns the saved runtime client by ip. If no such client exists,
|
||||
// returns nil.
|
||||
func (ri *RuntimeIndex) Client(ip netip.Addr) (rc *Runtime) {
|
||||
return ri.index[ip]
|
||||
}
|
||||
|
||||
// Add saves the runtime client in the index. IP address of a client must be
|
||||
// unique. See [Runtime.Client]. rc must not be nil.
|
||||
func (ri *RuntimeIndex) Add(rc *Runtime) {
|
||||
ip := rc.Addr()
|
||||
ri.index[ip] = rc
|
||||
}
|
||||
|
||||
// Size returns the number of the runtime clients.
|
||||
func (ri *RuntimeIndex) Size() (n int) {
|
||||
return len(ri.index)
|
||||
}
|
||||
|
||||
// Range calls f for each runtime client in an undefined order.
|
||||
func (ri *RuntimeIndex) Range(f func(rc *Runtime) (cont bool)) {
|
||||
for _, rc := range ri.index {
|
||||
if !f(rc) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete removes the runtime client by ip.
|
||||
func (ri *RuntimeIndex) Delete(ip netip.Addr) {
|
||||
delete(ri.index, ip)
|
||||
}
|
||||
|
||||
// DeleteBySource removes all runtime clients that have information only from
|
||||
// the specified source and returns the number of removed clients.
|
||||
func (ri *RuntimeIndex) DeleteBySource(src Source) (n int) {
|
||||
for ip, rc := range ri.index {
|
||||
rc.unset(src)
|
||||
|
||||
if rc.isEmpty() {
|
||||
delete(ri.index, ip)
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
85
internal/client/runtimeindex_test.go
Normal file
85
internal/client/runtimeindex_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRuntimeIndex(t *testing.T) {
|
||||
const cliSrc = client.SourceARP
|
||||
|
||||
var (
|
||||
ip1 = netip.MustParseAddr("1.1.1.1")
|
||||
ip2 = netip.MustParseAddr("2.2.2.2")
|
||||
ip3 = netip.MustParseAddr("3.3.3.3")
|
||||
)
|
||||
|
||||
ri := client.NewRuntimeIndex()
|
||||
currentSize := 0
|
||||
|
||||
testCases := []struct {
|
||||
ip netip.Addr
|
||||
name string
|
||||
hosts []string
|
||||
src client.Source
|
||||
}{{
|
||||
src: cliSrc,
|
||||
ip: ip1,
|
||||
name: "1",
|
||||
hosts: []string{"host1"},
|
||||
}, {
|
||||
src: cliSrc,
|
||||
ip: ip2,
|
||||
name: "2",
|
||||
hosts: []string{"host2"},
|
||||
}, {
|
||||
src: cliSrc,
|
||||
ip: ip3,
|
||||
name: "3",
|
||||
hosts: []string{"host3"},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := client.NewRuntime(tc.ip)
|
||||
rc.SetInfo(tc.src, tc.hosts)
|
||||
|
||||
ri.Add(rc)
|
||||
currentSize++
|
||||
|
||||
got := ri.Client(tc.ip)
|
||||
assert.Equal(t, rc, got)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("size", func(t *testing.T) {
|
||||
assert.Equal(t, currentSize, ri.Size())
|
||||
})
|
||||
|
||||
t.Run("range", func(t *testing.T) {
|
||||
s := 0
|
||||
|
||||
ri.Range(func(rc *client.Runtime) (cont bool) {
|
||||
s++
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, currentSize, s)
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
ri.Delete(ip1)
|
||||
currentSize--
|
||||
|
||||
assert.Equal(t, currentSize, ri.Size())
|
||||
})
|
||||
|
||||
t.Run("delete_by_src", func(t *testing.T) {
|
||||
assert.Equal(t, currentSize, ri.DeleteBySource(cliSrc))
|
||||
assert.Equal(t, 0, ri.Size())
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo15 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -43,7 +45,7 @@ func migrateTo15(diskConf yobj) (err error) {
|
||||
}
|
||||
diskConf["querylog"] = qlog
|
||||
|
||||
return coalesceError(
|
||||
return errors.Join(
|
||||
moveVal[bool](dns, qlog, "querylog_enabled", "enabled"),
|
||||
moveVal[bool](dns, qlog, "querylog_file_enabled", "file_enabled"),
|
||||
moveVal[any](dns, qlog, "querylog_interval", "interval"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo24 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -28,7 +30,7 @@ func migrateTo24(diskConf yobj) (err error) {
|
||||
diskConf["schema_version"] = 24
|
||||
|
||||
logObj := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveVal[string](diskConf, logObj, "log_file", "file"),
|
||||
moveVal[int](diskConf, logObj, "log_max_backups", "max_backups"),
|
||||
moveVal[int](diskConf, logObj, "log_max_size", "max_size"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo26 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -78,7 +80,7 @@ func migrateTo26(diskConf yobj) (err error) {
|
||||
}
|
||||
|
||||
filteringObj := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveSameVal[bool](dns, filteringObj, "filtering_enabled"),
|
||||
moveSameVal[int](dns, filteringObj, "filters_update_interval"),
|
||||
moveSameVal[bool](dns, filteringObj, "parental_enabled"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
|
||||
// migrateTo7 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
@@ -37,7 +39,7 @@ func migrateTo7(diskConf yobj) (err error) {
|
||||
}
|
||||
|
||||
dhcpv4 := yobj{}
|
||||
err = coalesceError(
|
||||
err = errors.Join(
|
||||
moveSameVal[string](dhcp, dhcpv4, "gateway_ip"),
|
||||
moveSameVal[string](dhcp, dhcpv4, "subnet_mask"),
|
||||
moveSameVal[string](dhcp, dhcpv4, "range_start"),
|
||||
|
||||
@@ -50,19 +50,3 @@ func moveVal[T any](src, dst yobj, srcKey, dstKey string) (err error) {
|
||||
func moveSameVal[T any](src, dst yobj, key string) (err error) {
|
||||
return moveVal[T](src, dst, key, key)
|
||||
}
|
||||
|
||||
// coalesceError returns the first non-nil error. It is named after function
|
||||
// COALESCE in SQL. If all errors are nil, it returns nil.
|
||||
//
|
||||
// TODO(e.burkov): Replace with [errors.Join].
|
||||
//
|
||||
// TODO(a.garipov): Think of ways to merge with [aghalg.Coalesce].
|
||||
func coalesceError(errors ...error) (res error) {
|
||||
for _, err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/renameio/v2/maybe"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
@@ -19,7 +20,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type v4ServerConfJSON struct {
|
||||
@@ -592,7 +592,7 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
|
||||
}
|
||||
|
||||
// parseLease parses a lease from r. If there is no error returns DHCPServer
|
||||
// and *Lease. r must be non-nil.
|
||||
// and *Lease. r must be non-nil.
|
||||
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) {
|
||||
l := &leaseStatic{}
|
||||
err = json.NewDecoder(r).Decode(l)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// v4Server is a DHCPv4 server.
|
||||
|
||||
@@ -2,11 +2,11 @@ package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Config is the configuration for the DHCP service.
|
||||
@@ -19,6 +19,8 @@ type Config struct {
|
||||
// clients' hostnames.
|
||||
LocalDomainName string
|
||||
|
||||
// TODO(e.burkov): Add DB path.
|
||||
|
||||
// ICMPTimeout is the timeout for checking another DHCP server's presence.
|
||||
ICMPTimeout time.Duration
|
||||
|
||||
@@ -68,12 +70,6 @@ func (conf *Config) Validate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
// validate returns an error in ic, if any.
|
||||
func (ic *InterfaceConfig) validate() (err error) {
|
||||
if ic == nil {
|
||||
|
||||
@@ -7,48 +7,16 @@ 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 applicable to Enabled method.
|
||||
//
|
||||
// TODO(e.burkov): Reconsider the requirements for the leases validity.
|
||||
type Interface interface {
|
||||
agh.ServiceWithConfig[*Config]
|
||||
|
||||
@@ -63,6 +31,8 @@ type Interface interface {
|
||||
// MACByIP returns the MAC address for the given IP address leased. It
|
||||
// returns nil if there is no such client, due to an assumption that a DHCP
|
||||
// client must always have a MAC address.
|
||||
//
|
||||
// TODO(e.burkov): Think of a contract for the returned value.
|
||||
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// IPByHost returns the IP address of the DHCP client with the given
|
||||
@@ -71,26 +41,29 @@ 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.
|
||||
Leases() (ls []*Lease)
|
||||
|
||||
// AddLease adds a new DHCP lease. It returns an error if the lease is
|
||||
// invalid or already exists.
|
||||
// AddLease adds a new DHCP lease. l must be valid. It returns an error if
|
||||
// l already exists.
|
||||
AddLease(l *Lease) (err error)
|
||||
|
||||
// UpdateStaticLease changes an existing DHCP lease. It returns an error if
|
||||
// there is no lease with such hardware addressor if new values are invalid
|
||||
// or already exist.
|
||||
// UpdateStaticLease replaces an existing static DHCP lease. l must be
|
||||
// valid. It returns an error if the lease with the given hardware address
|
||||
// doesn't exist or if other values match another existing lease.
|
||||
UpdateStaticLease(l *Lease) (err error)
|
||||
|
||||
// RemoveLease removes an existing DHCP lease. It returns an error if there
|
||||
// is no lease equal to l.
|
||||
// RemoveLease removes an existing DHCP lease. l must be valid. It returns
|
||||
// an error if there is no lease equal to l.
|
||||
RemoveLease(l *Lease) (err error)
|
||||
|
||||
// Reset removes all the DHCP leases.
|
||||
//
|
||||
// TODO(e.burkov): If it's really needed?
|
||||
Reset() (err error)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package dhcpsvc
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// errNilConfig is returned when a nil config met.
|
||||
@@ -9,3 +13,9 @@ const (
|
||||
// errNoInterfaces is returned when no interfaces found in configuration.
|
||||
errNoInterfaces errors.Error = "no interfaces specified"
|
||||
)
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
66
internal/dhcpsvc/interface.go
Normal file
66
internal/dhcpsvc/interface.go
Normal file
@@ -0,0 +1,66 @@
|
||||
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
|
||||
}
|
||||
|
||||
// updateLease replaces an existing lease within iface with the given one. It
|
||||
// returns an error if there is no lease with such hardware address.
|
||||
func (iface *netInterface) updateLease(l *Lease) (prev *Lease, err error) {
|
||||
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("no lease for mac %s", l.HWAddr)
|
||||
}
|
||||
|
||||
prev, iface.leases[i] = iface.leases[i], l
|
||||
|
||||
return prev, nil
|
||||
}
|
||||
|
||||
// removeLease removes an existing lease from iface. It returns an error if
|
||||
// there is no lease equal to l.
|
||||
func (iface *netInterface) removeLease(l *Lease) (err error) {
|
||||
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
|
||||
if !found {
|
||||
return fmt.Errorf("no lease for mac %s", l.HWAddr)
|
||||
}
|
||||
|
||||
iface.leases = slices.Delete(iface.leases, i, i+1)
|
||||
|
||||
return nil
|
||||
}
|
||||
52
internal/dhcpsvc/lease.go
Normal file
52
internal/dhcpsvc/lease.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
//
|
||||
// TODO(e.burkov): Add validation method.
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// compareLeaseMAC compares two [Lease]s by hardware address.
|
||||
func compareLeaseMAC(a, b *Lease) (res int) {
|
||||
return bytes.Compare(a.HWAddr, b.HWAddr)
|
||||
}
|
||||
126
internal/dhcpsvc/leaseindex.go
Normal file
126
internal/dhcpsvc/leaseindex.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// leaseIndex is the set of leases indexed by their identifiers for quick
|
||||
// lookup.
|
||||
type leaseIndex struct {
|
||||
// byAddr is a lookup shortcut for leases by their IP addresses.
|
||||
byAddr map[netip.Addr]*Lease
|
||||
|
||||
// byName is a lookup shortcut for leases by their hostnames.
|
||||
//
|
||||
// TODO(e.burkov): Use a slice of leases with the same hostname?
|
||||
byName map[string]*Lease
|
||||
}
|
||||
|
||||
// newLeaseIndex returns a new index for [Lease]s.
|
||||
func newLeaseIndex() *leaseIndex {
|
||||
return &leaseIndex{
|
||||
byAddr: map[netip.Addr]*Lease{},
|
||||
byName: map[string]*Lease{},
|
||||
}
|
||||
}
|
||||
|
||||
// leaseByAddr returns a lease by its IP address.
|
||||
func (idx *leaseIndex) leaseByAddr(addr netip.Addr) (l *Lease, ok bool) {
|
||||
l, ok = idx.byAddr[addr]
|
||||
|
||||
return l, ok
|
||||
}
|
||||
|
||||
// leaseByName returns a lease by its hostname.
|
||||
func (idx *leaseIndex) leaseByName(name string) (l *Lease, ok bool) {
|
||||
// TODO(e.burkov): Probably, use a case-insensitive comparison and store in
|
||||
// slice. This would require a benchmark.
|
||||
l, ok = idx.byName[strings.ToLower(name)]
|
||||
|
||||
return l, ok
|
||||
}
|
||||
|
||||
// clear removes all leases from idx.
|
||||
func (idx *leaseIndex) clear() {
|
||||
clear(idx.byAddr)
|
||||
clear(idx.byName)
|
||||
}
|
||||
|
||||
// add adds l into idx and into iface. l must be valid, iface should be
|
||||
// responsible for l's IP. It returns an error if l duplicates at least a
|
||||
// single value of another lease.
|
||||
func (idx *leaseIndex) add(l *Lease, iface *netInterface) (err error) {
|
||||
loweredName := strings.ToLower(l.Hostname)
|
||||
|
||||
if _, ok := idx.byAddr[l.IP]; ok {
|
||||
return fmt.Errorf("lease for ip %s already exists", l.IP)
|
||||
} else if _, ok = idx.byName[loweredName]; ok {
|
||||
return fmt.Errorf("lease for hostname %s already exists", l.Hostname)
|
||||
}
|
||||
|
||||
err = iface.insertLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idx.byAddr[l.IP] = l
|
||||
idx.byName[loweredName] = l
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove removes l from idx and from iface. l must be valid, iface should
|
||||
// contain the same lease or the lease itself. It returns an error if the lease
|
||||
// not found.
|
||||
func (idx *leaseIndex) remove(l *Lease, iface *netInterface) (err error) {
|
||||
loweredName := strings.ToLower(l.Hostname)
|
||||
|
||||
if _, ok := idx.byAddr[l.IP]; !ok {
|
||||
return fmt.Errorf("no lease for ip %s", l.IP)
|
||||
} else if _, ok = idx.byName[loweredName]; !ok {
|
||||
return fmt.Errorf("no lease for hostname %s", l.Hostname)
|
||||
}
|
||||
|
||||
err = iface.removeLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(idx.byAddr, l.IP)
|
||||
delete(idx.byName, loweredName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// update updates l in idx and in iface. l must be valid, iface should be
|
||||
// responsible for l's IP. It returns an error if l duplicates at least a
|
||||
// single value of another lease, except for the updated lease itself.
|
||||
func (idx *leaseIndex) update(l *Lease, iface *netInterface) (err error) {
|
||||
loweredName := strings.ToLower(l.Hostname)
|
||||
|
||||
existing, ok := idx.byAddr[l.IP]
|
||||
if ok && !slices.Equal(l.HWAddr, existing.HWAddr) {
|
||||
return fmt.Errorf("lease for ip %s already exists", l.IP)
|
||||
}
|
||||
|
||||
existing, ok = idx.byName[loweredName]
|
||||
if ok && !slices.Equal(l.HWAddr, existing.HWAddr) {
|
||||
return fmt.Errorf("lease for hostname %s already exists", l.Hostname)
|
||||
}
|
||||
|
||||
prev, err := iface.updateLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(idx.byAddr, prev.IP)
|
||||
delete(idx.byName, strings.ToLower(prev.Hostname))
|
||||
|
||||
idx.byAddr[l.IP] = l
|
||||
idx.byName[loweredName] = l
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,11 +2,15 @@ package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"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,21 @@ 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 leases index as well as leases in the interfaces.
|
||||
leasesMu *sync.RWMutex
|
||||
|
||||
// leases stores the DHCP leases for quick lookups.
|
||||
leases *leaseIndex
|
||||
|
||||
// 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 +49,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 +78,19 @@ 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{},
|
||||
leases: newLeaseIndex(),
|
||||
interfaces4: ifaces4,
|
||||
interfaces6: ifaces6,
|
||||
localTLD: conf.LocalDomainName,
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Load leases.
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -91,10 +105,140 @@ 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())
|
||||
}
|
||||
}
|
||||
for _, iface := range srv.interfaces6 {
|
||||
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.leases.leaseByAddr(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.leases.leaseByAddr(ip); ok {
|
||||
return l.HWAddr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) IPByHost(host string) (ip netip.Addr) {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leases.leaseByName(host); 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()
|
||||
}
|
||||
srv.leases.clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLease implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "adding lease: %w") }()
|
||||
|
||||
addr := l.IP
|
||||
iface, err := srv.ifaceForAddr(addr)
|
||||
if err != nil {
|
||||
// Don't wrap the error since there is already an annotation deferred.
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
return srv.leases.add(l, iface)
|
||||
}
|
||||
|
||||
// UpdateStaticLease implements the [Interface] interface for *DHCPServer.
|
||||
//
|
||||
// TODO(e.burkov): Support moving leases between interfaces.
|
||||
func (srv *DHCPServer) UpdateStaticLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "updating static lease: %w") }()
|
||||
|
||||
addr := l.IP
|
||||
iface, err := srv.ifaceForAddr(addr)
|
||||
if err != nil {
|
||||
// Don't wrap the error since there is already an annotation deferred.
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
return srv.leases.update(l, iface)
|
||||
}
|
||||
|
||||
// RemoveLease implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) RemoveLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "removing lease: %w") }()
|
||||
|
||||
addr := l.IP
|
||||
iface, err := srv.ifaceForAddr(addr)
|
||||
if err != nil {
|
||||
// Don't wrap the error since there is already an annotation deferred.
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
return srv.leases.remove(l, iface)
|
||||
}
|
||||
|
||||
// ifaceForAddr returns the handled network interface for the given IP address,
|
||||
// or an error if no such interface exists.
|
||||
func (srv *DHCPServer) ifaceForAddr(addr netip.Addr) (iface *netInterface, err error) {
|
||||
var ok bool
|
||||
if addr.Is4() {
|
||||
iface, ok = srv.interfaces4.find(addr)
|
||||
} else {
|
||||
iface, ok = srv.interfaces6.find(addr)
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no interface for ip %s", addr)
|
||||
}
|
||||
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
@@ -1,17 +1,67 @@
|
||||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"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.
|
||||
const testLocalTLD = "local"
|
||||
|
||||
// testInterfaceConf is a common set of interface configurations for tests.
|
||||
var testInterfaceConf = 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mustParseMAC parses a hardware address from s and requires no errors.
|
||||
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
|
||||
mac, err := net.ParseMAC(s)
|
||||
require.NoError(t, err)
|
||||
|
||||
return mac
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
validIPv4Conf := &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
@@ -113,3 +163,433 @@ func TestNew(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_AddLease(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: testInterfaceConf,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
)
|
||||
|
||||
ip1 := netip.MustParseAddr("192.168.0.2")
|
||||
ip2 := netip.MustParseAddr("192.168.0.3")
|
||||
ip3 := netip.MustParseAddr("2001:db8::2")
|
||||
|
||||
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
|
||||
mac2 := mustParseMAC(t, "06:05:04:03:02:01")
|
||||
mac3 := mustParseMAC(t, "02:03:04:05:06:07")
|
||||
|
||||
require.NoError(t, srv.AddLease(&dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "outside_range",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: netip.MustParseAddr("1.2.3.4"),
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "adding lease: no interface for ip 1.2.3.4",
|
||||
}, {
|
||||
name: "duplicate_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip1,
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for ip " + ip1.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for hostname " + host1 +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname_case",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: strings.ToUpper(host1),
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for hostname " +
|
||||
strings.ToUpper(host1) + " already exists",
|
||||
}, {
|
||||
name: "duplicate_mac",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip2,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for mac " + mac1.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(tc.lease))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_index(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: testInterfaceConf,
|
||||
})
|
||||
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 := mustParseMAC(t, "01:02:03:04:05:06")
|
||||
mac2 := mustParseMAC(t, "06:05:04:03:02:01")
|
||||
mac3 := mustParseMAC(t, "02:03:04:05:06:07")
|
||||
|
||||
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{}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: testInterfaceConf,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
host4 = "host4"
|
||||
host5 = "host5"
|
||||
host6 = "host6"
|
||||
)
|
||||
|
||||
ip1 := netip.MustParseAddr("192.168.0.2")
|
||||
ip2 := netip.MustParseAddr("192.168.0.3")
|
||||
ip3 := netip.MustParseAddr("192.168.0.4")
|
||||
ip4 := netip.MustParseAddr("2001:db8::2")
|
||||
ip5 := netip.MustParseAddr("2001:db8::3")
|
||||
|
||||
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
|
||||
mac2 := mustParseMAC(t, "01:02:03:04:05:07")
|
||||
mac3 := mustParseMAC(t, "06:05:04:03:02:01")
|
||||
mac4 := mustParseMAC(t, "06:05:04:03:02:02")
|
||||
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host2,
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host4,
|
||||
IP: ip4,
|
||||
HWAddr: mac4,
|
||||
IsStatic: true,
|
||||
}}
|
||||
for _, l := range leases {
|
||||
require.NoError(t, srv.AddLease(l))
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "outside_range",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: netip.MustParseAddr("1.2.3.4"),
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "updating static lease: no interface for ip 1.2.3.4",
|
||||
}, {
|
||||
name: "not_found",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
},
|
||||
wantErrMsg: "updating static lease: no lease for mac " + mac3.String(),
|
||||
}, {
|
||||
name: "duplicate_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip2,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for ip " + ip2.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for hostname " + host2 +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname_case",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: strings.ToUpper(host2),
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for hostname " +
|
||||
strings.ToUpper(host2) + " already exists",
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host6,
|
||||
IP: ip5,
|
||||
HWAddr: mac4,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(tc.lease))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_RemoveLease(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: testInterfaceConf,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
)
|
||||
|
||||
ip1 := netip.MustParseAddr("192.168.0.2")
|
||||
ip2 := netip.MustParseAddr("192.168.0.3")
|
||||
ip3 := netip.MustParseAddr("2001:db8::2")
|
||||
|
||||
mac1 := mustParseMAC(t, "01:02:03:04:05:06")
|
||||
mac2 := mustParseMAC(t, "02:03:04:05:06:07")
|
||||
mac3 := mustParseMAC(t, "06:05:04:03:02:01")
|
||||
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
IsStatic: true,
|
||||
}}
|
||||
for _, l := range leases {
|
||||
require.NoError(t, srv.AddLease(l))
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "not_found_mac",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac2,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for mac " + mac2.String(),
|
||||
}, {
|
||||
name: "not_found_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip2,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for ip " + ip2.String(),
|
||||
}, {
|
||||
name: "not_found_host",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for hostname " + host2,
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(tc.lease))
|
||||
})
|
||||
}
|
||||
|
||||
assert.Empty(t, srv.Leases())
|
||||
}
|
||||
|
||||
func TestDHCPServer_Reset(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: testInterfaceConf,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Hostname: "host1",
|
||||
IP: netip.MustParseAddr("192.168.0.2"),
|
||||
HWAddr: mustParseMAC(t, "01:02:03:04:05:06"),
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: "host2",
|
||||
IP: netip.MustParseAddr("192.168.0.3"),
|
||||
HWAddr: mustParseMAC(t, "06:05:04:03:02:01"),
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: "host3",
|
||||
IP: netip.MustParseAddr("2001:db8::2"),
|
||||
HWAddr: mustParseMAC(t, "02:03:04:05:06:07"),
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: "host4",
|
||||
IP: netip.MustParseAddr("2001:db8::3"),
|
||||
HWAddr: mustParseMAC(t, "06:05:04:03:02:02"),
|
||||
IsStatic: true,
|
||||
}}
|
||||
|
||||
for _, l := range leases {
|
||||
require.NoError(t, srv.AddLease(l))
|
||||
}
|
||||
|
||||
require.Len(t, srv.Leases(), len(leases))
|
||||
|
||||
require.NoError(t, srv.Reset())
|
||||
|
||||
assert.Empty(t, srv.Leases())
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
@@ -64,69 +64,6 @@ func (conf *IPv4Config) validate() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// iface4 is a DHCP interface for IPv4 address family.
|
||||
type iface4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
addrSpace ipRange
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// newIface4 creates a new DHCP interface for IPv4 address family with the given
|
||||
// configuration. It returns an error if the given configuration can't be used.
|
||||
func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
|
||||
if !conf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||
|
||||
switch {
|
||||
case !subnet.Contains(conf.RangeStart):
|
||||
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||
case !subnet.Contains(conf.RangeEnd):
|
||||
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||
}
|
||||
|
||||
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if addrSpace.contains(conf.GatewayIP) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
i = &iface4{
|
||||
name: name,
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
@@ -318,3 +255,83 @@ func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
|
||||
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
||||
// netInterfaceV4 is a DHCP interface for IPv4 address family.
|
||||
type netInterfaceV4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
addrSpace ipRange
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
}
|
||||
|
||||
// newNetInterfaceV4 creates a new DHCP interface for IPv4 address family with
|
||||
// the given configuration. It returns an error if the given configuration
|
||||
// can't be used.
|
||||
func newNetInterfaceV4(name string, conf *IPv4Config) (i *netInterfaceV4, err error) {
|
||||
if !conf.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||
|
||||
switch {
|
||||
case !subnet.Contains(conf.RangeStart):
|
||||
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||
case !subnet.Contains(conf.RangeEnd):
|
||||
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||
}
|
||||
|
||||
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if addrSpace.contains(conf.GatewayIP) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
i = &netInterfaceV4{
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV4 []*netInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package dhcpsvc
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
@@ -52,57 +53,6 @@ func (conf *IPv6Config) validate() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// iface6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type iface6 struct {
|
||||
// rangeStart is the first IP address in the range.
|
||||
rangeStart netip.Addr
|
||||
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPv6Options
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPv6Options
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
|
||||
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
|
||||
// flags.
|
||||
raSLAACOnly bool
|
||||
|
||||
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
|
||||
raAllowSLAAC bool
|
||||
}
|
||||
|
||||
// newIface6 creates a new DHCP interface for IPv6 address family with the given
|
||||
// configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newIface6(name string, conf *IPv6Config) (i *iface6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
i = &iface6{
|
||||
name: name,
|
||||
rangeStart: conf.RangeStart,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
@@ -133,3 +83,79 @@ func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
|
||||
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
|
||||
return int(a.Code) - int(b.Code)
|
||||
}
|
||||
|
||||
// netInterfaceV6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type netInterfaceV6 struct {
|
||||
// rangeStart is the first IP address in the range.
|
||||
rangeStart netip.Addr
|
||||
|
||||
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPv6Options
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPv6Options
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
|
||||
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
|
||||
// flags.
|
||||
raSLAACOnly bool
|
||||
|
||||
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
|
||||
raAllowSLAAC bool
|
||||
}
|
||||
|
||||
// newNetInterfaceV6 creates a new DHCP interface for IPv6 address family with
|
||||
// the given configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newNetInterfaceV6(name string, conf *IPv6Config) (i *netInterfaceV6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
i = &netInterfaceV6{
|
||||
rangeStart: conf.RangeStart,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV6 []*netInterfaceV6
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool) {
|
||||
// prefLen is the length of prefix to match ip against.
|
||||
//
|
||||
// TODO(e.burkov): DHCPv6 inherits the weird behavior of legacy
|
||||
// implementation where the allocated range constrained by the first address
|
||||
// and the first address with last byte set to 0xff. Proper prefixes should
|
||||
// be used instead.
|
||||
const prefLen = netutil.IPv6BitLen - 8
|
||||
|
||||
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV6) (contains bool) {
|
||||
return !ip.Less(iface.rangeStart) &&
|
||||
netip.PrefixFrom(iface.rangeStart, prefLen).Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
@@ -16,22 +18,19 @@ import (
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
)
|
||||
|
||||
// unit is a convenient alias for struct{}
|
||||
type unit = struct{}
|
||||
|
||||
// accessManager controls IP and client blocking that takes place before all
|
||||
// other processing. An accessManager is safe for concurrent use.
|
||||
type accessManager struct {
|
||||
allowedIPs map[netip.Addr]unit
|
||||
blockedIPs map[netip.Addr]unit
|
||||
allowedIPs *container.MapSet[netip.Addr]
|
||||
blockedIPs *container.MapSet[netip.Addr]
|
||||
|
||||
allowedClientIDs *stringutil.Set
|
||||
blockedClientIDs *stringutil.Set
|
||||
allowedClientIDs *container.MapSet[string]
|
||||
blockedClientIDs *container.MapSet[string]
|
||||
|
||||
// TODO(s.chzhen): Use [aghnet.IgnoreEngine].
|
||||
blockedHostsEng *urlfilter.DNSEngine
|
||||
|
||||
// TODO(a.garipov): Create a type for a set of IP networks.
|
||||
// TODO(a.garipov): Create a type for an efficient tree set of IP networks.
|
||||
allowedNets []netip.Prefix
|
||||
blockedNets []netip.Prefix
|
||||
}
|
||||
@@ -40,15 +39,15 @@ type accessManager struct {
|
||||
// which may be an IP address, a CIDR, or a ClientID.
|
||||
func processAccessClients(
|
||||
clientStrs []string,
|
||||
ips map[netip.Addr]unit,
|
||||
ips *container.MapSet[netip.Addr],
|
||||
nets *[]netip.Prefix,
|
||||
clientIDs *stringutil.Set,
|
||||
clientIDs *container.MapSet[string],
|
||||
) (err error) {
|
||||
for i, s := range clientStrs {
|
||||
var ip netip.Addr
|
||||
var ipnet netip.Prefix
|
||||
if ip, err = netip.ParseAddr(s); err == nil {
|
||||
ips[ip] = unit{}
|
||||
ips.Add(ip)
|
||||
} else if ipnet, err = netip.ParsePrefix(s); err == nil {
|
||||
*nets = append(*nets, ipnet)
|
||||
} else {
|
||||
@@ -67,11 +66,11 @@ func processAccessClients(
|
||||
// newAccessCtx creates a new accessCtx.
|
||||
func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessManager, err error) {
|
||||
a = &accessManager{
|
||||
allowedIPs: map[netip.Addr]unit{},
|
||||
blockedIPs: map[netip.Addr]unit{},
|
||||
allowedIPs: container.NewMapSet[netip.Addr](),
|
||||
blockedIPs: container.NewMapSet[netip.Addr](),
|
||||
|
||||
allowedClientIDs: stringutil.NewSet(),
|
||||
blockedClientIDs: stringutil.NewSet(),
|
||||
allowedClientIDs: container.NewMapSet[string](),
|
||||
blockedClientIDs: container.NewMapSet[string](),
|
||||
}
|
||||
|
||||
err = processAccessClients(allowed, a.allowedIPs, &a.allowedNets, a.allowedClientIDs)
|
||||
@@ -109,7 +108,7 @@ func newAccessCtx(allowed, blocked, blockedHosts []string) (a *accessManager, er
|
||||
|
||||
// allowlistMode returns true if this *accessCtx is in the allowlist mode.
|
||||
func (a *accessManager) allowlistMode() (ok bool) {
|
||||
return len(a.allowedIPs) != 0 || a.allowedClientIDs.Len() != 0 || len(a.allowedNets) != 0
|
||||
return a.allowedIPs.Len() != 0 || a.allowedClientIDs.Len() != 0 || len(a.allowedNets) != 0
|
||||
}
|
||||
|
||||
// isBlockedClientID returns true if the ClientID should be blocked.
|
||||
@@ -152,12 +151,15 @@ func (a *accessManager) isBlockedIP(ip netip.Addr) (blocked bool, rule string) {
|
||||
ipnets = a.allowedNets
|
||||
}
|
||||
|
||||
if _, ok := ips[ip]; ok {
|
||||
if ips.Has(ip) {
|
||||
return blocked, ip.String()
|
||||
}
|
||||
|
||||
for _, ipnet := range ipnets {
|
||||
if ipnet.Contains(ip) {
|
||||
// Remove zone before checking because prefixes stip zones.
|
||||
//
|
||||
// TODO(d.kolyshev): Cover with tests.
|
||||
if ipnet.Contains(ip.WithZone("")) {
|
||||
return blocked, ipnet.String()
|
||||
}
|
||||
}
|
||||
@@ -176,9 +178,9 @@ func (s *Server) accessListJSON() (j accessListJSON) {
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
return accessListJSON{
|
||||
AllowedClients: stringutil.CloneSlice(s.conf.AllowedClients),
|
||||
DisallowedClients: stringutil.CloneSlice(s.conf.DisallowedClients),
|
||||
BlockedHosts: stringutil.CloneSlice(s.conf.BlockedHosts),
|
||||
AllowedClients: slices.Clone(s.conf.AllowedClients),
|
||||
DisallowedClients: slices.Clone(s.conf.DisallowedClients),
|
||||
BlockedHosts: slices.Clone(s.conf.BlockedHosts),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
116
internal/dnsforward/beforerequest.go
Normal file
116
internal/dnsforward/beforerequest.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// type check
|
||||
var _ proxy.BeforeRequestHandler = (*Server)(nil)
|
||||
|
||||
// HandleBefore is the handler that is called before any other processing,
|
||||
// including logs. It performs access checks and puts the client ID, if there
|
||||
// is one, into the server's cache.
|
||||
//
|
||||
// TODO(d.kolyshev): Extract to separate package.
|
||||
func (s *Server) HandleBefore(
|
||||
_ *proxy.Proxy,
|
||||
pctx *proxy.DNSContext,
|
||||
) (err error) {
|
||||
clientID, err := s.clientIDFromDNSContext(pctx)
|
||||
if err != nil {
|
||||
return &proxy.BeforeRequestError{
|
||||
Err: fmt.Errorf("getting clientid: %w", err),
|
||||
Response: s.NewMsgSERVFAIL(pctx.Req),
|
||||
}
|
||||
}
|
||||
|
||||
blocked, _ := s.IsBlockedClient(pctx.Addr.Addr(), clientID)
|
||||
if blocked {
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
|
||||
if len(pctx.Req.Question) == 1 {
|
||||
q := pctx.Req.Question[0]
|
||||
qt := q.Qtype
|
||||
host := aghnet.NormalizeDomain(q.Name)
|
||||
if s.access.isBlockedHost(host, qt) {
|
||||
log.Debug("access: request %s %s is in access blocklist", dns.Type(qt), host)
|
||||
|
||||
return s.preBlockedResponse(pctx)
|
||||
}
|
||||
}
|
||||
|
||||
if clientID != "" {
|
||||
key := [8]byte{}
|
||||
binary.BigEndian.PutUint64(key[:], pctx.RequestID)
|
||||
s.clientIDCache.Set(key[:], []byte(clientID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientIDFromDNSContext extracts the client's ID from the server name of the
|
||||
// client's DoT or DoQ request or the path of the client's DoH. If the protocol
|
||||
// is not one of these, clientID is an empty string and err is nil.
|
||||
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
clientID, err = clientIDFromDNSContextHTTPS(pctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking url: %w", err)
|
||||
} else if clientID != "" {
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// Go on and check the domain name as well.
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
hostSrvName := s.conf.ServerName
|
||||
if hostSrvName == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cliSrvName, err := clientServerName(pctx, proto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
clientID, err = clientIDFromClientServerName(
|
||||
hostSrvName,
|
||||
cliSrvName,
|
||||
s.conf.StrictSNICheck,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("clientid check: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// errAccessBlocked is a sentinel error returned when a request is blocked by
|
||||
// access settings.
|
||||
var errAccessBlocked errors.Error = "blocked by access settings"
|
||||
|
||||
// preBlockedResponse returns a protocol-appropriate response for a request that
|
||||
// was blocked by access settings.
|
||||
func (s *Server) preBlockedResponse(pctx *proxy.DNSContext) (err error) {
|
||||
if pctx.Proto == proxy.ProtoUDP || pctx.Proto == proxy.ProtoDNSCrypt {
|
||||
// Return nil so that dnsproxy drops the connection and thus
|
||||
// prevent DNS amplification attacks.
|
||||
return errAccessBlocked
|
||||
}
|
||||
|
||||
return &proxy.BeforeRequestError{
|
||||
Err: errAccessBlocked,
|
||||
Response: s.makeResponseREFUSED(pctx.Req),
|
||||
}
|
||||
}
|
||||
299
internal/dnsforward/beforerequest_internal_test.go
Normal file
299
internal/dnsforward/beforerequest_internal_test.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
blockedHost = "blockedhost.org"
|
||||
testFQDN = "example.org."
|
||||
dnsClientTimeout = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
func TestServer_HandleBefore_tls(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const clientID = "client-1"
|
||||
|
||||
testCases := []struct {
|
||||
clientSrvName string
|
||||
name string
|
||||
host string
|
||||
allowedClients []string
|
||||
disallowedClients []string
|
||||
blockedHosts []string
|
||||
wantRCode int
|
||||
}{{
|
||||
clientSrvName: tlsServerName,
|
||||
name: "allow_all",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: "%" + "." + tlsServerName,
|
||||
name: "invalid_client_id",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeServerFailure,
|
||||
}, {
|
||||
clientSrvName: clientID + "." + tlsServerName,
|
||||
name: "allowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{clientID},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: "client-2." + tlsServerName,
|
||||
name: "allowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{clientID},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "disallowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{clientID},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: clientID + "." + tlsServerName,
|
||||
name: "disallowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{clientID},
|
||||
blockedHosts: []string{},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "blocked_hosts_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantRCode: dns.RcodeSuccess,
|
||||
}, {
|
||||
clientSrvName: tlsServerName,
|
||||
name: "blocked_hosts_rejected",
|
||||
host: dns.Fqdn(blockedHost),
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantRCode: dns.RcodeRefused,
|
||||
}}
|
||||
|
||||
localAns := []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: testFQDN,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 3600,
|
||||
Rdlength: 4,
|
||||
},
|
||||
A: net.IP{1, 2, 3, 4},
|
||||
}}
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = localAns
|
||||
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, _ := createTestTLS(t, TLSConfig{
|
||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||
ServerName: tlsServerName,
|
||||
})
|
||||
|
||||
s.conf.UpstreamDNS = []string{localUpsAddr}
|
||||
|
||||
s.conf.AllowedClients = tc.allowedClients
|
||||
s.conf.DisallowedClients = tc.disallowedClients
|
||||
s.conf.BlockedHosts = tc.blockedHosts
|
||||
|
||||
err := s.Prepare(&s.conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: tc.clientSrvName,
|
||||
}
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "tcp-tls",
|
||||
TLSConfig: tlsConfig,
|
||||
Timeout: dnsClientTimeout,
|
||||
}
|
||||
|
||||
req := createTestMessage(tc.host)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoTLS).String()
|
||||
|
||||
reply, _, err := client.Exchange(req, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wantRCode, reply.Rcode)
|
||||
if tc.wantRCode == dns.RcodeSuccess {
|
||||
assert.Equal(t, localAns, reply.Answer)
|
||||
} else {
|
||||
assert.Empty(t, reply.Answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_HandleBefore_udp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
clientIPv4 = "127.0.0.1"
|
||||
clientIPv6 = "::1"
|
||||
)
|
||||
|
||||
clientIPs := []string{clientIPv4, clientIPv6}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
allowedClients []string
|
||||
disallowedClients []string
|
||||
blockedHosts []string
|
||||
wantTimeout bool
|
||||
}{{
|
||||
name: "allow_all",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "allowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: clientIPs,
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "allowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{"1:2:3::4"},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: true,
|
||||
}, {
|
||||
name: "disallowed_client_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{"1:2:3::4"},
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "disallowed_client_rejected",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: clientIPs,
|
||||
blockedHosts: []string{},
|
||||
wantTimeout: true,
|
||||
}, {
|
||||
name: "blocked_hosts_allowed",
|
||||
host: testFQDN,
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantTimeout: false,
|
||||
}, {
|
||||
name: "blocked_hosts_rejected",
|
||||
host: dns.Fqdn(blockedHost),
|
||||
allowedClients: []string{},
|
||||
disallowedClients: []string{},
|
||||
blockedHosts: []string{blockedHost},
|
||||
wantTimeout: true,
|
||||
}}
|
||||
|
||||
localAns := []dns.RR{&dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: testFQDN,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 3600,
|
||||
Rdlength: 4,
|
||||
},
|
||||
A: net.IP{1, 2, 3, 4},
|
||||
}}
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = localAns
|
||||
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
Config: Config{
|
||||
AllowedClients: tc.allowedClients,
|
||||
DisallowedClients: tc.disallowedClients,
|
||||
BlockedHosts: tc.blockedHosts,
|
||||
UpstreamDNS: []string{localUpsAddr},
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "udp",
|
||||
Timeout: dnsClientTimeout,
|
||||
}
|
||||
|
||||
req := createTestMessage(tc.host)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
|
||||
reply, _, err := client.Exchange(req, addr)
|
||||
if tc.wantTimeout {
|
||||
wantErr := &net.OpError{}
|
||||
require.ErrorAs(t, err, &wantErr)
|
||||
assert.True(t, wantErr.Timeout())
|
||||
|
||||
assert.Nil(t, reply)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, reply)
|
||||
|
||||
assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
|
||||
assert.Equal(t, localAns, reply.Answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
)
|
||||
|
||||
// ValidateClientID returns an error if id is not a valid ClientID.
|
||||
//
|
||||
// Keep in sync with [client.ValidateClientID].
|
||||
func ValidateClientID(id string) (err error) {
|
||||
err = netutil.ValidateHostnameLabel(id)
|
||||
if err != nil {
|
||||
@@ -108,46 +110,6 @@ type quicConnection interface {
|
||||
ConnectionState() (cs quic.ConnectionState)
|
||||
}
|
||||
|
||||
// clientIDFromDNSContext extracts the client's ID from the server name of the
|
||||
// client's DoT or DoQ request or the path of the client's DoH. If the protocol
|
||||
// is not one of these, clientID is an empty string and err is nil.
|
||||
func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string, err error) {
|
||||
proto := pctx.Proto
|
||||
if proto == proxy.ProtoHTTPS {
|
||||
clientID, err = clientIDFromDNSContextHTTPS(pctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking url: %w", err)
|
||||
} else if clientID != "" {
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// Go on and check the domain name as well.
|
||||
} else if proto != proxy.ProtoTLS && proto != proxy.ProtoQUIC {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
hostSrvName := s.conf.ServerName
|
||||
if hostSrvName == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
cliSrvName, err := clientServerName(pctx, proto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
clientID, err = clientIDFromClientServerName(
|
||||
hostSrvName,
|
||||
cliSrvName,
|
||||
s.conf.StrictSNICheck,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("clientid check: %w", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// clientServerName returns the TLS server name based on the protocol. For
|
||||
// DNS-over-HTTPS requests, it will return the hostname part of the Host header
|
||||
// if there is one.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -18,13 +19,13 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"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 +41,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
|
||||
@@ -234,9 +235,18 @@ type DNSCryptConfig struct {
|
||||
// ServerConfig represents server configuration.
|
||||
// The zero ServerConfig is empty and ready for use.
|
||||
type ServerConfig struct {
|
||||
UDPListenAddrs []*net.UDPAddr // UDP listen address
|
||||
TCPListenAddrs []*net.TCPAddr // TCP listen address
|
||||
UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config
|
||||
// UDPListenAddrs is the list of addresses to listen for DNS-over-UDP.
|
||||
UDPListenAddrs []*net.UDPAddr
|
||||
|
||||
// TCPListenAddrs is the list of addresses to listen for DNS-over-TCP.
|
||||
TCPListenAddrs []*net.TCPAddr
|
||||
|
||||
// UpstreamConfig is the general configuration of upstream DNS servers.
|
||||
UpstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
// PrivateRDNSUpstreamConfig is the configuration of upstream DNS servers
|
||||
// for private reverse DNS.
|
||||
PrivateRDNSUpstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
// AddrProcConf defines the configuration for the client IP processor.
|
||||
// If nil, [client.EmptyAddrProc] is used.
|
||||
@@ -305,24 +315,28 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
||||
trustedPrefixes := netutil.UnembedPrefixes(srvConf.TrustedProxies)
|
||||
|
||||
conf = &proxy.Config{
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: netutil.SliceSubnetSet(trustedPrefixes),
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
HTTP3: srvConf.ServeHTTP3,
|
||||
Ratelimit: int(srvConf.Ratelimit),
|
||||
RatelimitSubnetLenIPv4: srvConf.RatelimitSubnetLenIPv4,
|
||||
RatelimitSubnetLenIPv6: srvConf.RatelimitSubnetLenIPv6,
|
||||
RatelimitWhitelist: srvConf.RatelimitWhitelist,
|
||||
RefuseAny: srvConf.RefuseAny,
|
||||
TrustedProxies: netutil.SliceSubnetSet(trustedPrefixes),
|
||||
CacheMinTTL: srvConf.CacheMinTTL,
|
||||
CacheMaxTTL: srvConf.CacheMaxTTL,
|
||||
CacheOptimistic: srvConf.CacheOptimistic,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
PrivateRDNSUpstreamConfig: srvConf.PrivateRDNSUpstreamConfig,
|
||||
BeforeRequestHandler: s,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
HTTPSServerName: aghhttp.UserAgent(),
|
||||
EnableEDNSClientSubnet: srvConf.EDNSClientSubnet.Enabled,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
||||
PrivateSubnets: s.privateNets,
|
||||
MessageConstructor: s,
|
||||
}
|
||||
|
||||
if srvConf.EDNSClientSubnet.UseCustom {
|
||||
@@ -357,10 +371,6 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) {
|
||||
conf.DNSCryptResolverCert = c.ResolverCert
|
||||
}
|
||||
|
||||
if conf.UpstreamConfig == nil || len(conf.UpstreamConfig.Upstreams) == 0 {
|
||||
return nil, errors.Error("no default upstream servers configured")
|
||||
}
|
||||
|
||||
conf, err = prepareCacheConfig(conf,
|
||||
srvConf.CacheSize,
|
||||
srvConf.CacheMinTTL,
|
||||
@@ -455,36 +465,58 @@ func (s *Server) prepareIpsetListSettings() (err error) {
|
||||
}
|
||||
|
||||
ipsets := stringutil.SplitTrimmed(string(data), "\n")
|
||||
ipsets = stringutil.FilterOut(ipsets, IsCommentOrEmpty)
|
||||
|
||||
log.Debug("dns: using %d ipset rules from file %q", len(ipsets), fn)
|
||||
|
||||
return s.ipset.init(ipsets)
|
||||
}
|
||||
|
||||
// loadUpstreams parses upstream DNS servers from the configured file or from
|
||||
// the configuration itself.
|
||||
func (conf *ServerConfig) loadUpstreams() (upstreams []string, err error) {
|
||||
if conf.UpstreamDNSFileName == "" {
|
||||
return stringutil.FilterOut(conf.UpstreamDNS, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
data, err = os.ReadFile(conf.UpstreamDNSFileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading upstream from file: %w", err)
|
||||
}
|
||||
|
||||
upstreams = stringutil.SplitTrimmed(string(data), "\n")
|
||||
|
||||
log.Debug("dnsforward: got %d upstreams in %q", len(upstreams), conf.UpstreamDNSFileName)
|
||||
|
||||
return stringutil.FilterOut(upstreams, IsCommentOrEmpty), nil
|
||||
}
|
||||
|
||||
// collectListenAddr adds addrPort to addrs. It also adds its port to
|
||||
// unspecPorts if its address is unspecified.
|
||||
func collectListenAddr(
|
||||
addrPort netip.AddrPort,
|
||||
addrs map[netip.AddrPort]unit,
|
||||
unspecPorts map[uint16]unit,
|
||||
addrs *container.MapSet[netip.AddrPort],
|
||||
unspecPorts *container.MapSet[uint16],
|
||||
) {
|
||||
if addrPort == (netip.AddrPort{}) {
|
||||
return
|
||||
}
|
||||
|
||||
addrs[addrPort] = unit{}
|
||||
addrs.Add(addrPort)
|
||||
if addrPort.Addr().IsUnspecified() {
|
||||
unspecPorts[addrPort.Port()] = unit{}
|
||||
unspecPorts.Add(addrPort.Port())
|
||||
}
|
||||
}
|
||||
|
||||
// collectDNSAddrs returns configured set of listening addresses. It also
|
||||
// returns a set of ports of each unspecified listening address.
|
||||
func (conf *ServerConfig) collectDNSAddrs() (addrs mapAddrPortSet, unspecPorts map[uint16]unit) {
|
||||
// TODO(e.burkov): Perhaps, we shouldn't allocate as much memory, since the
|
||||
// TCP and UDP listening addresses are currently the same.
|
||||
addrs = make(map[netip.AddrPort]unit, len(conf.TCPListenAddrs)+len(conf.UDPListenAddrs))
|
||||
unspecPorts = map[uint16]unit{}
|
||||
func (conf *ServerConfig) collectDNSAddrs() (
|
||||
addrs *container.MapSet[netip.AddrPort],
|
||||
unspecPorts *container.MapSet[uint16],
|
||||
) {
|
||||
addrs = container.NewMapSet[netip.AddrPort]()
|
||||
unspecPorts = container.NewMapSet[uint16]()
|
||||
|
||||
for _, laddr := range conf.TCPListenAddrs {
|
||||
collectListenAddr(laddr.AddrPort(), addrs, unspecPorts)
|
||||
@@ -515,26 +547,12 @@ type emptyAddrPortSet struct{}
|
||||
// Has implements the [addrPortSet] interface for [emptyAddrPortSet].
|
||||
func (emptyAddrPortSet) Has(_ netip.AddrPort) (ok bool) { return false }
|
||||
|
||||
// mapAddrPortSet is the [addrPortSet] containing values of [netip.AddrPort] as
|
||||
// keys of a map.
|
||||
type mapAddrPortSet map[netip.AddrPort]unit
|
||||
|
||||
// type check
|
||||
var _ addrPortSet = mapAddrPortSet{}
|
||||
|
||||
// Has implements the [addrPortSet] interface for [mapAddrPortSet].
|
||||
func (m mapAddrPortSet) Has(addrPort netip.AddrPort) (ok bool) {
|
||||
_, ok = m[addrPort]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// combinedAddrPortSet is the [addrPortSet] defined by some IP addresses along
|
||||
// with ports, any combination of which is considered being in the set.
|
||||
type combinedAddrPortSet struct {
|
||||
// TODO(e.burkov): Use sorted slices in combination with binary search.
|
||||
ports map[uint16]unit
|
||||
addrs []netip.Addr
|
||||
// TODO(e.burkov): Use container.SliceSet when available.
|
||||
ports *container.MapSet[uint16]
|
||||
addrs *container.MapSet[netip.Addr]
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -542,13 +560,11 @@ var _ addrPortSet = (*combinedAddrPortSet)(nil)
|
||||
|
||||
// Has implements the [addrPortSet] interface for [*combinedAddrPortSet].
|
||||
func (m *combinedAddrPortSet) Has(addrPort netip.AddrPort) (ok bool) {
|
||||
_, ok = m.ports[addrPort.Port()]
|
||||
|
||||
return ok && slices.Contains(m.addrs, addrPort.Addr())
|
||||
return m.ports.Has(addrPort.Port()) && m.addrs.Has(addrPort.Addr())
|
||||
}
|
||||
|
||||
// filterOut filters out all the upstreams that match um. It returns all the
|
||||
// closing errors joined.
|
||||
// filterOutAddrs filters out all the upstreams that match um. It returns all
|
||||
// the closing errors joined.
|
||||
func filterOutAddrs(upsConf *proxy.UpstreamConfig, set addrPortSet) (err error) {
|
||||
var errs []error
|
||||
delFunc := func(u upstream.Upstream) (ok bool) {
|
||||
@@ -582,11 +598,11 @@ func filterOutAddrs(upsConf *proxy.UpstreamConfig, set addrPortSet) (err error)
|
||||
func (conf *ServerConfig) ourAddrsSet() (m addrPortSet, err error) {
|
||||
addrs, unspecPorts := conf.collectDNSAddrs()
|
||||
switch {
|
||||
case len(addrs) == 0:
|
||||
case addrs.Len() == 0:
|
||||
log.Debug("dnsforward: no listen addresses")
|
||||
|
||||
return emptyAddrPortSet{}, nil
|
||||
case len(unspecPorts) == 0:
|
||||
case unspecPorts.Len() == 0:
|
||||
log.Debug("dnsforward: filtering out addresses %s", addrs)
|
||||
|
||||
return addrs, nil
|
||||
@@ -602,7 +618,7 @@ func (conf *ServerConfig) ourAddrsSet() (m addrPortSet, err error) {
|
||||
|
||||
return &combinedAddrPortSet{
|
||||
ports: unspecPorts,
|
||||
addrs: ifaceAddrs,
|
||||
addrs: container.NewMapSet(ifaceAddrs...),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestAnyNameMatches(t *testing.T) {
|
||||
|
||||
@@ -2,54 +2,56 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// upstreamConfigValidator parses the [*proxy.UpstreamConfig] and checks the
|
||||
// actual DNS availability of each upstream.
|
||||
// upstreamConfigValidator parses each section of an upstream configuration into
|
||||
// a corresponding [*proxy.UpstreamConfig] and checks the actual DNS
|
||||
// availability of each upstream.
|
||||
type upstreamConfigValidator struct {
|
||||
// general is the general upstream configuration.
|
||||
general []*upstreamResult
|
||||
// generalUpstreamResults contains upstream results of a general section.
|
||||
generalUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// fallback is the fallback upstream configuration.
|
||||
fallback []*upstreamResult
|
||||
// fallbackUpstreamResults contains upstream results of a fallback section.
|
||||
fallbackUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// private is the private upstream configuration.
|
||||
private []*upstreamResult
|
||||
// privateUpstreamResults contains upstream results of a private section.
|
||||
privateUpstreamResults map[string]*upstreamResult
|
||||
|
||||
// generalParseResults contains parsing results of a general section.
|
||||
generalParseResults []*parseResult
|
||||
|
||||
// fallbackParseResults contains parsing results of a fallback section.
|
||||
fallbackParseResults []*parseResult
|
||||
|
||||
// privateParseResults contains parsing results of a private section.
|
||||
privateParseResults []*parseResult
|
||||
}
|
||||
|
||||
// upstreamResult is a result of validation of an [upstream.Upstream] within an
|
||||
// upstreamResult is a result of parsing of an [upstream.Upstream] within an
|
||||
// [proxy.UpstreamConfig].
|
||||
type upstreamResult struct {
|
||||
// server is the parsed upstream. It is nil when there was an error during
|
||||
// parsing.
|
||||
// server is the parsed upstream.
|
||||
server upstream.Upstream
|
||||
|
||||
// err is the error either from parsing or from checking the upstream.
|
||||
// err is the upstream check error.
|
||||
err error
|
||||
|
||||
// original is the piece of configuration that have either been turned to an
|
||||
// upstream or caused an error.
|
||||
original string
|
||||
|
||||
// isSpecific is true if the upstream is domain-specific.
|
||||
isSpecific bool
|
||||
}
|
||||
|
||||
// compare compares two [upstreamResult]s. It returns 0 if they are equal, -1
|
||||
// if ur should be sorted before other, and 1 otherwise.
|
||||
//
|
||||
// TODO(e.burkov): Perhaps it makes sense to sort the results with errors near
|
||||
// the end.
|
||||
func (ur *upstreamResult) compare(other *upstreamResult) (res int) {
|
||||
return strings.Compare(ur.original, other.original)
|
||||
// parseResult contains a original piece of upstream configuration and a
|
||||
// corresponding error.
|
||||
type parseResult struct {
|
||||
err *proxy.ParseError
|
||||
original string
|
||||
}
|
||||
|
||||
// newUpstreamConfigValidator parses the upstream configuration and returns a
|
||||
@@ -61,97 +63,99 @@ func newUpstreamConfigValidator(
|
||||
private []string,
|
||||
opts *upstream.Options,
|
||||
) (cv *upstreamConfigValidator) {
|
||||
cv = &upstreamConfigValidator{}
|
||||
cv = &upstreamConfigValidator{
|
||||
generalUpstreamResults: map[string]*upstreamResult{},
|
||||
fallbackUpstreamResults: map[string]*upstreamResult{},
|
||||
privateUpstreamResults: map[string]*upstreamResult{},
|
||||
}
|
||||
|
||||
for _, line := range general {
|
||||
cv.general = cv.insertLineResults(cv.general, line, opts)
|
||||
}
|
||||
for _, line := range fallback {
|
||||
cv.fallback = cv.insertLineResults(cv.fallback, line, opts)
|
||||
}
|
||||
for _, line := range private {
|
||||
cv.private = cv.insertLineResults(cv.private, line, opts)
|
||||
}
|
||||
conf, err := proxy.ParseUpstreamsConfig(general, opts)
|
||||
cv.generalParseResults = collectErrResults(general, err)
|
||||
insertConfResults(conf, cv.generalUpstreamResults)
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(fallback, opts)
|
||||
cv.fallbackParseResults = collectErrResults(fallback, err)
|
||||
insertConfResults(conf, cv.fallbackUpstreamResults)
|
||||
|
||||
conf, err = proxy.ParseUpstreamsConfig(private, opts)
|
||||
cv.privateParseResults = collectErrResults(private, err)
|
||||
insertConfResults(conf, cv.privateUpstreamResults)
|
||||
|
||||
return cv
|
||||
}
|
||||
|
||||
// insertLineResults parses line and inserts the result into s. It can insert
|
||||
// multiple results as well as none.
|
||||
func (cv *upstreamConfigValidator) insertLineResults(
|
||||
s []*upstreamResult,
|
||||
line string,
|
||||
opts *upstream.Options,
|
||||
) (result []*upstreamResult) {
|
||||
upstreams, isSpecific, err := splitUpstreamLine(line)
|
||||
if err != nil {
|
||||
return cv.insert(s, &upstreamResult{
|
||||
err: err,
|
||||
original: line,
|
||||
})
|
||||
// collectErrResults parses err and returns parsing results containing the
|
||||
// original upstream configuration line and the corresponding error. err can be
|
||||
// nil.
|
||||
func collectErrResults(lines []string, err error) (results []*parseResult) {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, upstreamAddr := range upstreams {
|
||||
var res *upstreamResult
|
||||
if upstreamAddr != "#" {
|
||||
res = cv.parseUpstream(upstreamAddr, opts)
|
||||
} else if !isSpecific {
|
||||
res = &upstreamResult{
|
||||
err: errNotDomainSpecific,
|
||||
original: upstreamAddr,
|
||||
}
|
||||
} else {
|
||||
// limit is a maximum length for upstream configuration lines.
|
||||
const limit = 80
|
||||
|
||||
wrapper, ok := err.(errors.WrapperSlice)
|
||||
if !ok {
|
||||
log.Debug("dnsforward: configvalidator: unwrapping: %s", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := wrapper.Unwrap()
|
||||
results = make([]*parseResult, 0, len(errs))
|
||||
for i, e := range errs {
|
||||
var parseErr *proxy.ParseError
|
||||
if !errors.As(e, &parseErr) {
|
||||
log.Debug("dnsforward: configvalidator: inserting unexpected error %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
res.isSpecific = isSpecific
|
||||
s = cv.insert(s, res)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// insert inserts r into slice in a sorted order, except duplicates. slice must
|
||||
// not be nil.
|
||||
func (cv *upstreamConfigValidator) insert(
|
||||
s []*upstreamResult,
|
||||
r *upstreamResult,
|
||||
) (result []*upstreamResult) {
|
||||
i, has := slices.BinarySearchFunc(s, r, (*upstreamResult).compare)
|
||||
if has {
|
||||
log.Debug("dnsforward: duplicate configuration %q", r.original)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
return slices.Insert(s, i, r)
|
||||
}
|
||||
|
||||
// parseUpstream parses addr and returns the result of parsing. It returns nil
|
||||
// if the specified server points at the default upstream server which is
|
||||
// validated separately.
|
||||
func (cv *upstreamConfigValidator) parseUpstream(
|
||||
addr string,
|
||||
opts *upstream.Options,
|
||||
) (r *upstreamResult) {
|
||||
// Check if the upstream has a valid protocol prefix.
|
||||
//
|
||||
// TODO(e.burkov): Validate the domain name.
|
||||
if proto, _, ok := strings.Cut(addr, "://"); ok {
|
||||
if !slices.Contains(protocols, proto) {
|
||||
return &upstreamResult{
|
||||
err: fmt.Errorf("bad protocol %q", proto),
|
||||
original: addr,
|
||||
}
|
||||
idx := parseErr.Idx
|
||||
line := []rune(lines[idx])
|
||||
if len(line) > limit {
|
||||
line = line[:limit]
|
||||
line[limit-1] = '…'
|
||||
}
|
||||
|
||||
results = append(results, &parseResult{
|
||||
original: string(line),
|
||||
err: parseErr,
|
||||
})
|
||||
}
|
||||
|
||||
ups, err := upstream.AddressToUpstream(addr, opts)
|
||||
return results
|
||||
}
|
||||
|
||||
return &upstreamResult{
|
||||
server: ups,
|
||||
err: err,
|
||||
original: addr,
|
||||
// insertConfResults parses conf and inserts the upstream result into results.
|
||||
// It can insert multiple results as well as none.
|
||||
func insertConfResults(conf *proxy.UpstreamConfig, results map[string]*upstreamResult) {
|
||||
insertListResults(conf.Upstreams, results, false)
|
||||
|
||||
for _, ups := range conf.DomainReservedUpstreams {
|
||||
insertListResults(ups, results, true)
|
||||
}
|
||||
|
||||
for _, ups := range conf.SpecifiedDomainUpstreams {
|
||||
insertListResults(ups, results, true)
|
||||
}
|
||||
}
|
||||
|
||||
// insertListResults constructs upstream results from the upstream list and
|
||||
// inserts them into results. It can insert multiple results as well as none.
|
||||
func insertListResults(ups []upstream.Upstream, results map[string]*upstreamResult, specific bool) {
|
||||
for _, u := range ups {
|
||||
addr := u.Address()
|
||||
_, ok := results[addr]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
||||
results[addr] = &upstreamResult{
|
||||
server: u,
|
||||
isSpecific: specific,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,35 +191,30 @@ func (cv *upstreamConfigValidator) check() {
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(cv.general) + len(cv.fallback) + len(cv.private))
|
||||
wg.Add(len(cv.generalUpstreamResults) +
|
||||
len(cv.fallbackUpstreamResults) +
|
||||
len(cv.privateUpstreamResults))
|
||||
|
||||
for _, res := range cv.general {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
for _, res := range cv.generalUpstreamResults {
|
||||
go checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
go cv.checkSrv(res, wg, commonChecker)
|
||||
for _, res := range cv.fallbackUpstreamResults {
|
||||
go checkSrv(res, wg, commonChecker)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
go cv.checkSrv(res, wg, arpaChecker)
|
||||
for _, res := range cv.privateUpstreamResults {
|
||||
go checkSrv(res, wg, arpaChecker)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// checkSrv runs hc on the server from res, if any, and stores any occurred
|
||||
// error in res. wg is always marked done in the end. It used to be called in
|
||||
// a separate goroutine.
|
||||
func (cv *upstreamConfigValidator) checkSrv(
|
||||
res *upstreamResult,
|
||||
wg *sync.WaitGroup,
|
||||
hc *healthchecker,
|
||||
) {
|
||||
// error in res. wg is always marked done in the end. It is intended to be
|
||||
// used as a goroutine.
|
||||
func checkSrv(res *upstreamResult, wg *sync.WaitGroup, hc *healthchecker) {
|
||||
defer log.OnPanic(fmt.Sprintf("dnsforward: checking upstream %s", res.server.Address()))
|
||||
defer wg.Done()
|
||||
|
||||
if res.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
res.err = hc.check(res.server)
|
||||
if res.err != nil && res.isSpecific {
|
||||
res.err = domainSpecificTestError{Err: res.err}
|
||||
@@ -225,65 +224,126 @@ func (cv *upstreamConfigValidator) checkSrv(
|
||||
// close closes all the upstreams that were successfully parsed. It enriches
|
||||
// the results with deferred closing errors.
|
||||
func (cv *upstreamConfigValidator) close() {
|
||||
for _, slice := range [][]*upstreamResult{cv.general, cv.fallback, cv.private} {
|
||||
for _, r := range slice {
|
||||
if r.server != nil {
|
||||
r.err = errors.WithDeferred(r.err, r.server.Close())
|
||||
}
|
||||
all := []map[string]*upstreamResult{
|
||||
cv.generalUpstreamResults,
|
||||
cv.fallbackUpstreamResults,
|
||||
cv.privateUpstreamResults,
|
||||
}
|
||||
|
||||
for _, m := range all {
|
||||
for _, r := range m {
|
||||
r.err = errors.WithDeferred(r.err, r.server.Close())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sections of the upstream configuration according to the text label of the
|
||||
// localization.
|
||||
//
|
||||
// Keep in sync with client/src/__locales/en.json.
|
||||
//
|
||||
// TODO(s.chzhen): Refactor.
|
||||
const (
|
||||
generalTextLabel = "upstream_dns"
|
||||
fallbackTextLabel = "fallback_dns_title"
|
||||
privateTextLabel = "local_ptr_title"
|
||||
)
|
||||
|
||||
// status returns all the data collected during parsing, healthcheck, and
|
||||
// closing of the upstreams. The returned map is keyed by the original upstream
|
||||
// configuration piece and contains the corresponding error or "OK" if there was
|
||||
// no error.
|
||||
func (cv *upstreamConfigValidator) status() (results map[string]string) {
|
||||
result := map[string]string{}
|
||||
// Names of the upstream configuration sections for logging.
|
||||
const (
|
||||
generalSection = "general"
|
||||
fallbackSection = "fallback"
|
||||
privateSection = "private"
|
||||
)
|
||||
|
||||
for _, res := range cv.general {
|
||||
resultToStatus("general", res, result)
|
||||
results = map[string]string{}
|
||||
|
||||
for original, res := range cv.generalUpstreamResults {
|
||||
upstreamResultToStatus(generalSection, string(original), res, results)
|
||||
}
|
||||
for _, res := range cv.fallback {
|
||||
resultToStatus("fallback", res, result)
|
||||
for original, res := range cv.fallbackUpstreamResults {
|
||||
upstreamResultToStatus(fallbackSection, string(original), res, results)
|
||||
}
|
||||
for _, res := range cv.private {
|
||||
resultToStatus("private", res, result)
|
||||
for original, res := range cv.privateUpstreamResults {
|
||||
upstreamResultToStatus(privateSection, string(original), res, results)
|
||||
}
|
||||
|
||||
return result
|
||||
parseResultToStatus(generalTextLabel, generalSection, cv.generalParseResults, results)
|
||||
parseResultToStatus(fallbackTextLabel, fallbackSection, cv.fallbackParseResults, results)
|
||||
parseResultToStatus(privateTextLabel, privateSection, cv.privateParseResults, results)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// resultToStatus puts "OK" or an error message from res into resMap. section
|
||||
// is the name of the upstream configuration section, i.e. "general",
|
||||
// upstreamResultToStatus puts "OK" or an error message from res into resMap.
|
||||
// section is the name of the upstream configuration section, i.e. "general",
|
||||
// "fallback", or "private", and only used for logging.
|
||||
//
|
||||
// TODO(e.burkov): Currently, the HTTP handler expects that all the results are
|
||||
// put together in a single map, which may lead to collisions, see AG-27539.
|
||||
// Improve the results compilation.
|
||||
func resultToStatus(section string, res *upstreamResult, resMap map[string]string) {
|
||||
func upstreamResultToStatus(
|
||||
section string,
|
||||
original string,
|
||||
res *upstreamResult,
|
||||
resMap map[string]string,
|
||||
) {
|
||||
val := "OK"
|
||||
if res.err != nil {
|
||||
val = res.err.Error()
|
||||
}
|
||||
|
||||
prevVal := resMap[res.original]
|
||||
prevVal := resMap[original]
|
||||
switch prevVal {
|
||||
case "":
|
||||
resMap[res.original] = val
|
||||
resMap[original] = val
|
||||
case val:
|
||||
log.Debug("dnsforward: duplicating %s config line %q", section, res.original)
|
||||
log.Debug("dnsforward: duplicating %s config line %q", section, original)
|
||||
default:
|
||||
log.Debug(
|
||||
"dnsforward: warning: %s config line %q (%v) had different result %v",
|
||||
section,
|
||||
val,
|
||||
res.original,
|
||||
original,
|
||||
prevVal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// parseResultToStatus puts parsing error messages from results into resMap.
|
||||
// section is the name of the upstream configuration section, i.e. "general",
|
||||
// "fallback", or "private", and only used for logging.
|
||||
//
|
||||
// Parsing error message has the following format:
|
||||
//
|
||||
// sectionTextLabel line: parsing error
|
||||
//
|
||||
// Where sectionTextLabel is a section text label of a localization and line is
|
||||
// a line number.
|
||||
func parseResultToStatus(
|
||||
textLabel string,
|
||||
section string,
|
||||
results []*parseResult,
|
||||
resMap map[string]string,
|
||||
) {
|
||||
for _, res := range results {
|
||||
original := res.original
|
||||
_, ok := resMap[original]
|
||||
if ok {
|
||||
log.Debug("dnsforward: duplicating %s parsing error %q", section, original)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resMap[original] = fmt.Sprintf("%s %d: parsing error", textLabel, res.err.Idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
// domainSpecificTestError is a wrapper for errors returned by checkDNS to mark
|
||||
// the tested upstream domain-specific and therefore consider its errors
|
||||
// non-critical.
|
||||
@@ -342,7 +402,7 @@ func (h *healthchecker) check(u upstream.Upstream) (err error) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
} else if h.ansEmpty && len(reply.Answer) > 0 {
|
||||
return errWrongResponse
|
||||
return errors.Error("wrong response")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -3,15 +3,14 @@ package dnsforward
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -65,6 +64,8 @@ func newRR(t *testing.T, name string, qtype uint16, ttl uint32, val any) (rr dns
|
||||
}
|
||||
|
||||
func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
ipv4Domain = "ipv4.only."
|
||||
ipv6Domain = "ipv6.only."
|
||||
@@ -101,21 +102,6 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
type answerMap = map[uint16][sectionsNum][]dns.RR
|
||||
|
||||
pt := testutil.PanicT{}
|
||||
newUps := func(answers answerMap) (u upstream.Upstream) {
|
||||
return aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
q := req.Question[0]
|
||||
require.Contains(pt, answers, q.Qtype)
|
||||
|
||||
answer := answers[q.Qtype]
|
||||
|
||||
resp = (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = answer[sectionAnswer]
|
||||
resp.Ns = answer[sectionAuthority]
|
||||
resp.Extra = answer[sectionAdditional]
|
||||
|
||||
return resp, nil
|
||||
})
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -265,47 +251,113 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) {
|
||||
}}
|
||||
|
||||
localRR := newRR(t, ptr64Domain, dns.TypePTR, 3600, pointedDomain)
|
||||
localUps := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
require.Equal(pt, req.Question[0].Name, ptr64Domain)
|
||||
resp = (&dns.Msg{}).SetReply(req)
|
||||
localUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
require.Len(pt, m.Question, 1)
|
||||
require.Equal(pt, m.Question[0].Name, ptr64Domain)
|
||||
|
||||
resp := (&dns.Msg{}).SetReply(m)
|
||||
resp.Answer = []dns.RR{localRR}
|
||||
|
||||
return resp, nil
|
||||
require.NoError(t, w.WriteMsg(resp))
|
||||
})
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, localUpsHdlr).String()
|
||||
|
||||
client := &dns.Client{
|
||||
Net: "tcp",
|
||||
Timeout: 1 * time.Second,
|
||||
Net: string(proxy.ProtoTCP),
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// TODO(e.burkov): It seems [proxy.Proxy] isn't intended to be reused
|
||||
// right after stop, due to a data race in [proxy.Proxy.Init] method
|
||||
// when setting an OOB size. As a temporary workaround, recreate the
|
||||
// whole server for each test case.
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, localUps)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newUps(tc.upsAns)}
|
||||
t.Parallel()
|
||||
|
||||
upsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
q := req.Question[0]
|
||||
|
||||
require.Contains(pt, tc.upsAns, q.Qtype)
|
||||
answer := tc.upsAns[q.Qtype]
|
||||
|
||||
resp := (&dns.Msg{}).SetReply(req)
|
||||
resp.Answer = answer[sectionAnswer]
|
||||
resp.Ns = answer[sectionAuthority]
|
||||
resp.Extra = answer[sectionAdditional]
|
||||
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
upsAddr := aghtest.StartLocalhostUpstream(t, upsHdlr).String()
|
||||
|
||||
// TODO(e.burkov): It seems [proxy.Proxy] isn't intended to be
|
||||
// reused right after stop, due to a data race in [proxy.Proxy.Init]
|
||||
// method when setting an OOB size. As a temporary workaround,
|
||||
// recreate the whole server for each test case.
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
UpstreamDNS: []string{upsAddr},
|
||||
},
|
||||
UsePrivateRDNS: true,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
|
||||
startDeferStop(t, s)
|
||||
|
||||
req := (&dns.Msg{}).SetQuestion(tc.qname, tc.qtype)
|
||||
|
||||
resp, _, excErr := client.Exchange(req, s.dnsProxy.Addr(proxy.ProtoTCP).String())
|
||||
resp, _, excErr := client.Exchange(req, s.proxy().Addr(proxy.ProtoTCP).String())
|
||||
require.NoError(t, excErr)
|
||||
|
||||
require.Equal(t, tc.wantAns, resp.Answer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_dns64WithDisabledRDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Shouldn't go to upstream at all.
|
||||
panicHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
panic("not implemented")
|
||||
})
|
||||
upsAddr := aghtest.StartLocalhostUpstream(t, panicHdlr).String()
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, panicHdlr).String()
|
||||
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
UseDNS64: true,
|
||||
Config: Config{
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
UpstreamDNS: []string{upsAddr},
|
||||
},
|
||||
UsePrivateRDNS: false,
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
startDeferStop(t, s)
|
||||
|
||||
mappedIPv6 := net.ParseIP("64:ff9b::102:304")
|
||||
arpa, err := netutil.IPToReversedAddr(mappedIPv6)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := (&dns.Msg{}).SetQuestion(dns.Fqdn(arpa), dns.TypePTR)
|
||||
|
||||
cli := &dns.Client{
|
||||
Net: string(proxy.ProtoTCP),
|
||||
Timeout: testTimeout,
|
||||
}
|
||||
|
||||
resp, _, err := cli.Exchange(req, s.proxy().Addr(proxy.ProtoTCP).String())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, dns.RcodeNameError, resp.Rcode)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -9,12 +10,12 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -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
|
||||
@@ -135,12 +135,6 @@ type Server struct {
|
||||
// WHOIS, etc.
|
||||
addrProc client.AddressProcessor
|
||||
|
||||
// localResolvers is a DNS proxy instance used to resolve PTR records for
|
||||
// addresses considered private as per the [privateNets].
|
||||
//
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
localResolvers *proxy.Proxy
|
||||
|
||||
// sysResolvers used to fetch system resolvers to use by default for private
|
||||
// PTR resolving.
|
||||
sysResolvers SystemResolvers
|
||||
@@ -158,12 +152,6 @@ type Server struct {
|
||||
// [upstream.Resolver] interface.
|
||||
bootResolvers []*upstream.UpstreamResolver
|
||||
|
||||
// recDetector is a cache for recursive requests. It is used to detect and
|
||||
// prevent recursive requests only for private upstreams.
|
||||
//
|
||||
// See https://github.com/adguardTeam/adGuardHome/issues/3185#issuecomment-851048135.
|
||||
recDetector *recursionDetector
|
||||
|
||||
// dns64Pref is the NAT64 prefix used for DNS64 response mapping. The major
|
||||
// part of DNS64 happens inside the [proxy] package, but there still are
|
||||
// some places where response mapping is needed (e.g. DHCP).
|
||||
@@ -212,14 +200,6 @@ type DNSCreateParams struct {
|
||||
LocalDomain string
|
||||
}
|
||||
|
||||
const (
|
||||
// recursionTTL is the time recursive request is cached for.
|
||||
recursionTTL = 1 * time.Second
|
||||
// cachedRecurrentReqNum is the maximum number of cached recurrent
|
||||
// requests.
|
||||
cachedRecurrentReqNum = 1000
|
||||
)
|
||||
|
||||
// NewServer creates a new instance of the dnsforward.Server
|
||||
// Note: this function must be called only once
|
||||
//
|
||||
@@ -256,7 +236,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
// TODO(e.burkov): Use some case-insensitive string comparison.
|
||||
localDomainSuffix: strings.ToLower(localDomainSuffix),
|
||||
etcHosts: etcHosts,
|
||||
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
|
||||
clientIDCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
MaxCount: defaultClientIDCacheCount,
|
||||
@@ -308,13 +287,13 @@ func (s *Server) WriteDiskConfig(c *Config) {
|
||||
sc := s.conf.Config
|
||||
*c = sc
|
||||
c.RatelimitWhitelist = slices.Clone(sc.RatelimitWhitelist)
|
||||
c.BootstrapDNS = stringutil.CloneSlice(sc.BootstrapDNS)
|
||||
c.FallbackDNS = stringutil.CloneSlice(sc.FallbackDNS)
|
||||
c.AllowedClients = stringutil.CloneSlice(sc.AllowedClients)
|
||||
c.DisallowedClients = stringutil.CloneSlice(sc.DisallowedClients)
|
||||
c.BlockedHosts = stringutil.CloneSlice(sc.BlockedHosts)
|
||||
c.BootstrapDNS = slices.Clone(sc.BootstrapDNS)
|
||||
c.FallbackDNS = slices.Clone(sc.FallbackDNS)
|
||||
c.AllowedClients = slices.Clone(sc.AllowedClients)
|
||||
c.DisallowedClients = slices.Clone(sc.DisallowedClients)
|
||||
c.BlockedHosts = slices.Clone(sc.BlockedHosts)
|
||||
c.TrustedProxies = slices.Clone(sc.TrustedProxies)
|
||||
c.UpstreamDNS = stringutil.CloneSlice(sc.UpstreamDNS)
|
||||
c.UpstreamDNS = slices.Clone(sc.UpstreamDNS)
|
||||
}
|
||||
|
||||
// LocalPTRResolvers returns the current local PTR resolver configuration.
|
||||
@@ -322,7 +301,7 @@ func (s *Server) LocalPTRResolvers() (localPTRResolvers []string) {
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
return stringutil.CloneSlice(s.conf.LocalPTRResolvers)
|
||||
return slices.Clone(s.conf.LocalPTRResolvers)
|
||||
}
|
||||
|
||||
// AddrProcConfig returns the current address processing configuration. Only
|
||||
@@ -366,6 +345,7 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
|
||||
s.serverLock.RLock()
|
||||
defer s.serverLock.RUnlock()
|
||||
|
||||
// TODO(e.burkov): Migrate to [netip.Addr] already.
|
||||
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("reversing ip: %w", err)
|
||||
@@ -386,25 +366,23 @@ func (s *Server) Exchange(ip netip.Addr) (host string, ttl time.Duration, err er
|
||||
}
|
||||
|
||||
dctx := &proxy.DNSContext{
|
||||
Proto: "udp",
|
||||
Req: req,
|
||||
Proto: proxy.ProtoUDP,
|
||||
Req: req,
|
||||
IsPrivateClient: true,
|
||||
}
|
||||
|
||||
var resolver *proxy.Proxy
|
||||
var errMsg string
|
||||
if s.privateNets.Contains(ip) {
|
||||
if !s.conf.UsePrivateRDNS {
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
resolver = s.localResolvers
|
||||
errMsg = "resolving a private address: %w"
|
||||
s.recDetector.add(*req)
|
||||
dctx.RequestedPrivateRDNS = netip.PrefixFrom(ip, ip.BitLen())
|
||||
} else {
|
||||
resolver = s.internalProxy
|
||||
errMsg = "resolving an address: %w"
|
||||
}
|
||||
if err = resolver.Resolve(dctx); err != nil {
|
||||
if err = s.internalProxy.Resolve(dctx); err != nil {
|
||||
return "", 0, fmt.Errorf(errMsg, err)
|
||||
}
|
||||
|
||||
@@ -464,7 +442,8 @@ func (s *Server) Start() error {
|
||||
// startLocked starts the DNS server without locking. s.serverLock is expected
|
||||
// to be locked.
|
||||
func (s *Server) startLocked() error {
|
||||
err := s.dnsProxy.Start()
|
||||
// TODO(e.burkov): Use context properly.
|
||||
err := s.dnsProxy.Start(context.Background())
|
||||
if err == nil {
|
||||
s.isRunning = true
|
||||
}
|
||||
@@ -472,82 +451,6 @@ func (s *Server) startLocked() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepareLocalResolvers initializes the local upstreams configuration using
|
||||
// boot as bootstrap. It assumes that s.serverLock is locked or s not running.
|
||||
func (s *Server) prepareLocalResolvers(
|
||||
boot upstream.Resolver,
|
||||
) (uc *proxy.UpstreamConfig, err error) {
|
||||
set, err := s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := s.conf.LocalPTRResolvers
|
||||
confNeedsFiltering := len(resolvers) > 0
|
||||
if confNeedsFiltering {
|
||||
resolvers = stringutil.FilterOut(resolvers, IsCommentOrEmpty)
|
||||
} else {
|
||||
sysResolvers := slices.DeleteFunc(slices.Clone(s.sysResolvers.Addrs()), set.Has)
|
||||
resolvers = make([]string, 0, len(sysResolvers))
|
||||
for _, r := range sysResolvers {
|
||||
resolvers = append(resolvers, r.String())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: upstreams to resolve ptr for local addresses: %v", resolvers)
|
||||
|
||||
uc, err = s.prepareUpstreamConfig(resolvers, nil, &upstream.Options{
|
||||
Bootstrap: boot,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing private upstreams: %w", err)
|
||||
}
|
||||
|
||||
if confNeedsFiltering {
|
||||
err = filterOutAddrs(uc, set)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("filtering private upstreams: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// setupLocalResolvers initializes and sets the resolvers for local addresses.
|
||||
// It assumes s.serverLock is locked or s not running.
|
||||
func (s *Server) setupLocalResolvers(boot upstream.Resolver) (err error) {
|
||||
uc, err := s.prepareLocalResolvers(boot)
|
||||
if err != nil {
|
||||
// Don't wrap the error because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
s.localResolvers = &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: uc,
|
||||
},
|
||||
}
|
||||
|
||||
err = s.localResolvers.Init()
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing proxy: %w", err)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Should we also consider the DNS64 usage?
|
||||
if s.conf.UsePrivateRDNS &&
|
||||
// Only set the upstream config if there are any upstreams. It's safe
|
||||
// to put nil into [proxy.Config.PrivateRDNSUpstreamConfig].
|
||||
len(uc.Upstreams)+len(uc.DomainReservedUpstreams)+len(uc.SpecifiedDomainUpstreams) > 0 {
|
||||
s.dnsProxy.PrivateRDNSUpstreamConfig = uc
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare initializes parameters of s using data from conf. conf must not be
|
||||
// nil.
|
||||
func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
@@ -564,7 +467,7 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
|
||||
s.initDefaultSettings()
|
||||
|
||||
boot, err := s.prepareInternalDNS()
|
||||
err = s.prepareInternalDNS()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
@@ -586,22 +489,17 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
return fmt.Errorf("preparing access: %w", err)
|
||||
}
|
||||
|
||||
// Set the proxy here because [setupLocalResolvers] sets its values.
|
||||
//
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
s.dnsProxy = &proxy.Proxy{Config: *proxyConfig}
|
||||
|
||||
err = s.setupLocalResolvers(boot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up resolvers: %w", err)
|
||||
}
|
||||
|
||||
err = s.setupFallbackDNS()
|
||||
proxyConfig.Fallbacks, err = s.setupFallbackDNS()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up fallback dns servers: %w", err)
|
||||
}
|
||||
|
||||
s.recDetector.clear()
|
||||
dnsProxy, err := proxy.New(proxyConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating proxy: %w", err)
|
||||
}
|
||||
|
||||
s.dnsProxy = dnsProxy
|
||||
|
||||
s.setupAddrProc()
|
||||
|
||||
@@ -610,59 +508,149 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareInternalDNS initializes the internal state of s before initializing
|
||||
// the primary DNS proxy instance. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareInternalDNS() (boot upstream.Resolver, err error) {
|
||||
err = s.prepareIpsetListSettings()
|
||||
// prepareUpstreamSettings sets upstream DNS server settings.
|
||||
func (s *Server) prepareUpstreamSettings(boot upstream.Resolver) (err error) {
|
||||
// Load upstreams either from the file, or from the settings
|
||||
var upstreams []string
|
||||
upstreams, err = s.conf.loadUpstreams()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing ipset settings: %w", err)
|
||||
return fmt.Errorf("loading upstreams: %w", err)
|
||||
}
|
||||
|
||||
s.bootstrap, s.bootResolvers, err = s.createBootstrap(s.conf.BootstrapDNS, &upstream.Options{
|
||||
Timeout: DefaultTimeout,
|
||||
uc, err := newUpstreamConfig(upstreams, defaultDNS, &upstream.Options{
|
||||
Bootstrap: boot,
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
// Use a customized set of RootCAs, because Go's default mechanism of
|
||||
// loading TLS roots does not always work properly on some routers so we're
|
||||
// loading roots manually and pass it here.
|
||||
//
|
||||
// See [aghtls.SystemRootCAs].
|
||||
//
|
||||
// TODO(a.garipov): Investigate if that's true.
|
||||
RootCAs: s.conf.TLSv12Roots,
|
||||
CipherSuites: s.conf.TLSCiphers,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing upstream config: %w", err)
|
||||
}
|
||||
|
||||
s.conf.UpstreamConfig = uc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrivateRDNSError is returned when the private rDNS upstreams are
|
||||
// invalid but enabled.
|
||||
//
|
||||
// TODO(e.burkov): Consider allowing to use incomplete private rDNS upstreams
|
||||
// configuration in proxy when the private rDNS function is enabled. In theory,
|
||||
// proxy supports the case when no upstreams provided to resolve the private
|
||||
// request, since it already supports this for DNS64-prefixed PTR requests.
|
||||
type PrivateRDNSError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Error implements the [errors.Error] interface.
|
||||
func (e *PrivateRDNSError) Error() (s string) {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e *PrivateRDNSError) Unwrap() (err error) {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// prepareLocalResolvers initializes the private RDNS upstream configuration
|
||||
// according to the server's settings. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareLocalResolvers() (uc *proxy.UpstreamConfig, err error) {
|
||||
if !s.conf.UsePrivateRDNS {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ownAddrs addrPortSet
|
||||
ownAddrs, err = s.conf.ourAddrsSet()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := &upstream.Options{
|
||||
Bootstrap: s.bootstrap,
|
||||
Timeout: defaultLocalTimeout,
|
||||
// TODO(e.burkov): Should we verify server's certificates?
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
}
|
||||
|
||||
addrs := s.conf.LocalPTRResolvers
|
||||
uc, err = newPrivateConfig(addrs, ownAddrs, s.sysResolvers, s.privateNets, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preparing resolvers: %w", err)
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// prepareInternalDNS initializes the internal state of s before initializing
|
||||
// the primary DNS proxy instance. It assumes s.serverLock is locked or the
|
||||
// Server not running.
|
||||
func (s *Server) prepareInternalDNS() (err error) {
|
||||
err = s.prepareIpsetListSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing ipset settings: %w", err)
|
||||
}
|
||||
|
||||
bootOpts := &upstream.Options{
|
||||
Timeout: DefaultTimeout,
|
||||
HTTPVersions: UpstreamHTTPVersions(s.conf.UseHTTP3Upstreams),
|
||||
}
|
||||
|
||||
s.bootstrap, s.bootResolvers, err = newBootstrap(s.conf.BootstrapDNS, s.etcHosts, bootOpts)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.prepareUpstreamSettings(s.bootstrap)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return s.bootstrap, err
|
||||
return err
|
||||
}
|
||||
|
||||
s.conf.PrivateRDNSUpstreamConfig, err = s.prepareLocalResolvers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.prepareInternalProxy()
|
||||
if err != nil {
|
||||
return s.bootstrap, fmt.Errorf("preparing internal proxy: %w", err)
|
||||
return fmt.Errorf("preparing internal proxy: %w", err)
|
||||
}
|
||||
|
||||
return s.bootstrap, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupFallbackDNS initializes the fallback DNS servers.
|
||||
func (s *Server) setupFallbackDNS() (err error) {
|
||||
func (s *Server) setupFallbackDNS() (uc *proxy.UpstreamConfig, err error) {
|
||||
fallbacks := s.conf.FallbackDNS
|
||||
fallbacks = stringutil.FilterOut(fallbacks, IsCommentOrEmpty)
|
||||
if len(fallbacks) == 0 {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
uc, err := proxy.ParseUpstreamsConfig(fallbacks, &upstream.Options{
|
||||
uc, err = proxy.ParseUpstreamsConfig(fallbacks, &upstream.Options{
|
||||
// TODO(s.chzhen): Investigate if other options are needed.
|
||||
Timeout: s.conf.UpstreamTimeout,
|
||||
PreferIPv6: s.conf.BootstrapPreferIPv6,
|
||||
// TODO(e.burkov): Use bootstrap.
|
||||
})
|
||||
if err != nil {
|
||||
// Do not wrap the error because it's informative enough as is.
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.dnsProxy.Fallbacks = uc
|
||||
|
||||
return nil
|
||||
return uc, nil
|
||||
}
|
||||
|
||||
// setupAddrProc initializes the address processor. It assumes s.serverLock is
|
||||
@@ -719,10 +707,16 @@ func validateBlockingMode(
|
||||
func (s *Server) prepareInternalProxy() (err error) {
|
||||
srvConf := s.conf
|
||||
conf := &proxy.Config{
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
MaxGoroutines: s.conf.MaxGoroutines,
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
PrivateRDNSUpstreamConfig: srvConf.PrivateRDNSUpstreamConfig,
|
||||
UpstreamConfig: srvConf.UpstreamConfig,
|
||||
MaxGoroutines: srvConf.MaxGoroutines,
|
||||
UseDNS64: srvConf.UseDNS64,
|
||||
DNS64Prefs: srvConf.DNS64Prefixes,
|
||||
UsePrivateRDNS: srvConf.UsePrivateRDNS,
|
||||
PrivateSubnets: s.privateNets,
|
||||
MessageConstructor: s,
|
||||
}
|
||||
|
||||
err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration)
|
||||
@@ -730,19 +724,9 @@ func (s *Server) prepareInternalProxy() (err error) {
|
||||
return fmt.Errorf("invalid upstream mode: %w", err)
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Make a proper constructor for proxy.Proxy.
|
||||
p := &proxy.Proxy{
|
||||
Config: *conf,
|
||||
}
|
||||
s.internalProxy, err = proxy.New(conf)
|
||||
|
||||
err = p.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.internalProxy = p
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop stops the DNS server.
|
||||
@@ -761,15 +745,13 @@ func (s *Server) stopLocked() (err error) {
|
||||
// [upstream.Upstream] implementations.
|
||||
|
||||
if s.dnsProxy != nil {
|
||||
err = s.dnsProxy.Stop()
|
||||
// TODO(e.burkov): Use context properly.
|
||||
err = s.dnsProxy.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
log.Error("dnsforward: closing primary resolvers: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
logCloserErr(s.internalProxy.UpstreamConfig, "dnsforward: closing internal resolvers: %s")
|
||||
logCloserErr(s.localResolvers.UpstreamConfig, "dnsforward: closing local resolvers: %s")
|
||||
|
||||
for _, b := range s.bootResolvers {
|
||||
logCloserErr(b, "dnsforward: closing bootstrap %s: %s", b.Address())
|
||||
}
|
||||
@@ -841,6 +823,8 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): It seems an error here brings the server down, which is
|
||||
// not reliable enough.
|
||||
err = s.Prepare(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not reconfigure the server: %w", err)
|
||||
@@ -889,5 +873,5 @@ func (s *Server) IsBlockedClient(ip netip.Addr, clientID string) (blocked bool,
|
||||
blocked = true
|
||||
}
|
||||
|
||||
return blocked, aghalg.Coalesce(rule, clientID)
|
||||
return blocked, cmp.Or(rule, clientID)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cmp"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -19,7 +21,6 @@ import (
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
@@ -63,8 +64,7 @@ func startDeferStop(t *testing.T, s *Server) {
|
||||
t.Helper()
|
||||
|
||||
err := s.Start()
|
||||
require.NoErrorf(t, err, "failed to start server: %s", err)
|
||||
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, s.Stop)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ func createTestServer(
|
||||
t *testing.T,
|
||||
filterConf *filtering.Config,
|
||||
forwardConf ServerConfig,
|
||||
localUps upstream.Upstream,
|
||||
) (s *Server) {
|
||||
t.Helper()
|
||||
|
||||
@@ -82,7 +81,8 @@ func createTestServer(
|
||||
@@||whitelist.example.org^
|
||||
||127.0.0.255`
|
||||
filters := []filtering.Filter{{
|
||||
ID: 0, Data: []byte(rules),
|
||||
ID: 0,
|
||||
Data: []byte(rules),
|
||||
}}
|
||||
|
||||
f, err := filtering.New(filterConf, filters)
|
||||
@@ -105,19 +105,6 @@ func createTestServer(
|
||||
err = s.Prepare(&forwardConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.serverLock.Lock()
|
||||
defer s.serverLock.Unlock()
|
||||
|
||||
// TODO(e.burkov): Try to move it higher.
|
||||
if localUps != nil {
|
||||
ups := []upstream.Upstream{localUps}
|
||||
s.localResolvers.UpstreamConfig.Upstreams = ups
|
||||
s.conf.UsePrivateRDNS = true
|
||||
s.dnsProxy.PrivateRDNSUpstreamConfig = &proxy.UpstreamConfig{
|
||||
Upstreams: ups,
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -181,7 +168,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
})
|
||||
|
||||
tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem
|
||||
s.conf.TLSConfig = tlsConf
|
||||
@@ -202,7 +189,7 @@ func newGoogleUpstream() (u upstream.Upstream) {
|
||||
return &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "google.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, googleDomainName, "8.8.8.8"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -265,7 +252,7 @@ func sendTestMessagesAsync(t *testing.T, conn *dns.Conn) {
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for i := 0; i < testMessagesCount; i++ {
|
||||
for range testMessagesCount {
|
||||
msg := createGoogleATestMessage()
|
||||
wg.Add(1)
|
||||
|
||||
@@ -288,7 +275,7 @@ func sendTestMessagesAsync(t *testing.T, conn *dns.Conn) {
|
||||
func sendTestMessages(t *testing.T, conn *dns.Conn) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < testMessagesCount; i++ {
|
||||
for i := range testMessagesCount {
|
||||
req := createGoogleATestMessage()
|
||||
err := conn.WriteMsg(req)
|
||||
assert.NoErrorf(t, err, "cannot write message #%d: %s", i, err)
|
||||
@@ -310,7 +297,7 @@ func TestServer(t *testing.T) {
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -410,7 +397,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -490,7 +477,7 @@ func TestServerRace(t *testing.T) {
|
||||
ConfigModified: func() {},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s := createTestServer(t, filterConf, forwardConf)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
@@ -503,19 +490,10 @@ func TestServerRace(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
resolver := &aghtest.Resolver{
|
||||
OnLookupIP: func(_ context.Context, _, host string) (ips []net.IP, err error) {
|
||||
ip4, ip6 := aghtest.HostToIPs(host)
|
||||
|
||||
return []net.IP{ip4.AsSlice(), ip6.AsSlice()}, nil
|
||||
},
|
||||
}
|
||||
|
||||
safeSearchConf := filtering.SafeSearchConfig{
|
||||
Enabled: true,
|
||||
Google: true,
|
||||
Yandex: true,
|
||||
CustomResolver: resolver,
|
||||
Enabled: true,
|
||||
Google: true,
|
||||
Yandex: true,
|
||||
}
|
||||
|
||||
filterConf := &filtering.Config{
|
||||
@@ -545,14 +523,13 @@ func TestSafeSearch(t *testing.T) {
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s := createTestServer(t, filterConf, forwardConf)
|
||||
startDeferStop(t, s)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
client := &dns.Client{}
|
||||
|
||||
yandexIP := netip.AddrFrom4([4]byte{213, 180, 193, 56})
|
||||
googleIP, _ := aghtest.HostToIPs("forcesafesearch.google.com")
|
||||
|
||||
testCases := []struct {
|
||||
host string
|
||||
@@ -576,19 +553,19 @@ func TestSafeSearch(t *testing.T) {
|
||||
wantCNAME: "",
|
||||
}, {
|
||||
host: "www.google.com.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.com.af.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.be.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}, {
|
||||
host: "www.google.by.",
|
||||
want: googleIP,
|
||||
want: netip.Addr{},
|
||||
wantCNAME: "forcesafesearch.google.com.",
|
||||
}}
|
||||
|
||||
@@ -605,12 +582,15 @@ func TestSafeSearch(t *testing.T) {
|
||||
|
||||
cname := testutil.RequireTypeAssert[*dns.CNAME](t, reply.Answer[0])
|
||||
assert.Equal(t, tc.wantCNAME, cname.Target)
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[1])
|
||||
assert.NotEmpty(t, a.A)
|
||||
} else {
|
||||
require.Len(t, reply.Answer, 1)
|
||||
}
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[len(reply.Answer)-1])
|
||||
assert.Equal(t, net.IP(tc.want.AsSlice()), a.A)
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, reply.Answer[0])
|
||||
assert.Equal(t, net.IP(tc.want.AsSlice()), a.A)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -628,7 +608,7 @@ func TestInvalidRequest(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
})
|
||||
startDeferStop(t, s)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP).String()
|
||||
@@ -662,7 +642,7 @@ func TestBlockedRequest(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
startDeferStop(t, s)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
@@ -698,12 +678,12 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
atomic.AddUint32(&upsCalledCounter, 1)
|
||||
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -773,7 +753,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}, nil)
|
||||
})
|
||||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
IPv4: testIPv4,
|
||||
@@ -811,7 +791,7 @@ func TestBlockCNAME(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
@@ -886,7 +866,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) {
|
||||
}
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
@@ -933,7 +913,7 @@ func TestNullBlockedRequest(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: filtering.BlockingModeNullIP,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
startDeferStop(t, s)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
@@ -1054,7 +1034,7 @@ func TestBlockedByHosts(t *testing.T) {
|
||||
s := createTestServer(t, &filtering.Config{
|
||||
ProtectionEnabled: true,
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, forwardConf, nil)
|
||||
}, forwardConf)
|
||||
startDeferStop(t, s)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
@@ -1102,7 +1082,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
||||
},
|
||||
ServePlainDNS: true,
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s := createTestServer(t, filterConf, forwardConf)
|
||||
startDeferStop(t, s)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
@@ -1164,7 +1144,7 @@ func TestRewrite(t *testing.T) {
|
||||
}))
|
||||
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
return cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "example.org", "4.3.2.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
@@ -1330,6 +1310,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))
|
||||
|
||||
@@ -1481,6 +1462,8 @@ func TestServer_Exchange(t *testing.T) {
|
||||
onesIP = netip.MustParseAddr("1.1.1.1")
|
||||
twosIP = netip.MustParseAddr("2.2.2.2")
|
||||
localIP = netip.MustParseAddr("192.168.1.1")
|
||||
|
||||
pt = testutil.PanicT{}
|
||||
)
|
||||
|
||||
onesRevExtIPv4, err := netutil.IPToReversedAddr(onesIP.AsSlice())
|
||||
@@ -1489,72 +1472,73 @@ func TestServer_Exchange(t *testing.T) {
|
||||
twosRevExtIPv4, err := netutil.IPToReversedAddr(twosIP.AsSlice())
|
||||
require.NoError(t, err)
|
||||
|
||||
extUpstream := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "external.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, onesRevExtIPv4, onesHost),
|
||||
doubleTTL(aghtest.MatchedResponse(req, dns.TypePTR, twosRevExtIPv4, twosHost)),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
},
|
||||
}
|
||||
extUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, onesRevExtIPv4, dns.Fqdn(onesHost)),
|
||||
doubleTTL(aghtest.MatchedResponse(req, dns.TypePTR, twosRevExtIPv4, dns.Fqdn(twosHost))),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
upsAddr := aghtest.StartLocalhostUpstream(t, extUpsHdlr).String()
|
||||
|
||||
revLocIPv4, err := netutil.IPToReversedAddr(localIP.AsSlice())
|
||||
require.NoError(t, err)
|
||||
|
||||
locUpstream := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "local.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revLocIPv4, localDomainHost),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
},
|
||||
}
|
||||
locUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := cmp.Or(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revLocIPv4, dns.Fqdn(localDomainHost)),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
errUpstream := aghtest.NewErrorUpstream()
|
||||
nonPtrUpstream := aghtest.NewBlockUpstream("some-host", true)
|
||||
refusingUpstream := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return new(dns.Msg).SetRcode(req, dns.RcodeRefused), nil
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
zeroTTLUps := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "zero.ttl.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = new(dns.Msg).SetReply(req)
|
||||
hdr := dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
}
|
||||
resp.Answer = []dns.RR{&dns.PTR{
|
||||
Hdr: hdr,
|
||||
Ptr: localDomainHost,
|
||||
}}
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
}
|
||||
errUpsHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
require.NoError(pt, w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeServerFailure)))
|
||||
})
|
||||
|
||||
srv := &Server{
|
||||
recDetector: newRecursionDetector(0, 1),
|
||||
internalProxy: &proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{extUpstream},
|
||||
nonPtrHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
hash := sha256.Sum256([]byte("some-host"))
|
||||
resp := (&dns.Msg{
|
||||
Answer: []dns.RR{&dns.TXT{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypeTXT,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
srv.conf.UsePrivateRDNS = true
|
||||
srv.privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
|
||||
require.NoError(t, srv.internalProxy.Init())
|
||||
Txt: []string{hex.EncodeToString(hash[:])},
|
||||
}},
|
||||
}).SetReply(req)
|
||||
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
refusingHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
require.NoError(pt, w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeRefused)))
|
||||
})
|
||||
|
||||
zeroTTLHdlr := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
resp := (&dns.Msg{
|
||||
Answer: []dns.RR{&dns.PTR{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
},
|
||||
Ptr: dns.Fqdn(localDomainHost),
|
||||
}},
|
||||
}).SetReply(req)
|
||||
|
||||
require.NoError(pt, w.WriteMsg(resp))
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
req netip.Addr
|
||||
wantErr error
|
||||
locUpstream upstream.Upstream
|
||||
locUpstream dns.Handler
|
||||
name string
|
||||
want string
|
||||
wantTTL time.Duration
|
||||
@@ -1569,35 +1553,35 @@ func TestServer_Exchange(t *testing.T) {
|
||||
name: "local_good",
|
||||
want: localDomainHost,
|
||||
wantErr: nil,
|
||||
locUpstream: locUpstream,
|
||||
locUpstream: locUpsHdlr,
|
||||
req: localIP,
|
||||
wantTTL: defaultTTL,
|
||||
}, {
|
||||
name: "upstream_error",
|
||||
want: "",
|
||||
wantErr: aghtest.ErrUpstream,
|
||||
locUpstream: errUpstream,
|
||||
wantErr: ErrRDNSFailed,
|
||||
locUpstream: errUpsHdlr,
|
||||
req: localIP,
|
||||
wantTTL: 0,
|
||||
}, {
|
||||
name: "empty_answer_error",
|
||||
want: "",
|
||||
wantErr: ErrRDNSNoData,
|
||||
locUpstream: locUpstream,
|
||||
locUpstream: locUpsHdlr,
|
||||
req: netip.MustParseAddr("192.168.1.2"),
|
||||
wantTTL: 0,
|
||||
}, {
|
||||
name: "invalid_answer",
|
||||
want: "",
|
||||
wantErr: ErrRDNSNoData,
|
||||
locUpstream: nonPtrUpstream,
|
||||
locUpstream: nonPtrHdlr,
|
||||
req: localIP,
|
||||
wantTTL: 0,
|
||||
}, {
|
||||
name: "refused",
|
||||
want: "",
|
||||
wantErr: ErrRDNSFailed,
|
||||
locUpstream: refusingUpstream,
|
||||
locUpstream: refusingHdlr,
|
||||
req: localIP,
|
||||
wantTTL: 0,
|
||||
}, {
|
||||
@@ -1611,23 +1595,28 @@ func TestServer_Exchange(t *testing.T) {
|
||||
name: "zero_ttl",
|
||||
want: localDomainHost,
|
||||
wantErr: nil,
|
||||
locUpstream: zeroTTLUps,
|
||||
locUpstream: zeroTTLHdlr,
|
||||
req: localIP,
|
||||
wantTTL: 0,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
pcfg := proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{tc.locUpstream},
|
||||
},
|
||||
}
|
||||
srv.localResolvers = &proxy.Proxy{
|
||||
Config: pcfg,
|
||||
}
|
||||
require.NoError(t, srv.localResolvers.Init())
|
||||
localUpsAddr := aghtest.StartLocalhostUpstream(t, tc.locUpstream).String()
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
Config: Config{
|
||||
UpstreamDNS: []string{upsAddr},
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
LocalPTRResolvers: []string{localUpsAddr},
|
||||
UsePrivateRDNS: true,
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
|
||||
host, ttl, eerr := srv.Exchange(tc.req)
|
||||
|
||||
require.ErrorIs(t, eerr, tc.wantErr)
|
||||
@@ -1637,8 +1626,17 @@ func TestServer_Exchange(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("resolving_disabled", func(t *testing.T) {
|
||||
srv.conf.UsePrivateRDNS = false
|
||||
t.Cleanup(func() { srv.conf.UsePrivateRDNS = true })
|
||||
srv := createTestServer(t, &filtering.Config{
|
||||
BlockingMode: filtering.BlockingModeDefault,
|
||||
}, ServerConfig{
|
||||
Config: Config{
|
||||
UpstreamDNS: []string{upsAddr},
|
||||
UpstreamMode: UpstreamModeLoadBalance,
|
||||
EDNSClientSubnet: &EDNSClientSubnet{Enabled: false},
|
||||
},
|
||||
LocalPTRResolvers: []string{},
|
||||
ServePlainDNS: true,
|
||||
})
|
||||
|
||||
host, _, eerr := srv.Exchange(localIP)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user